23 Commits
Author SHA1 Message Date
outfoxxed a1a150fab0 version: bump to 0.2.1 2025-10-11 17:14:14 -07:00
Gregor Kleen 40bc09b800 service/greetd: always send responses 2025-10-11 17:06:06 -07:00
bbedward a94418f45d core/desktopentry: don't match keys with wrong modifier or country 2025-10-11 17:06:06 -07:00
bbedward 8d2a2d3dd2 core/desktopentry: mask entries with priority less than hidden entry 2025-10-11 17:06:06 -07:00
outfoxxed e10747addd all: fix gcc warnings and lints 2025-10-11 17:06:06 -07:00
outfoxxed b254f6dabc nix: remove qtwayland dependency when qt >= 6.10
QtWaylandClient was moved into QtBase in 6.10. The QtWayland packages
is now only the wayland server code which Quickshell does not need.
2025-10-11 17:06:06 -07:00
outfoxxed 7fc6635ca8 wayland/lock: support Qt 6.10 2025-10-11 17:06:06 -07:00
outfoxxed 522d126d1b services/pipewire: consider device volume step when sending updates
Previously a hardcoded 0.0001 offset was used to determine if a volume
change was significant enough to send to a device, however some
devices have a much more granular step size, which caused future
volume updates to be blocked.

This change replaces the hardcoded offset with the volumeStep device
route property which should be large enough for the device to work with.

Fixes #279
2025-10-11 17:06:06 -07:00
outfoxxed f5ca8453c0 docs: start tracking qs-next changelog 2025-10-11 17:06:04 -07:00
outfoxxed e81e6da08f ci: fix magic-nix-cache write permissions 2025-10-11 17:05:30 -07:00
outfoxxed f73729754d build: explicitly depend on private qt modules
In Qt 6.10, private Qt modules must be depended on explicitly.
2025-10-11 17:05:30 -07:00
outfoxxed 344ca340ba all: fix lints 2025-10-11 17:05:26 -07:00
outfoxxed 6cc1b6f36a nix: update flake + tidyfox 2025-10-11 17:04:23 -07:00
outfoxxed 354afeaa35 ci: add detsys nix cache 2025-10-11 17:04:23 -07:00
outfoxxed 62df8d917f ci: use unwrapped package for dependencies derivation
Since adding the wrapper, CI built qs as it was a dependency of the
wrapper instead of dependencies of qs itself.
2025-10-11 17:04:23 -07:00
outfoxxed 2f0f2d1201 ci: add qt 6.9.2 and 6.9.1 checkouts 2025-10-11 17:04:23 -07:00
outfoxxed 4b825e7051 nix: add overlay 2025-10-11 17:04:23 -07:00
outfoxxed d4b19e4a30 build: fix cross compilation 2025-10-11 17:04:23 -07:00
outfoxxed b9cce25061 core: derive incubation controllers from tracked windows list
Replaces the attempts to track incubation controllers directly with a
list of all known windows, then pulls the first usable incubation
controller when an assignment is requested.

This should finally fix incubation controller related use after free crashes.
2025-10-11 17:04:23 -07:00
bbedward 996efc93b7 core/desktopentry: watch for changes and rescan entries 2025-10-11 17:04:22 -07:00
outfoxxed 2115f31416 ci: use latest wayland-protocol for all test cases
Fixes missing protocols on old nixpkgs versions
2025-10-11 17:04:22 -07:00
kossLAN e4d33fa52f hyprland/ipc: fix focusedWorkspaceChanged connection 2025-10-11 17:04:22 -07:00
Derock 83f5af522d core/log: fix nullptr crash in ThreadLogging 2025-10-11 17:04:00 -07:00
238 changed files with 1983 additions and 12161 deletions

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
AlignArrayOfStructures: None
AlignAfterOpenBracket: BlockIndent
AllowShortBlocksOnASingleLine: Empty
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: true
AllowShortEnumsOnASingleLine: true
AllowShortFunctionsOnASingleLine: All
-4
View File
@@ -20,8 +20,6 @@ Checks: >
-cppcoreguidelines-avoid-do-while,
-cppcoreguidelines-pro-type-reinterpret-cast,
-cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-use-enum-class,
google-global-names-in-headers,
google-readability-casting,
google-runtime-int,
@@ -65,8 +63,6 @@ CheckOptions:
readability-identifier-naming.ParameterCase: camelBack
readability-identifier-naming.VariableCase: camelBack
misc-const-correctness.WarnPointersAsPointers: false
# does not appear to work
readability-operators-representation.BinaryOperators: '&&;&=;&;|;~;!;!=;||;|=;^;^='
readability-operators-representation.OverloadedOperators: '&&;&=;&;|;~;!;!=;||;|=;^;^='
+1 -1
View File
@@ -1,4 +1,4 @@
name: Crash Report (v1)
name: Crash Report
description: Quickshell has crashed
labels: ["bug", "crash"]
body:
-49
View File
@@ -1,49 +0,0 @@
name: Crash Report (v2)
description: Quickshell has crashed
labels: ["bug", "crash"]
body:
- type: textarea
id: userinfo
attributes:
label: What caused the crash
description: |
Any information likely to help debug the crash. What were you doing when the crash occurred,
what changes did you make, can you get it to happen again?
- type: textarea
id: report
attributes:
label: Report file
description: Attach `report.txt` here.
validations:
required: true
- type: textarea
id: logs
attributes:
label: Log file
description: |
Attach `log.qslog.log` here. If it is too big to upload, compress it.
You can preview the log if you'd like using `quickshell read-log <path-to-log>`.
validations:
required: true
- type: textarea
id: config
attributes:
label: Configuration
description: |
Attach your configuration here, preferrably in full (not just one file).
Compress it into a zip, tar, etc.
This will help us reproduce the crash ourselves.
- type: textarea
id: bt
attributes:
label: Backtrace
description: |
GDB usually produces better stacktraces than quickshell can. Consider attaching a gdb backtrace
following the instructions below.
1. Run `coredumpctl debug <pid>` where `pid` is the number shown after "Crashed process ID"
in the crash reporter.
2. Once it loads, type `bt -full` (then enter)
3. Copy the output and attach it as a file or in a spoiler.
+4 -7
View File
@@ -6,7 +6,7 @@ jobs:
name: Nix
strategy:
matrix:
qtver: [qt6.10.1, qt6.10.0, qt6.9.2, qt6.9.1, qt6.9.0, qt6.8.3, qt6.8.2, qt6.8.1, qt6.8.0, qt6.7.3, qt6.7.2, qt6.7.1, qt6.7.0, qt6.6.3, qt6.6.2, qt6.6.1, qt6.6.0]
qtver: [qt6.9.2, qt6.9.1, qt6.9.0, qt6.8.3, qt6.8.2, qt6.8.1, qt6.8.0, qt6.7.3, qt6.7.2, qt6.7.1, qt6.7.0, qt6.6.3, qt6.6.2, qt6.6.1, qt6.6.0]
compiler: [clang, gcc]
runs-on: ubuntu-latest
permissions:
@@ -50,16 +50,13 @@ jobs:
wayland-protocols \
wayland \
libdrm \
vulkan-headers \
libxcb \
libpipewire \
cli11 \
polkit \
jemalloc \
libunwind \
git # for cpptrace clone
jemalloc
- name: Build
# breakpad is annoying to build in ci due to makepkg not running as root
run: |
cmake -GNinja -B build -DVENDOR_CPPTRACE=ON
cmake -GNinja -B build -DCRASH_REPORTER=OFF
cmake --build build
+15 -25
View File
@@ -15,7 +15,15 @@ Please make this descriptive enough to identify your specific package, for examp
- `Nixpkgs`
- `Fedora COPR (errornointernet/quickshell)`
If you are forking quickshell, please change `CRASHREPORT_URL` to your own issue tracker.
`-DDISTRIBUTOR_DEBUGINFO_AVAILABLE=YES/NO`
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
Currently all QML modules are statically linked to quickshell, but this is where
@@ -33,7 +41,6 @@ Quickshell has a set of base dependencies you will always need, names vary by di
- `cmake`
- `qt6base`
- `qt6declarative`
- `libdrm`
- `qtshadertools` (build-time)
- `spirv-tools` (build-time)
- `pkg-config` (build-time)
@@ -57,24 +64,14 @@ At least Qt 6.6 is required.
All features are enabled by default and some have their own dependencies.
### Crash Handler
The crash reporter catches crashes, restarts Quickshell when it crashes,
### Crash Reporter
The crash reporter catches crashes, restarts quickshell when it crashes,
and collects useful crash information in one place. Leaving this enabled will
enable us to fix bugs far more easily.
To disable: `-DCRASH_HANDLER=OFF`
To disable: `-DCRASH_REPORTER=OFF`
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.
Dependencies: `google-breakpad` (static library)
### Jemalloc
We recommend leaving Jemalloc enabled as it will mask memory fragmentation caused
@@ -147,8 +144,8 @@ Enables streaming video from monitors and toplevel windows through various proto
To disable: `-DSCREENCOPY=OFF`
Dependencies:
- `libdrm`
- `libgbm`
- `vulkan-headers` (build-time)
Specific protocols can also be disabled:
- `DSCREENCOPY_ICC=OFF` - Disable screencopy via [ext-image-copy-capture-v1]
@@ -195,13 +192,6 @@ To disable: `-DSERVICE_PAM=OFF`
Dependencies: `pam`
### Polkit
This feature enables creating Polkit agents that can prompt user for authentication.
To disable: `-DSERVICE_POLKIT=OFF`
Dependencies: `polkit`, `glib`
### Hyprland
This feature enables hyprland specific integrations. It requires wayland support
but has no extra dependencies.
@@ -242,7 +232,7 @@ Only `ninja` builds are tested, but makefiles may work.
#### Configuring the build
```sh
$ cmake -GNinja -B build -DCMAKE_BUILD_TYPE=Release [additional disable flags from above here]
$ cmake -GNinja -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo [additional disable flags from above here]
```
Note that features you do not supply dependencies for MUST be disabled with their associated flags
+4 -14
View File
@@ -1,17 +1,12 @@
cmake_minimum_required(VERSION 3.20)
project(quickshell VERSION "0.2.1" LANGUAGES CXX C)
set(UNRELEASED_FEATURES)
set(QT_MIN_VERSION "6.6.0")
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
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)
cmake_parse_arguments(PARSE_ARGV 3 arg "" "REQUIRES" "")
@@ -43,17 +38,14 @@ string(APPEND QS_BUILD_OPTIONS " Distributor: ${DISTRIBUTOR}")
message(STATUS "Quickshell configuration")
message(STATUS " Distributor: ${DISTRIBUTOR}")
boption(DISTRIBUTOR_DEBUGINFO_AVAILABLE "Distributor provided debuginfo" NO)
boption(NO_PCH "Disable precompild headers (dev)" OFF)
boption(BUILD_TESTING "Build tests (dev)" OFF)
boption(ASAN "ASAN (dev)" OFF) # note: better output with gcc than clang
boption(FRAME_POINTERS "Keep Frame Pointers (dev)" ${ASAN})
if (CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
boption(USE_JEMALLOC "Use jemalloc" OFF)
else()
boption(USE_JEMALLOC "Use jemalloc" ON)
endif()
boption(CRASH_HANDLER "Crash Handling" ON)
boption(CRASH_REPORTER "Crash Handling" ON)
boption(USE_JEMALLOC "Use jemalloc" ON)
boption(SOCKETS "Unix Sockets" ON)
boption(WAYLAND "Wayland" ON)
boption(WAYLAND_WLR_LAYERSHELL " Wlroots Layer-Shell" ON REQUIRES WAYLAND)
@@ -75,12 +67,10 @@ boption(SERVICE_STATUS_NOTIFIER "System Tray" ON)
boption(SERVICE_PIPEWIRE "PipeWire" ON)
boption(SERVICE_MPRIS "Mpris" ON)
boption(SERVICE_PAM "Pam" ON)
boption(SERVICE_POLKIT "Polkit" ON)
boption(SERVICE_GREETD "Greetd" ON)
boption(SERVICE_UPOWER "UPower" ON)
boption(SERVICE_NOTIFICATIONS "Notifications" ON)
boption(BLUETOOTH "Bluetooth" ON)
boption(NETWORK "Network" ON)
include(cmake/install-qml-module.cmake)
include(cmake/util.cmake)
@@ -129,7 +119,7 @@ if (WAYLAND)
list(APPEND QT_PRIVDEPS WaylandClientPrivate)
endif()
if (SERVICE_STATUS_NOTIFIER OR SERVICE_MPRIS OR SERVICE_UPOWER OR SERVICE_NOTIFICATIONS OR BLUETOOTH OR NETWORK)
if (SERVICE_STATUS_NOTIFIER OR SERVICE_MPRIS OR SERVICE_UPOWER OR SERVICE_NOTIFICATIONS OR BLUETOOTH)
set(DBUS ON)
endif()
+221 -26
View File
@@ -1,40 +1,235 @@
# Contributing
# Contributing / Development
Instructions for development setup and upstreaming patches.
Thank you for taking the time to contribute.
To ensure nobody's time is wasted, please follow the rules below.
If you just want to build or package quickshell see [BUILD.md](BUILD.md).
## Acceptable Code Contributions
## Development
- All changes submitted MUST be **fully understood by the submitter**. If you do not know why or how
your change works, do not submit it to be merged. You must be able to explain your reasoning
for every change.
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.
- 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**.
Quickshell also uses `just` for common development command aliases.
- Changes MUST respect Quickshell's license and the license of any source works. Changes including
code from any other works must disclose the source of the code, explain why it was used, and
ensure the license is compatible.
The dependencies are also available as a nix shell or nix flake which we recommend
using with nix-direnv.
- Changes must follow the guidelines outlined in [HACKING.md](HACKING.md) for style and substance.
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.
- Changes must stand on their own as a unit. Do not make multiple unrelated changes in one PR.
Changes depending on prior merges should be marked as a draft.
### Formatting
All contributions should be formatted similarly to what already exists.
Group related functionality together.
## Acceptable Non-code Contributions
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`.
- Bug and crash reports. You must follow the instructions in the issue templates and provide the
information requested.
#### 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.
- Feature requests can be made via Issues. Please check to ensure nobody else has requested the same feature.
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.
- Do not make insubstantial or pointless changes.
```cpp
auto x = <expr>; // ok
auto x = QString::number(3); // ok
QString x; // ok
QString x = "foo"; // ok
auto x = QString("foo"); // ok
- Changes to project rules / policy / governance will not be entertained, except from significant
long-term contributors. These changes should not be addressed through contribution channels.
auto x = QString(); // avoid
QString x(); // avoid
QString x("foo"); // avoid
```
## Merge timelines
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
We handle work for the most part on a push basis. If your PR has been ignored for a while
and is still relevant please bump it.
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);
};
```
### 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
View File
@@ -1,226 +0,0 @@
## 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.
+1 -1
View File
@@ -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") }}
lint-staged:
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") }}
git diff --staged --name-only HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
configure target='debug' *FLAGS='':
cmake -GNinja -B {{builddir}} \
+1 -3
View File
@@ -7,9 +7,7 @@ This repo is hosted at:
- https://github.com/quickshell-mirror/quickshell
# Contributing / Development
- [HACKING.md](HACKING.md) - Development instructions and policy.
- [CONTRIBUTING.md](CONTRIBUTING.md) - Contribution policy.
- [BUILD.md](BUILD.md) - Packaging and build instructions.
See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
#### License
-68
View File
@@ -1,68 +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.
- Added generic WindowManager interface implementing ext-workspace.
## Other Changes
- FreeBSD is now partially supported.
- IPC operations filter available instances to the current display connection by default.
- PwNodeLinkTracker ignores sound level monitoring programs.
- Replaced breakpad with cpptrace.
- 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.
## 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.
- Fixed ToplevelManager not clearing activeToplevel on deactivation.
- Desktop action order is now preserved.
- Fixed partial socket reads in greetd and hyprland on slow machines.
## 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.
+1 -11
View File
@@ -8,17 +8,7 @@ let
inherit sha256;
}) {};
in rec {
latest = qt6_10_0;
qt6_10_1 = byCommit {
commit = "ffbc9f8cbaacfb331b6017d5a5abb21a492c9a38";
sha256 = "0fvbizl7j5rv2rf8j76yw0xb3d9l06hahkjys2a7k1yraznvnafm";
};
qt6_10_0 = byCommit {
commit = "c5ae371f1a6a7fd27823bc500d9390b38c05fa55";
sha256 = "18g0f8cb9m8mxnz9cf48sks0hib79b282iajl2nysyszph993yp0";
};
latest = qt6_9_0;
qt6_9_2 = byCommit {
commit = "e9f00bd893984bc8ce46c895c3bf7cac95331127";
+7 -26
View File
@@ -10,23 +10,17 @@
ninja,
spirv-tools,
qt6,
cpptrace ? null,
libunwind,
libdwarf,
breakpad,
jemalloc,
cli11,
wayland,
wayland-protocols,
wayland-scanner,
xorg,
libxcb ? xorg.libxcb,
libdrm,
libgbm ? null,
vulkan-headers,
pipewire,
pam,
polkit,
glib,
gitRev ? (let
headExists = builtins.pathExists ./.git/HEAD;
@@ -49,14 +43,10 @@
withPam ? true,
withHyprland ? true,
withI3 ? true,
withPolkit ? true,
withNetworkManager ? true,
}: let
withCrashHandler = withCrashReporter && cpptrace != null && lib.strings.compareVersions cpptrace.version "0.7.2" >= 0;
unwrapped = stdenv.mkDerivation {
pname = "quickshell${lib.optionalString debug "-debug"}";
version = "0.2.1";
version = "0.2.0";
src = nix-gitignore.gitignoreSource "/default.nix\n" ./.;
dontWrapQtApps = true; # see wrappers
@@ -76,24 +66,17 @@
buildInputs = [
qt6.qtbase
qt6.qtdeclarative
libdrm
cli11
]
++ lib.optional withQtSvg qt6.qtsvg
++ lib.optional withCrashHandler (cpptrace.overrideAttrs (prev: {
cmakeFlags = prev.cmakeFlags ++ [
"-DCPPTRACE_UNWIND_WITH_LIBUNWIND=TRUE"
];
buildInputs = prev.buildInputs ++ [ libunwind ];
}))
++ lib.optional withCrashReporter breakpad
++ lib.optional withJemalloc jemalloc
++ lib.optional (withWayland && lib.strings.compareVersions qt6.qtbase.version "6.10.0" == -1) qt6.qtwayland
++ lib.optionals withWayland [ wayland wayland-protocols ]
++ lib.optionals (withWayland && libgbm != null) [ libgbm vulkan-headers ]
++ lib.optional withX11 libxcb
++ lib.optionals (withWayland && libgbm != null) [ libdrm libgbm ]
++ lib.optional withX11 xorg.libxcb
++ lib.optional withPam pam
++ lib.optional withPipewire pipewire
++ lib.optionals withPolkit [ polkit glib ];
++ lib.optional withPipewire pipewire;
cmakeBuildType = if debug then "Debug" else "RelWithDebInfo";
@@ -102,14 +85,12 @@
(lib.cmakeFeature "INSTALL_QML_PREFIX" qt6.qtbase.qtQmlPrefix)
(lib.cmakeBool "DISTRIBUTOR_DEBUGINFO_AVAILABLE" true)
(lib.cmakeFeature "GIT_REVISION" gitRev)
(lib.cmakeBool "CRASH_HANDLER" withCrashHandler)
(lib.cmakeBool "CRASH_REPORTER" withCrashReporter)
(lib.cmakeBool "USE_JEMALLOC" withJemalloc)
(lib.cmakeBool "WAYLAND" withWayland)
(lib.cmakeBool "SCREENCOPY" (libgbm != null))
(lib.cmakeBool "SERVICE_PIPEWIRE" withPipewire)
(lib.cmakeBool "SERVICE_PAM" withPam)
(lib.cmakeBool "SERVICE_NETWORKMANAGER" withNetworkManager)
(lib.cmakeBool "SERVICE_POLKIT" withPolkit)
(lib.cmakeBool "HYPRLAND" withHyprland)
(lib.cmakeBool "I3" withI3)
];
Generated
+3 -3
View File
@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1768127708,
"narHash": "sha256-1Sm77VfZh3mU0F5OqKABNLWxOuDeHIlcFjsXeeiPazs=",
"lastModified": 1758690382,
"narHash": "sha256-NY3kSorgqE5LMm1LqNwGne3ZLMF2/ILgLpFr1fS4X3o=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "ffbc9f8cbaacfb331b6017d5a5abb21a492c9a38",
"rev": "e643668fd71b949c53f8626614b21ff71a07379d",
"type": "github"
},
"original": {
+2 -2
View File
@@ -42,7 +42,6 @@
libxcb
libxkbcommon
linux-pam
polkit
mesa
pipewire
qtbase
@@ -56,7 +55,8 @@
#~(list "-GNinja"
"-DDISTRIBUTOR=\"In-tree Guix channel\""
"-DDISTRIBUTOR_DEBUGINFO_AVAILABLE=NO"
"-DCRASH_HANDLER=OFF")
;; Breakpad is not currently packaged for Guix.
"-DCRASH_REPORTER=OFF")
#:phases
#~(modify-phases %standard-phases
(replace 'build (lambda _ (invoke "cmake" "--build" ".")))
+1 -6
View File
@@ -11,9 +11,8 @@ add_subdirectory(window)
add_subdirectory(io)
add_subdirectory(widgets)
add_subdirectory(ui)
add_subdirectory(windowmanager)
if (CRASH_HANDLER)
if (CRASH_REPORTER)
add_subdirectory(crash)
endif()
@@ -34,7 +33,3 @@ add_subdirectory(services)
if (BLUETOOTH)
add_subdirectory(bluetooth)
endif()
if (NETWORK)
add_subdirectory(network)
endif()
+1
View File
@@ -9,6 +9,7 @@
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qstring.h>
#include <qstringliteral.h>
#include <qtypes.h>
#include "../core/logcat.hpp"
+1
View File
@@ -8,6 +8,7 @@
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qstring.h>
#include <qstringliteral.h>
#include <qtmetamacros.h>
#include <qtypes.h>
+9 -3
View File
@@ -9,10 +9,16 @@ if (NOT DEFINED GIT_REVISION)
)
endif()
if (CRASH_HANDLER)
set(CRASH_HANDLER_DEF 1)
if (CRASH_REPORTER)
set(CRASH_REPORTER_DEF 1)
else()
set(CRASH_HANDLER_DEF 0)
set(CRASH_REPORTER_DEF 0)
endif()
if (DISTRIBUTOR_DEBUGINFO_AVAILABLE)
set(DEBUGINFO_AVAILABLE 1)
else()
set(DEBUGINFO_AVAILABLE 0)
endif()
configure_file(build.hpp.in build.hpp @ONLY ESCAPE_QUOTES)
+2 -7
View File
@@ -1,17 +1,12 @@
#pragma once
// NOLINTBEGIN
#define QS_VERSION "@quickshell_VERSION@"
#define QS_VERSION_MAJOR @quickshell_VERSION_MAJOR@
#define QS_VERSION_MINOR @quickshell_VERSION_MINOR@
#define QS_VERSION_PATCH @quickshell_VERSION_PATCH@
#define QS_UNRELEASED_FEATURES "@UNRELEASED_FEATURES@"
#define GIT_REVISION "@GIT_REVISION@"
#define DISTRIBUTOR "@DISTRIBUTOR@"
#define CRASH_HANDLER @CRASH_HANDLER_DEF@
#define DISTRIBUTOR_DEBUGINFO_AVAILABLE @DEBUGINFO_AVAILABLE@
#define CRASH_REPORTER @CRASH_REPORTER_DEF@
#define BUILD_TYPE "@CMAKE_BUILD_TYPE@"
#define COMPILER "@CMAKE_CXX_COMPILER_ID@ (@CMAKE_CXX_COMPILER_VERSION@)"
#define COMPILE_FLAGS "@CMAKE_CXX_FLAGS@"
#define BUILD_CONFIGURATION "@QS_BUILD_OPTIONS@"
#define CRASHREPORT_URL "@CRASHREPORT_URL@"
// NOLINTEND
+2 -5
View File
@@ -1,4 +1,3 @@
pkg_check_modules(libdrm REQUIRED IMPORTED_TARGET libdrm)
qt_add_library(quickshell-core STATIC
plugin.cpp
shell.cpp
@@ -13,7 +12,6 @@ qt_add_library(quickshell-core STATIC
singleton.cpp
generation.cpp
scan.cpp
scanenv.cpp
qsintercept.cpp
incubator.cpp
lazyloader.cpp
@@ -26,6 +24,7 @@ qt_add_library(quickshell-core STATIC
elapsedtimer.cpp
desktopentry.cpp
desktopentrymonitor.cpp
objectrepeater.cpp
platformmenu.cpp
qsmenu.cpp
retainable.cpp
@@ -41,8 +40,6 @@ qt_add_library(quickshell-core STATIC
scriptmodel.cpp
colorquantizer.cpp
toolsupport.cpp
streamreader.cpp
debuginfo.cpp
)
qt_add_qml_module(quickshell-core
@@ -55,7 +52,7 @@ qt_add_qml_module(quickshell-core
install_qml_module(quickshell-core)
target_link_libraries(quickshell-core PRIVATE Qt::Quick Qt::QuickPrivate Qt::Widgets quickshell-build PkgConfig::libdrm)
target_link_libraries(quickshell-core PRIVATE Qt::Quick Qt::Widgets)
qs_module_pch(quickshell-core SET large)
-175
View File
@@ -1,175 +0,0 @@
#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, 5> {
"QS_",
"QT_",
"QML_",
"QML2_",
"QSG_",
};
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
-14
View File
@@ -1,14 +0,0 @@
#pragma once
#include <qcontainerfwd.h>
namespace qs::debuginfo {
QString qsVersion();
QString qtVersion();
QString gpuInfo();
QString systemInfo();
QString envInfo();
QString combinedInfo();
} // namespace qs::debuginfo
+15 -34
View File
@@ -107,10 +107,7 @@ ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString&
auto groupName = QString();
auto entries = QHash<QString, QPair<Locale, QString>>();
auto actionOrder = QStringList();
auto pendingActions = QHash<QString, DesktopActionData>();
auto finishCategory = [&data, &groupName, &entries, &actionOrder, &pendingActions]() {
auto finishCategory = [&data, &groupName, &entries]() {
if (groupName == "Desktop Entry") {
if (entries.value("Type").second != "Application") return;
@@ -132,10 +129,9 @@ ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString&
else if (key == "Terminal") data.terminal = value == "true";
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 == "Actions") actionOrder = value.split(u';', Qt::SkipEmptyParts);
}
} else if (groupName.startsWith("Desktop Action ")) {
auto actionName = groupName.sliced(15);
auto actionName = groupName.sliced(16);
DesktopActionData action;
action.id = actionName;
@@ -151,7 +147,7 @@ ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString&
}
}
pendingActions.insert(actionName, action);
data.actions.insert(actionName, action);
}
entries.clear();
@@ -197,13 +193,6 @@ ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString&
}
finishCategory();
for (const auto& actionId: actionOrder) {
if (pendingActions.contains(actionId)) {
data.actions.append(pendingActions.value(actionId));
}
}
return data;
}
@@ -227,18 +216,17 @@ void DesktopEntry::updateState(const ParsedDesktopEntryData& newState) {
this->updateActions(newState.actions);
}
void DesktopEntry::updateActions(const QVector<DesktopActionData>& newActions) {
void DesktopEntry::updateActions(const QHash<QString, DesktopActionData>& newActions) {
auto old = this->mActions;
this->mActions.clear();
for (const auto& d: newActions) {
for (const auto& [key, d]: newActions.asKeyValueRange()) {
DesktopAction* act = nullptr;
auto found = std::ranges::find(old, d.id, &DesktopAction::mId);
if (found != old.end()) {
act = *found;
if (auto found = old.find(key); found != old.end()) {
act = found.value();
old.erase(found);
} else {
act = new DesktopAction(d.id, this);
this->mActions.insert(key, act);
}
Qt::beginPropertyUpdateGroup();
@@ -249,7 +237,6 @@ void DesktopEntry::updateActions(const QVector<DesktopActionData>& newActions) {
Qt::endPropertyUpdateGroup();
act->mEntries = d.entries;
this->mActions.append(act);
}
for (auto* leftover: old) {
@@ -263,7 +250,7 @@ void DesktopEntry::execute() const {
bool DesktopEntry::isValid() const { return !this->bName.value().isEmpty(); }
QVector<DesktopAction*> DesktopEntry::actions() const { return this->mActions; }
QVector<DesktopAction*> DesktopEntry::actions() const { return this->mActions.values(); }
QVector<QString> DesktopEntry::parseExecString(const QString& execString) {
QVector<QString> arguments;
@@ -282,22 +269,16 @@ QVector<QString> DesktopEntry::parseExecString(const QString& execString) {
currentArgument += '\\';
escape = 0;
}
} else if (escape == 2) {
currentArgument += c;
escape = 0;
} else if (escape != 0) {
switch (c.unicode()) {
case 's': currentArgument += u' '; break;
case 'n': currentArgument += u'\n'; break;
case 't': currentArgument += u'\t'; break;
case 'r': currentArgument += u'\r'; break;
case '\\': currentArgument += u'\\'; break;
default:
if (escape != 2) {
// Technically this is an illegal state, but the spec has a terrible double escape
// rule in strings for no discernable reason. Assuming someone might understandably
// misunderstand it, treat it as a normal escape and log it.
qCWarning(logDesktopEntry).noquote()
<< "Illegal escape sequence in desktop entry exec string:" << execString;
currentArgument += c;
break;
}
currentArgument += c;
escape = 0;
} else if (c == u'"' || c == u'\'') {
parsingString = false;
+3 -3
View File
@@ -43,7 +43,7 @@ struct ParsedDesktopEntryData {
QVector<QString> categories;
QVector<QString> keywords;
QHash<QString, QString> entries;
QVector<DesktopActionData> actions;
QHash<QString, DesktopActionData> actions;
};
/// A desktop entry. See @@DesktopEntries for details.
@@ -164,10 +164,10 @@ public:
// clang-format on
private:
void updateActions(const QVector<DesktopActionData>& newActions);
void updateActions(const QHash<QString, DesktopActionData>& newActions);
ParsedDesktopEntryData state;
QVector<DesktopAction*> mActions;
QHash<QString, DesktopAction*> mActions;
friend class DesktopAction;
};
+19 -21
View File
@@ -49,8 +49,7 @@ EngineGeneration::EngineGeneration(const QDir& rootPath, QmlScanner scanner)
this->engine->addImportPath("qs:@/");
this->engine->setNetworkAccessManagerFactory(&this->interceptNetFactory);
this->incubationController.initLoop();
this->engine->setIncubationController(&this->incubationController);
this->engine->setIncubationController(&this->delayedIncubationController);
this->engine->addImageProvider("icon", new IconImageProvider());
this->engine->addImageProvider("qsimage", new QsImageProvider());
@@ -135,7 +134,7 @@ void EngineGeneration::onReload(EngineGeneration* old) {
// new generation acquires it then incubators will hang intermittently
qCDebug(logIncubator) << "Locking incubation controllers of old generation" << old;
old->incubationControllersLocked = true;
old->updateIncubationMode();
old->assignIncubationController();
}
QObject::connect(this->engine, &QQmlEngine::quit, this, &EngineGeneration::quit);
@@ -209,8 +208,6 @@ bool EngineGeneration::setExtraWatchedFiles(const QVector<QString>& files) {
for (const auto& file: files) {
if (!this->scanner.scannedFiles.contains(file)) {
this->extraWatchedFiles.append(file);
QByteArray data;
this->scanner.readAndHashFile(file, data);
}
}
@@ -231,11 +228,6 @@ void EngineGeneration::onFileChanged(const QString& name) {
auto fileInfo = QFileInfo(name);
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();
}
}
@@ -244,11 +236,6 @@ void EngineGeneration::onDirectoryChanged() {
// try to find any files that were just deleted from a replace operation
for (auto& file: this->deletedWatchedFiles) {
if (QFileInfo(file).exists()) {
if (!this->scanner.hasFileContentChanged(file)) {
qCDebug(logQmlScanner) << "Ignoring restored file with unchanged content:" << file;
continue;
}
emit this->filesChanged();
break;
}
@@ -301,18 +288,29 @@ void EngineGeneration::trackWindowIncubationController(QQuickWindow* window) {
QObject::connect(window, &QObject::destroyed, this, &EngineGeneration::onTrackedWindowDestroyed);
this->trackedWindows.append(window);
this->updateIncubationMode();
this->assignIncubationController();
}
void EngineGeneration::onTrackedWindowDestroyed(QObject* object) {
this->trackedWindows.removeAll(static_cast<QQuickWindow*>(object)); // NOLINT
this->updateIncubationMode();
this->assignIncubationController();
}
void EngineGeneration::updateIncubationMode() {
// If we're in a situation with only hidden but tracked windows this might be wrong,
// but it seems to at least work.
this->incubationController.setIncubationMode(!this->trackedWindows.empty());
void EngineGeneration::assignIncubationController() {
QQmlIncubationController* controller = &this->delayedIncubationController;
for (auto* window: this->trackedWindows) {
if (auto* wctl = window->incubationController()) {
controller = wctl;
break;
}
}
qCDebug(logIncubator) << "Assigning incubation controller" << controller << "to generation"
<< this
<< "fallback:" << (controller == &this->delayedIncubationController);
this->engine->setIncubationController(controller);
}
EngineGeneration* EngineGeneration::currentGeneration() {
+2 -2
View File
@@ -65,7 +65,7 @@ public:
QFileSystemWatcher* watcher = nullptr;
QVector<QString> deletedWatchedFiles;
QVector<QString> extraWatchedFiles;
QsIncubationController incubationController;
DelayedQmlIncubationController delayedIncubationController;
bool reloadComplete = false;
QuickshellGlobal* qsgInstance = nullptr;
@@ -89,7 +89,7 @@ private slots:
private:
void postReload();
void updateIncubationMode();
void assignIncubationController();
QVector<QQuickWindow*> trackedWindows;
bool incubationControllersLocked = false;
QHash<const void*, EngineGenerationExt*> extensions;
+3 -3
View File
@@ -19,7 +19,8 @@ IconImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& re
if (splitIdx != -1) {
iconName = id.sliced(0, splitIdx);
path = id.sliced(splitIdx + 6);
path = QString("/%1/%2").arg(path, iconName.sliced(iconName.lastIndexOf('/') + 1));
qWarning() << "Searching custom icon paths is not yet supported. Icon path will be ignored for"
<< id;
} else {
splitIdx = id.indexOf("?fallback=");
if (splitIdx != -1) {
@@ -31,8 +32,7 @@ IconImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& re
}
auto icon = QIcon::fromTheme(iconName);
if (icon.isNull() && !fallbackName.isEmpty()) icon = QIcon::fromTheme(fallbackName);
if (icon.isNull() && !path.isEmpty()) icon = QPixmap(path);
if (icon.isNull()) icon = QIcon::fromTheme(fallbackName);
auto targetSize = requestedSize.isValid() ? requestedSize : QSize(100, 100);
if (targetSize.width() == 0 || targetSize.height() == 0) targetSize = QSize(2, 2);
+2 -2
View File
@@ -22,8 +22,8 @@ class PixmapCacheIconEngine: public QIconEngine {
QIcon::Mode /*unused*/,
QIcon::State /*unused*/
) override {
qFatal()
<< "Unexpected icon paint request bypassed pixmap method. Please report this as a bug.";
qFatal(
) << "Unexpected icon paint request bypassed pixmap method. Please report this as a bug.";
}
QPixmap pixmap(const QSize& size, QIcon::Mode /*unused*/, QIcon::State /*unused*/) override {
-118
View File
@@ -1,16 +1,7 @@
#include "incubator.hpp"
#include <private/qsgrenderloop_p.h>
#include <qabstractanimation.h>
#include <qguiapplication.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qminmax.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qobjectdefs.h>
#include <qqmlincubator.h>
#include <qscreen.h>
#include <qtmetamacros.h>
#include "logcat.hpp"
@@ -24,112 +15,3 @@ void QsQmlIncubator::statusChanged(QQmlIncubator::Status status) {
default: break;
}
}
void QsIncubationController::initLoop() {
auto* app = static_cast<QGuiApplication*>(QGuiApplication::instance()); // NOLINT
this->renderLoop = QSGRenderLoop::instance();
QObject::connect(
app,
&QGuiApplication::screenAdded,
this,
&QsIncubationController::updateIncubationTime
);
QObject::connect(
app,
&QGuiApplication::screenRemoved,
this,
&QsIncubationController::updateIncubationTime
);
this->updateIncubationTime();
QObject::connect(
this->renderLoop,
&QSGRenderLoop::timeToIncubate,
this,
&QsIncubationController::incubate
);
QAnimationDriver* animationDriver = this->renderLoop->animationDriver();
if (animationDriver) {
QObject::connect(
animationDriver,
&QAnimationDriver::stopped,
this,
&QsIncubationController::animationStopped
);
} else {
qCInfo(logIncubator) << "Render loop does not have animation driver, animationStopped cannot "
"be used to trigger incubation.";
}
}
void QsIncubationController::setIncubationMode(bool render) {
if (render == this->followRenderloop) return;
this->followRenderloop = render;
if (render) {
qCDebug(logIncubator) << "Incubation mode changed: render loop driven";
} else {
qCDebug(logIncubator) << "Incubation mode changed: event loop driven";
}
if (!render && this->incubatingObjectCount()) this->incubateLater();
}
void QsIncubationController::timerEvent(QTimerEvent* /*event*/) {
this->killTimer(this->timerId);
this->timerId = 0;
this->incubate();
}
void QsIncubationController::incubateLater() {
if (this->followRenderloop) {
if (this->timerId != 0) {
this->killTimer(this->timerId);
this->timerId = 0;
}
// Incubate again at the end of the event processing queue
QMetaObject::invokeMethod(this, &QsIncubationController::incubate, Qt::QueuedConnection);
} else if (this->timerId == 0) {
// Wait for a while before processing the next batch. Using a
// timer to avoid starvation of system events.
this->timerId = this->startTimer(this->incubationTime);
}
}
void QsIncubationController::incubate() {
if ((!this->followRenderloop || this->renderLoop) && this->incubatingObjectCount()) {
if (!this->followRenderloop) {
this->incubateFor(10);
if (this->incubatingObjectCount()) this->incubateLater();
} else if (this->renderLoop->interleaveIncubation()) {
this->incubateFor(this->incubationTime);
} else {
this->incubateFor(this->incubationTime * 2);
if (this->incubatingObjectCount()) this->incubateLater();
}
}
}
void QsIncubationController::animationStopped() { this->incubate(); }
void QsIncubationController::incubatingObjectCountChanged(int count) {
if (count
&& (!this->followRenderloop
|| (this->renderLoop && !this->renderLoop->interleaveIncubation())))
{
this->incubateLater();
}
}
void QsIncubationController::updateIncubationTime() {
auto* screen = QGuiApplication::primaryScreen();
if (!screen) return;
// 1/3 frame on primary screen
this->incubationTime = qMax(1, static_cast<int>(1000 / screen->refreshRate() / 3));
}
+3 -34
View File
@@ -1,7 +1,6 @@
#pragma once
#include <qobject.h>
#include <qpointer.h>
#include <qqmlincubator.h>
#include <qtmetamacros.h>
@@ -26,37 +25,7 @@ signals:
void failed();
};
class QSGRenderLoop;
class QsIncubationController
: public QObject
, public QQmlIncubationController {
Q_OBJECT
public:
void initLoop();
void setIncubationMode(bool render);
void incubateLater();
protected:
void timerEvent(QTimerEvent* event) override;
public slots:
void incubate();
void animationStopped();
void updateIncubationTime();
protected:
void incubatingObjectCountChanged(int count) override;
private:
// QPointer did not work with forward declarations prior to 6.7
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
QPointer<QSGRenderLoop> renderLoop = nullptr;
#else
QSGRenderLoop* renderLoop = nullptr;
#endif
int incubationTime = 0;
int timerId = 0;
bool followRenderloop = false;
class DelayedQmlIncubationController: public QQmlIncubationController {
// Do nothing.
// This ensures lazy loaders don't start blocking before onReload creates windows.
};
+2 -4
View File
@@ -3,14 +3,12 @@
#include <qdatastream.h>
QDataStream& operator<<(QDataStream& stream, const InstanceInfo& info) {
stream << info.instanceId << info.configPath << info.shellId << info.launchTime << info.pid
<< info.display;
stream << info.instanceId << info.configPath << info.shellId << info.launchTime << info.pid;
return stream;
}
QDataStream& operator>>(QDataStream& stream, InstanceInfo& info) {
stream >> info.instanceId >> info.configPath >> info.shellId >> info.launchTime >> info.pid
>> info.display;
stream >> info.instanceId >> info.configPath >> info.shellId >> info.launchTime >> info.pid;
return stream;
}
-3
View File
@@ -11,7 +11,6 @@ struct InstanceInfo {
QString shellId;
QDateTime launchTime;
pid_t pid = -1;
QString display;
static InstanceInfo CURRENT; // NOLINT
};
@@ -35,8 +34,6 @@ namespace qs::crash {
struct CrashInfo {
int logFd = -1;
int traceFd = -1;
int infoFd = -1;
static CrashInfo INSTANCE; // NOLINT
};
+3
View File
@@ -82,6 +82,9 @@
/// > Notably, @@Variants does not corrently support asynchronous
/// > loading, meaning using it inside a LazyLoader will block similarly to not
/// > having a loader to start with.
///
/// > [!WARNING] LazyLoaders do not start loading before the first window is created,
/// > meaning if you create all windows inside of lazy loaders, none of them will ever load.
class LazyLoader: public Reloadable {
Q_OBJECT;
/// The fully loaded item if the loader is @@loading or @@active, or `null`
+9 -73
View File
@@ -27,10 +27,7 @@
#include <qtmetamacros.h>
#include <qtypes.h>
#include <sys/mman.h>
#ifdef __linux__
#include <sys/sendfile.h>
#include <sys/types.h>
#endif
#include "instanceinfo.hpp"
#include "logcat.hpp"
@@ -46,57 +43,6 @@ using namespace qt_logging_registry;
QS_LOGGING_CATEGORY(logLogging, "quickshell.logging", QtWarningMsg);
namespace {
bool copyFileData(int sourceFd, int destFd, qint64 size) {
auto usize = static_cast<size_t>(size);
#ifdef __linux__
off_t offset = 0;
auto remaining = usize;
while (remaining > 0) {
auto r = sendfile(destFd, sourceFd, &offset, remaining);
if (r == -1) {
if (errno == EINTR) continue;
return false;
}
if (r == 0) break;
remaining -= static_cast<size_t>(r);
}
return true;
#else
std::array<char, 64 * 1024> buffer = {};
auto remaining = totalTarget;
while (remaining > 0) {
auto chunk = std::min(remaining, buffer.size());
auto r = ::read(sourceFd, buffer.data(), chunk);
if (r == -1) {
if (errno == EINTR) continue;
return false;
}
if (r == 0) break;
auto readBytes = static_cast<size_t>(r);
size_t written = 0;
while (written < readBytes) {
auto w = ::write(destFd, buffer.data() + written, readBytes - written);
if (w == -1) {
if (errno == EINTR) continue;
return false;
}
written += static_cast<size_t>(w);
}
remaining -= readBytes;
}
return true;
#endif
}
} // namespace
bool LogMessage::operator==(const LogMessage& other) const {
// note: not including time
return this->type == other.type && this->category == other.category && this->body == other.body;
@@ -415,8 +361,7 @@ void ThreadLogging::initFs() {
auto* runDir = QsPaths::instance()->instanceRunDir();
if (!runDir) {
qCCritical(
logLogging
qCCritical(logLogging
) << "Could not start filesystem logging as the runtime directory could not be created.";
return;
}
@@ -427,8 +372,7 @@ void ThreadLogging::initFs() {
auto* detailedFile = new QFile(detailedPath);
if (!file->open(QFile::ReadWrite | QFile::Truncate)) {
qCCritical(
logLogging
qCCritical(logLogging
) << "Could not start filesystem logger as the log file could not be created:"
<< path;
delete file;
@@ -439,14 +383,13 @@ void ThreadLogging::initFs() {
// buffered by WriteBuffer
if (!detailedFile->open(QFile::ReadWrite | QFile::Truncate | QFile::Unbuffered)) {
qCCritical(
logLogging
qCCritical(logLogging
) << "Could not start detailed filesystem logger as the log file could not be created:"
<< detailedPath;
delete detailedFile;
detailedFile = nullptr;
} else {
struct flock lock = {
auto lock = flock {
.l_type = F_WRLCK,
.l_whence = SEEK_SET,
.l_start = 0,
@@ -468,11 +411,7 @@ void ThreadLogging::initFs() {
auto* oldFile = this->file;
if (oldFile) {
oldFile->seek(0);
if (!copyFileData(oldFile->handle(), file->handle(), oldFile->size())) {
qCritical(logLogging) << "Failed to copy log from memfd with error code " << errno
<< qt_error_string(errno);
}
sendfile(file->handle(), oldFile->handle(), nullptr, oldFile->size());
}
this->file = file;
@@ -484,10 +423,7 @@ void ThreadLogging::initFs() {
auto* oldFile = this->detailedFile;
if (oldFile) {
oldFile->seek(0);
if (!copyFileData(oldFile->handle(), detailedFile->handle(), oldFile->size())) {
qCritical(logLogging) << "Failed to copy detailed log from memfd with error code " << errno
<< qt_error_string(errno);
}
sendfile(detailedFile->handle(), oldFile->handle(), nullptr, oldFile->size());
}
crash::CrashInfo::INSTANCE.logFd = detailedFile->handle();
@@ -810,11 +746,11 @@ bool EncodedLogReader::readVarInt(quint32* slot) {
if (!this->reader.skip(1)) return false;
*slot = qFromLittleEndian(n);
} else if ((bytes[1] != 0xff || bytes[2] != 0xff) && readLength >= 3) {
auto n = *reinterpret_cast<quint16*>(bytes.data() + 1); // NOLINT
auto n = *reinterpret_cast<quint16*>(bytes.data() + 1);
if (!this->reader.skip(3)) return false;
*slot = qFromLittleEndian(n);
} else if (readLength == 7) {
auto n = *reinterpret_cast<quint32*>(bytes.data() + 3); // NOLINT
auto n = *reinterpret_cast<quint32*>(bytes.data() + 3);
if (!this->reader.skip(7)) return false;
*slot = qFromLittleEndian(n);
} else return false;
@@ -950,7 +886,7 @@ bool LogReader::continueReading() {
}
void LogFollower::FcntlWaitThread::run() {
struct flock lock = {
auto lock = flock {
.l_type = F_RDLCK, // won't block other read locks when we take it
.l_whence = SEEK_SET,
.l_start = 0,
+69 -2
View File
@@ -1,14 +1,81 @@
#include "model.hpp"
#include <qbytearray.h>
#include <qabstractitemmodel.h>
#include <qhash.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qqmllist.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include <qvariant.h>
qint32 UntypedObjectModel::rowCount(const QModelIndex& parent) const {
if (parent != QModelIndex()) return 0;
return static_cast<qint32>(this->valuesList.length());
}
QVariant UntypedObjectModel::data(const QModelIndex& index, qint32 role) const {
if (role != Qt::UserRole) return QVariant();
return QVariant::fromValue(this->valuesList.at(index.row()));
}
QHash<int, QByteArray> UntypedObjectModel::roleNames() const {
return {{Qt::UserRole, "modelData"}};
}
void UntypedObjectModel::insertObject(QObject* object, qsizetype index) {
auto iindex = index == -1 ? this->valuesList.length() : index;
emit this->objectInsertedPre(object, iindex);
auto intIndex = static_cast<qint32>(iindex);
this->beginInsertRows(QModelIndex(), intIndex, intIndex);
this->valuesList.insert(iindex, object);
this->endInsertRows();
emit this->valuesChanged();
emit this->objectInsertedPost(object, iindex);
}
void UntypedObjectModel::removeAt(qsizetype index) {
auto* object = this->valuesList.at(index);
emit this->objectRemovedPre(object, index);
auto intIndex = static_cast<qint32>(index);
this->beginRemoveRows(QModelIndex(), intIndex, intIndex);
this->valuesList.removeAt(index);
this->endRemoveRows();
emit this->valuesChanged();
emit this->objectRemovedPost(object, index);
}
bool UntypedObjectModel::removeObject(const QObject* object) {
auto index = this->valuesList.indexOf(object);
if (index == -1) return false;
this->removeAt(index);
return true;
}
void UntypedObjectModel::diffUpdate(const QVector<QObject*>& newValues) {
for (qsizetype i = 0; i < this->valuesList.length();) {
if (newValues.contains(this->valuesList.at(i))) i++;
else this->removeAt(i);
}
qsizetype oi = 0;
for (auto* object: newValues) {
if (this->valuesList.length() == oi || this->valuesList.at(oi) != object) {
this->insertObject(object, oi);
}
oi++;
}
}
qsizetype UntypedObjectModel::indexOf(QObject* object) { return this->valuesList.indexOf(object); }
UntypedObjectModel* UntypedObjectModel::emptyInstance() {
static auto* instance = new ObjectModel<void>(nullptr);
static auto* instance = new UntypedObjectModel(nullptr); // NOLINT
return instance;
}
+25 -73
View File
@@ -2,7 +2,7 @@
#include <functional>
#include <QtCore/qtmetamacros.h>
#include <bit>
#include <qabstractitemmodel.h>
#include <qcontainerfwd.h>
#include <qobject.h>
@@ -49,11 +49,14 @@ class UntypedObjectModel: public QAbstractListModel {
public:
explicit UntypedObjectModel(QObject* parent): QAbstractListModel(parent) {}
[[nodiscard]] qint32 rowCount(const QModelIndex& parent) const override;
[[nodiscard]] QVariant data(const QModelIndex& index, qint32 role) const override;
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
[[nodiscard]] virtual QList<QObject*> values() = 0;
[[nodiscard]] QList<QObject*> values() const { return this->valuesList; }
void removeAt(qsizetype index);
Q_INVOKABLE virtual qsizetype indexOf(QObject* object) const = 0;
Q_INVOKABLE qsizetype indexOf(QObject* object);
static UntypedObjectModel* emptyInstance();
@@ -68,6 +71,15 @@ signals:
/// Sent immediately after an object is removed from the list.
void objectRemovedPost(QObject* object, qsizetype index);
protected:
void insertObject(QObject* object, qsizetype index = -1);
bool removeObject(const QObject* object);
// Assumes only one instance of a specific value
void diffUpdate(const QVector<QObject*>& newValues);
QVector<QObject*> valuesList;
private:
static qsizetype valuesCount(QQmlListProperty<QObject>* property);
static QObject* valueAt(QQmlListProperty<QObject>* property, qsizetype index);
@@ -78,20 +90,14 @@ class ObjectModel: public UntypedObjectModel {
public:
explicit ObjectModel(QObject* parent): UntypedObjectModel(parent) {}
[[nodiscard]] const QList<T*>& valueList() const { return this->mValuesList; }
[[nodiscard]] QList<T*>& valueList() { return this->mValuesList; }
[[nodiscard]] QVector<T*>& valueList() { return *std::bit_cast<QVector<T*>*>(&this->valuesList); }
[[nodiscard]] const QVector<T*>& valueList() const {
return *std::bit_cast<const QVector<T*>*>(&this->valuesList);
}
void insertObject(T* object, qsizetype index = -1) {
auto iindex = index == -1 ? this->mValuesList.length() : index;
emit this->objectInsertedPre(object, iindex);
auto intIndex = static_cast<qint32>(iindex);
this->beginInsertRows(QModelIndex(), intIndex, intIndex);
this->mValuesList.insert(iindex, object);
this->endInsertRows();
emit this->valuesChanged();
emit this->objectInsertedPost(object, iindex);
this->UntypedObjectModel::insertObject(object, index);
}
void insertObjectSorted(T* object, const std::function<bool(T*, T*)>& compare) {
@@ -104,71 +110,17 @@ public:
}
auto idx = iter - list.begin();
this->insertObject(object, idx);
this->UntypedObjectModel::insertObject(object, idx);
}
bool removeObject(const T* object) {
auto index = this->mValuesList.indexOf(object);
if (index == -1) return false;
this->removeAt(index);
return true;
}
void removeAt(qsizetype index) {
auto* object = this->mValuesList.at(index);
emit this->objectRemovedPre(object, index);
auto intIndex = static_cast<qint32>(index);
this->beginRemoveRows(QModelIndex(), intIndex, intIndex);
this->mValuesList.removeAt(index);
this->endRemoveRows();
emit this->valuesChanged();
emit this->objectRemovedPost(object, index);
}
void removeObject(const T* object) { this->UntypedObjectModel::removeObject(object); }
// Assumes only one instance of a specific value
void diffUpdate(const QList<T*>& newValues) {
for (qsizetype i = 0; i < this->mValuesList.length();) {
if (newValues.contains(this->mValuesList.at(i))) i++;
else this->removeAt(i);
}
qsizetype oi = 0;
for (auto* object: newValues) {
if (this->mValuesList.length() == oi || this->mValuesList.at(oi) != object) {
this->insertObject(object, oi);
}
oi++;
}
void diffUpdate(const QVector<T*>& newValues) {
this->UntypedObjectModel::diffUpdate(*std::bit_cast<const QVector<QObject*>*>(&newValues));
}
static ObjectModel<T>* emptyInstance() {
return static_cast<ObjectModel<T>*>(UntypedObjectModel::emptyInstance());
}
[[nodiscard]] qint32 rowCount(const QModelIndex& parent) const override {
if (parent != QModelIndex()) return 0;
return static_cast<qint32>(this->mValuesList.length());
}
[[nodiscard]] QVariant data(const QModelIndex& index, qint32 role) const override {
if (role != Qt::UserRole) return QVariant();
// Values must be QObject derived, but we can't assert that here without breaking forward decls,
// so no static_cast.
return QVariant::fromValue(reinterpret_cast<QObject*>(this->mValuesList.at(index.row())));
}
qsizetype indexOf(QObject* object) const override {
return this->mValuesList.indexOf(reinterpret_cast<T*>(object));
}
[[nodiscard]] QList<QObject*> values() override {
return *reinterpret_cast<QList<QObject*>*>(&this->mValuesList);
}
private:
QList<T*> mValuesList;
};
+1
View File
@@ -21,6 +21,7 @@ headers = [
"model.hpp",
"elapsedtimer.hpp",
"desktopentry.hpp",
"objectrepeater.hpp",
"qsmenu.hpp",
"retainable.hpp",
"popupanchor.hpp",
+190
View File
@@ -0,0 +1,190 @@
#include "objectrepeater.hpp"
#include <utility>
#include <qabstractitemmodel.h>
#include <qcontainerfwd.h>
#include <qhash.h>
#include <qlogging.h>
#include <qobject.h>
#include <qqmlcomponent.h>
#include <qqmlcontext.h>
#include <qqmlengine.h>
#include <qqmllist.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include <qvariant.h>
QVariant ObjectRepeater::model() const { return this->mModel; }
void ObjectRepeater::setModel(QVariant model) {
if (model == this->mModel) return;
if (this->itemModel != nullptr) {
QObject::disconnect(this->itemModel, nullptr, this, nullptr);
}
this->mModel = std::move(model);
emit this->modelChanged();
this->reloadElements();
}
void ObjectRepeater::onModelDestroyed() {
this->mModel.clear();
this->itemModel = nullptr;
emit this->modelChanged();
this->reloadElements();
}
QQmlComponent* ObjectRepeater::delegate() const { return this->mDelegate; }
void ObjectRepeater::setDelegate(QQmlComponent* delegate) {
if (delegate == this->mDelegate) return;
if (this->mDelegate != nullptr) {
QObject::disconnect(this->mDelegate, nullptr, this, nullptr);
}
this->mDelegate = delegate;
if (delegate != nullptr) {
QObject::connect(
this->mDelegate,
&QObject::destroyed,
this,
&ObjectRepeater::onDelegateDestroyed
);
}
emit this->delegateChanged();
this->reloadElements();
}
void ObjectRepeater::onDelegateDestroyed() {
this->mDelegate = nullptr;
emit this->delegateChanged();
this->reloadElements();
}
void ObjectRepeater::reloadElements() {
for (auto i = this->valuesList.length() - 1; i >= 0; i--) {
this->removeComponent(i);
}
if (this->mDelegate == nullptr || !this->mModel.isValid()) return;
if (this->mModel.canConvert<QAbstractItemModel*>()) {
auto* model = this->mModel.value<QAbstractItemModel*>();
this->itemModel = model;
this->insertModelElements(model, 0, model->rowCount() - 1); // -1 is fine
// clang-format off
QObject::connect(model, &QObject::destroyed, this, &ObjectRepeater::onModelDestroyed);
QObject::connect(model, &QAbstractItemModel::rowsInserted, this, &ObjectRepeater::onModelRowsInserted);
QObject::connect(model, &QAbstractItemModel::rowsRemoved, this, &ObjectRepeater::onModelRowsRemoved);
QObject::connect(model, &QAbstractItemModel::rowsMoved, this, &ObjectRepeater::onModelRowsMoved);
QObject::connect(model, &QAbstractItemModel::modelAboutToBeReset, this, &ObjectRepeater::onModelAboutToBeReset);
// clang-format on
} else if (this->mModel.canConvert<QQmlListReference>()) {
auto values = this->mModel.value<QQmlListReference>();
auto len = values.count();
for (auto i = 0; i != len; i++) {
this->insertComponent(i, {{"modelData", QVariant::fromValue(values.at(i))}});
}
} else if (this->mModel.canConvert<QVector<QVariant>>()) {
auto values = this->mModel.value<QVector<QVariant>>();
for (auto& value: values) {
this->insertComponent(this->valuesList.length(), {{"modelData", value}});
}
} else {
qCritical() << this
<< "Cannot create components as the model is not compatible:" << this->mModel;
}
}
void ObjectRepeater::insertModelElements(QAbstractItemModel* model, int first, int last) {
auto roles = model->roleNames();
auto roleDataVec = QVector<QModelRoleData>();
for (auto id: roles.keys()) {
roleDataVec.push_back(QModelRoleData(id));
}
auto values = QModelRoleDataSpan(roleDataVec);
auto props = QVariantMap();
for (auto i = first; i != last + 1; i++) {
auto index = model->index(i, 0);
model->multiData(index, values);
for (auto [id, name]: roles.asKeyValueRange()) {
props.insert(name, *values.dataForRole(id));
}
this->insertComponent(i, props);
props.clear();
}
}
void ObjectRepeater::onModelRowsInserted(const QModelIndex& parent, int first, int last) {
if (parent != QModelIndex()) return;
this->insertModelElements(this->itemModel, first, last);
}
void ObjectRepeater::onModelRowsRemoved(const QModelIndex& parent, int first, int last) {
if (parent != QModelIndex()) return;
for (auto i = last; i != first - 1; i--) {
this->removeComponent(i);
}
}
void ObjectRepeater::onModelRowsMoved(
const QModelIndex& sourceParent,
int sourceStart,
int sourceEnd,
const QModelIndex& destParent,
int destStart
) {
auto hasSource = sourceParent != QModelIndex();
auto hasDest = destParent != QModelIndex();
if (!hasSource && !hasDest) return;
if (hasSource) {
this->onModelRowsRemoved(sourceParent, sourceStart, sourceEnd);
}
if (hasDest) {
this->onModelRowsInserted(destParent, destStart, destStart + (sourceEnd - sourceStart));
}
}
void ObjectRepeater::onModelAboutToBeReset() {
auto last = static_cast<int>(this->valuesList.length() - 1);
this->onModelRowsRemoved(QModelIndex(), 0, last); // -1 is fine
}
void ObjectRepeater::insertComponent(qsizetype index, const QVariantMap& properties) {
auto* context = QQmlEngine::contextForObject(this);
auto* instance = this->mDelegate->createWithInitialProperties(properties, context);
if (instance == nullptr) {
qWarning().noquote() << this->mDelegate->errorString();
qWarning() << this << "failed to create object for model data" << properties;
} else {
QQmlEngine::setObjectOwnership(instance, QQmlEngine::CppOwnership);
instance->setParent(this);
}
this->insertObject(instance, index);
}
void ObjectRepeater::removeComponent(qsizetype index) {
auto* instance = this->valuesList.at(index);
this->removeAt(index);
delete instance;
}
+85
View File
@@ -0,0 +1,85 @@
#pragma once
#include <qabstractitemmodel.h>
#include <qobject.h>
#include <qqmlcomponent.h>
#include <qqmlintegration.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include <qvariant.h>
#include "model.hpp"
///! A Repeater / for loop / map for non Item derived objects.
/// > [!ERROR] Removed in favor of @@QtQml.Models.Instantiator
///
/// The ObjectRepeater creates instances of the provided delegate for every entry in the
/// given model, similarly to a @@QtQuick.Repeater but for non visual types.
class ObjectRepeater: public ObjectModel<QObject> {
Q_OBJECT;
/// The model providing data to the ObjectRepeater.
///
/// Currently accepted model types are `list<T>` lists, javascript arrays,
/// and [QAbstractListModel] derived models, though only one column will be repeated
/// from the latter.
///
/// Note: @@ObjectModel is a [QAbstractListModel] with a single column.
///
/// [QAbstractListModel]: https://doc.qt.io/qt-6/qabstractlistmodel.html
Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged);
/// The delegate component to repeat.
///
/// The delegate is given the same properties as in a Repeater, except `index` which
/// is not currently implemented.
///
/// If the model is a `list<T>` or javascript array, a `modelData` property will be
/// exposed containing the entry from the model. If the model is a [QAbstractListModel],
/// the roles from the model will be exposed.
///
/// Note: @@ObjectModel has a single role named `modelData` for compatibility with normal lists.
///
/// [QAbstractListModel]: https://doc.qt.io/qt-6/qabstractlistmodel.html
Q_PROPERTY(QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY delegateChanged);
Q_CLASSINFO("DefaultProperty", "delegate");
QML_ELEMENT;
QML_UNCREATABLE("ObjectRepeater has been removed in favor of QtQml.Models.Instantiator.");
public:
explicit ObjectRepeater(QObject* parent = nullptr): ObjectModel(parent) {}
[[nodiscard]] QVariant model() const;
void setModel(QVariant model);
[[nodiscard]] QQmlComponent* delegate() const;
void setDelegate(QQmlComponent* delegate);
signals:
void modelChanged();
void delegateChanged();
private slots:
void onDelegateDestroyed();
void onModelDestroyed();
void onModelRowsInserted(const QModelIndex& parent, int first, int last);
void onModelRowsRemoved(const QModelIndex& parent, int first, int last);
void onModelRowsMoved(
const QModelIndex& sourceParent,
int sourceStart,
int sourceEnd,
const QModelIndex& destParent,
int destStart
);
void onModelAboutToBeReset();
private:
void reloadElements();
void insertModelElements(QAbstractItemModel* model, int first, int last);
void insertComponent(qsizetype index, const QVariantMap& properties);
void removeComponent(qsizetype index);
QVariant mModel;
QAbstractItemModel* itemModel = nullptr;
QQmlComponent* mDelegate = nullptr;
};
+9 -30
View File
@@ -27,19 +27,12 @@ QsPaths* QsPaths::instance() {
return instance;
}
void QsPaths::init(
QString shellId,
QString pathId,
QString dataOverride,
QString stateOverride,
QString cacheOverride
) {
void QsPaths::init(QString shellId, QString pathId, QString dataOverride, QString stateOverride) {
auto* instance = QsPaths::instance();
instance->shellId = std::move(shellId);
instance->pathId = std::move(pathId);
instance->shellDataOverride = std::move(dataOverride);
instance->shellStateOverride = std::move(stateOverride);
instance->shellCacheOverride = std::move(cacheOverride);
}
QDir QsPaths::crashDir(const QString& id) {
@@ -175,8 +168,7 @@ void QsPaths::linkRunDir() {
auto* shellDir = this->shellRunDir();
if (!shellDir) {
qCCritical(
logPaths
qCCritical(logPaths
) << "Could not create by-id symlink as the shell runtime path could not be created.";
} else {
auto shellPath = shellDir->filePath(runDir->dirName());
@@ -324,16 +316,9 @@ QDir QsPaths::shellStateDir() {
QDir QsPaths::shellCacheDir() {
if (this->shellCacheState == DirState::Unknown) {
QDir dir;
if (this->shellCacheOverride.isEmpty()) {
dir = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
dir = QDir(dir.filePath("by-shell"));
dir = QDir(dir.filePath(this->shellId));
} else {
auto basedir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation);
dir = QDir(this->shellCacheOverride.replace("$BASE", basedir));
}
auto dir = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
dir = QDir(dir.filePath("by-shell"));
dir = QDir(dir.filePath(this->shellId));
this->mShellCacheDir = dir;
qCDebug(logPaths) << "Initialized cache path:" << dir.path();
@@ -361,7 +346,7 @@ void QsPaths::createLock() {
return;
}
struct flock lock = {
auto lock = flock {
.l_type = F_WRLCK,
.l_whence = SEEK_SET,
.l_start = 0,
@@ -379,8 +364,7 @@ void QsPaths::createLock() {
qCDebug(logPaths) << "Created instance lock at" << path;
}
} else {
qCCritical(
logPaths
qCCritical(logPaths
) << "Could not create instance lock, as the instance runtime directory could not be created.";
}
}
@@ -389,7 +373,7 @@ bool QsPaths::checkLock(const QString& path, InstanceLockInfo* info, bool allowD
auto file = QFile(QDir(path).filePath("instance.lock"));
if (!file.open(QFile::ReadOnly)) return false;
struct flock lock = {
auto lock = flock {
.l_type = F_WRLCK,
.l_whence = SEEK_SET,
.l_start = 0,
@@ -413,7 +397,7 @@ bool QsPaths::checkLock(const QString& path, InstanceLockInfo* info, bool allowD
}
QPair<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
QsPaths::collectInstances(const QString& path, const QString& display) {
QsPaths::collectInstances(const QString& path) {
qCDebug(logPaths) << "Collecting instances from" << path;
auto liveInstances = QVector<InstanceLockInfo>();
auto deadInstances = QVector<InstanceLockInfo>();
@@ -427,11 +411,6 @@ QsPaths::collectInstances(const QString& path, const QString& display) {
qCDebug(logPaths).nospace() << "Found instance " << info.instance.instanceId << " (pid "
<< info.pid << ") at " << path;
if (!display.isEmpty() && info.instance.display != display) {
qCDebug(logPaths) << "Skipped instance with mismatched display at" << path;
continue;
}
if (info.pid == -1) {
deadInstances.push_back(info);
} else {
+2 -9
View File
@@ -17,20 +17,14 @@ QDataStream& operator>>(QDataStream& stream, InstanceLockInfo& info);
class QsPaths {
public:
static QsPaths* instance();
static void init(
QString shellId,
QString pathId,
QString dataOverride,
QString stateOverride,
QString cacheOverride
);
static void init(QString shellId, QString pathId, QString dataOverride, QString stateOverride);
static QDir crashDir(const QString& id);
static QString basePath(const QString& id);
static QString ipcPath(const QString& id);
static bool
checkLock(const QString& path, InstanceLockInfo* info = nullptr, bool allowDead = false);
static QPair<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
collectInstances(const QString& path, const QString& display);
collectInstances(const QString& path);
QDir* baseRunDir();
QDir* shellRunDir();
@@ -71,5 +65,4 @@ private:
QString shellDataOverride;
QString shellStateOverride;
QString shellCacheOverride;
};
+9 -9
View File
@@ -28,7 +28,7 @@ void PopupAnchor::markClean() { this->lastState = this->state; }
void PopupAnchor::markDirty() { this->lastState.reset(); }
QWindow* PopupAnchor::backingWindow() const {
return this->bProxyWindow ? this->bProxyWindow->backingWindow() : nullptr;
return this->mProxyWindow ? this->mProxyWindow->backingWindow() : nullptr;
}
void PopupAnchor::setWindowInternal(QObject* window) {
@@ -36,14 +36,14 @@ void PopupAnchor::setWindowInternal(QObject* window) {
if (this->mWindow) {
QObject::disconnect(this->mWindow, nullptr, this, nullptr);
QObject::disconnect(this->bProxyWindow, nullptr, this, nullptr);
QObject::disconnect(this->mProxyWindow, nullptr, this, nullptr);
}
if (window) {
if (auto* proxy = qobject_cast<ProxyWindowBase*>(window)) {
this->bProxyWindow = proxy;
this->mProxyWindow = proxy;
} else if (auto* interface = qobject_cast<WindowInterface*>(window)) {
this->bProxyWindow = interface->proxyWindow();
this->mProxyWindow = interface->proxyWindow();
} else {
qWarning() << "Tried to set popup anchor window to" << window
<< "which is not a quickshell window.";
@@ -55,7 +55,7 @@ void PopupAnchor::setWindowInternal(QObject* window) {
QObject::connect(this->mWindow, &QObject::destroyed, this, &PopupAnchor::onWindowDestroyed);
QObject::connect(
this->bProxyWindow,
this->mProxyWindow,
&ProxyWindowBase::backerVisibilityChanged,
this,
&PopupAnchor::backingWindowVisibilityChanged
@@ -70,7 +70,7 @@ void PopupAnchor::setWindowInternal(QObject* window) {
setnull:
if (this->mWindow) {
this->mWindow = nullptr;
this->bProxyWindow = nullptr;
this->mProxyWindow = nullptr;
emit this->windowChanged();
emit this->backingWindowVisibilityChanged();
@@ -100,7 +100,7 @@ void PopupAnchor::setItem(QQuickItem* item) {
void PopupAnchor::onWindowDestroyed() {
this->mWindow = nullptr;
this->bProxyWindow = nullptr;
this->mProxyWindow = nullptr;
emit this->windowChanged();
emit this->backingWindowVisibilityChanged();
}
@@ -186,11 +186,11 @@ void PopupAnchor::updatePlacement(const QPoint& anchorpoint, const QSize& size)
}
void PopupAnchor::updateAnchor() {
if (this->mItem && this->bProxyWindow) {
if (this->mItem && this->mProxyWindow) {
auto baseRect =
this->mUserRect.isEmpty() ? this->mItem->boundingRect() : this->mUserRect.qrect();
auto rect = this->bProxyWindow->contentItem()->mapFromItem(
auto rect = this->mProxyWindow->contentItem()->mapFromItem(
this->mItem,
baseRect.marginsRemoved(this->mMargins.qmargins())
);
+2 -6
View File
@@ -6,7 +6,6 @@
#include <qnamespace.h>
#include <qobject.h>
#include <qpoint.h>
#include <qproperty.h>
#include <qqmlintegration.h>
#include <qquickitem.h>
#include <qsize.h>
@@ -140,9 +139,7 @@ public:
void markDirty();
[[nodiscard]] QObject* window() const { return this->mWindow; }
[[nodiscard]] QBindable<ProxyWindowBase*> bindableProxyWindow() const {
return &this->bProxyWindow;
}
[[nodiscard]] ProxyWindowBase* proxyWindow() const { return this->mProxyWindow; }
[[nodiscard]] QWindow* backingWindow() const;
void setWindowInternal(QObject* window);
void setWindow(QObject* window);
@@ -196,12 +193,11 @@ private slots:
private:
QObject* mWindow = nullptr;
QQuickItem* mItem = nullptr;
ProxyWindowBase* mProxyWindow = nullptr;
PopupAnchorState state;
Box mUserRect;
Margins mMargins;
std::optional<PopupAnchorState> lastState;
Q_OBJECT_BINDABLE_PROPERTY(PopupAnchor, ProxyWindowBase*, bProxyWindow);
};
class PopupPositioner {
+1 -14
View File
@@ -29,7 +29,6 @@
#include "paths.hpp"
#include "qmlscreen.hpp"
#include "rootwrapper.hpp"
#include "scanenv.hpp"
QuickshellSettings::QuickshellSettings() {
QObject::connect(
@@ -60,9 +59,7 @@ void QuickshellSettings::setWorkingDirectory(QString workingDirectory) { // NOLI
emit this->workingDirectoryChanged();
}
bool QuickshellSettings::watchFiles() const {
return this->mWatchFiles && qEnvironmentVariableIsEmpty("QS_DISABLE_FILE_WATCHER");
}
bool QuickshellSettings::watchFiles() const { return this->mWatchFiles; }
void QuickshellSettings::setWatchFiles(bool watchFiles) {
if (watchFiles == this->mWatchFiles) return;
@@ -316,16 +313,6 @@ QString QuickshellGlobal::iconPath(const QString& icon, const QString& fallback)
return IconImageProvider::requestString(icon, "", fallback);
}
bool QuickshellGlobal::hasThemeIcon(const QString& icon) { return QIcon::hasThemeIcon(icon); }
bool QuickshellGlobal::hasVersion(qint32 major, qint32 minor, const QStringList& features) {
return qs::scan::env::PreprocEnv::hasVersion(major, minor, features);
}
bool QuickshellGlobal::hasVersion(qint32 major, qint32 minor) {
return QuickshellGlobal::hasVersion(major, minor, QStringList());
}
QuickshellGlobal* QuickshellGlobal::create(QQmlEngine* engine, QJSEngine* /*unused*/) {
auto* qsg = new QuickshellGlobal();
auto* generation = EngineGeneration::findEngineGeneration(engine);
+2 -22
View File
@@ -127,21 +127,18 @@ class QuickshellGlobal: public QObject {
/// Usually `~/.local/share/quickshell/by-shell/<shell-id>`
///
/// Can be overridden using `//@ pragma DataDir $BASE/path` in the root qml file, where `$BASE`
/// corresponds to `$XDG_DATA_HOME` (usually `~/.local/share`).
/// corrosponds to `$XDG_DATA_HOME` (usually `~/.local/share`).
Q_PROPERTY(QString dataDir READ dataDir CONSTANT);
/// The per-shell state directory.
///
/// Usually `~/.local/state/quickshell/by-shell/<shell-id>`
///
/// Can be overridden using `//@ pragma StateDir $BASE/path` in the root qml file, where `$BASE`
/// corresponds to `$XDG_STATE_HOME` (usually `~/.local/state`).
/// corrosponds to `$XDG_STATE_HOME` (usually `~/.local/state`).
Q_PROPERTY(QString stateDir READ stateDir CONSTANT);
/// The per-shell cache directory.
///
/// Usually `~/.cache/quickshell/by-shell/<shell-id>`
///
/// Can be overridden using `//@ pragma CacheDir $BASE/path` in the root qml file, where `$BASE`
/// corresponds to `$XDG_CACHE_HOME` (usually `~/.cache`).
Q_PROPERTY(QString cacheDir READ cacheDir CONSTANT);
// clang-format on
QML_SINGLETON;
@@ -202,8 +199,6 @@ public:
/// Setting the `fallback` parameter of `iconPath` will attempt to load the fallback
/// icon if the requested one could not be loaded.
Q_INVOKABLE static QString iconPath(const QString& icon, const QString& fallback);
/// Check if specified icon has an available icon in your icon theme
Q_INVOKABLE static bool hasThemeIcon(const QString& icon);
/// Equivalent to `${Quickshell.configDir}/${path}`
Q_INVOKABLE [[nodiscard]] QString shellPath(const QString& path) const;
/// > [!WARNING] Deprecated: Renamed to @@shellPath() for clarity.
@@ -219,21 +214,6 @@ public:
///
/// The popup can also be blocked by setting `QS_NO_RELOAD_POPUP=1`.
Q_INVOKABLE void inhibitReloadPopup() { this->mInhibitReloadPopup = true; }
/// Check if Quickshell's version is at least `major.minor` and the listed
/// unreleased features are available. If Quickshell is newer than the given version
/// it is assumed that all unreleased features are present. The unreleased feature list
/// may be omitted.
///
/// > [!NOTE] You can feature gate code blocks using Quickshell's preprocessor which
/// > has the same function available.
/// >
/// > ```qml
/// > //@ if hasVersion(0, 3, ["feature"])
/// > ...
/// > //@ endif
/// > ```
Q_INVOKABLE static bool hasVersion(qint32 major, qint32 minor, const QStringList& features);
Q_INVOKABLE static bool hasVersion(qint32 major, qint32 minor);
void clearReloadPopupInhibit() { this->mInhibitReloadPopup = false; }
[[nodiscard]] bool isReloadPopupInhibited() const { return this->mInhibitReloadPopup; }
+3 -27
View File
@@ -63,6 +63,9 @@ void RootWrapper::reloadGraph(bool hard) {
qs::core::QmlToolingSupport::updateTooling(rootPath, scanner);
this->configDirWatcher.addPath(rootPath.path());
auto* generation = new EngineGeneration(rootPath, std::move(scanner));
generation->wrapper = this;
// todo: move into EngineGeneration
if (this->generation != nullptr) {
qInfo() << "Reloading configuration...";
@@ -71,33 +74,6 @@ void RootWrapper::reloadGraph(bool hard) {
QDir::setCurrent(this->originalWorkingDirectory);
if (!scanner.scanErrors.isEmpty()) {
qCritical() << "Failed to load configuration";
QString errorString = "Failed to load configuration";
for (auto& error: scanner.scanErrors) {
const auto& file = error.file;
QString rel;
if (file.startsWith(rootPath.path() % '/')) {
rel = '@' % file.sliced(rootPath.path().length() + 1);
} else {
rel = file;
}
auto msg = " error in " % rel % '[' % QString::number(error.line) % ":0]: " % error.message;
errorString += '\n' % msg;
qCritical().noquote() << msg;
}
if (this->generation != nullptr && this->generation->qsgInstance != nullptr) {
emit this->generation->qsgInstance->reloadFailed(errorString);
}
return;
}
auto* generation = new EngineGeneration(rootPath, std::move(scanner));
generation->wrapper = this;
QUrl url;
url.setScheme("qs");
url.setPath("@/qs/" % rootFile.fileName());
+53 -138
View File
@@ -1,12 +1,9 @@
#include "scan.hpp"
#include <cmath>
#include <utility>
#include <qcontainerfwd.h>
#include <qcryptographichash.h>
#include <qdir.h>
#include <qfileinfo.h>
#include <qjsengine.h>
#include <qjsonarray.h>
#include <qjsondocument.h>
#include <qjsonobject.h>
@@ -15,39 +12,19 @@
#include <qloggingcategory.h>
#include <qpair.h>
#include <qstring.h>
#include <qstringliteral.h>
#include <qtextstream.h>
#include "logcat.hpp"
#include "scanenv.hpp"
QS_LOGGING_CATEGORY(logQmlScanner, "quickshell.qmlscanner", QtWarningMsg);
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) {
if (this->scannedDirs.contains(dir)) return;
this->scannedDirs.push_back(dir);
const auto& path = dir.path();
void QmlScanner::scanDir(const QString& path) {
if (this->scannedDirs.contains(path)) return;
this->scannedDirs.push_back(path);
qCDebug(logQmlScanner) << "Scanning directory" << path;
auto dir = QDir(path);
struct Entry {
QString name;
@@ -60,8 +37,7 @@ void QmlScanner::scanDir(const QDir& dir) {
for (auto& name: dir.entryList(QDir::Files | QDir::NoDotAndDotDot)) {
if (name == "qmldir") {
qCDebug(
logQmlScanner
qCDebug(logQmlScanner
) << "Found qmldir file, qmldir synthesization will be disabled for directory"
<< path;
seenQmldir = true;
@@ -129,128 +105,68 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
qCDebug(logQmlScanner) << "Scanning qml file" << path;
QByteArray fileData;
if (!this->readAndHashFile(path, fileData)) {
auto file = QFile(path);
if (!file.open(QFile::ReadOnly | QFile::Text)) {
qCWarning(logQmlScanner) << "Failed to open file" << path;
return false;
}
auto stream = QTextStream(&fileData);
auto stream = QTextStream(&file);
auto imports = QVector<QString>();
bool inHeader = true;
auto ifScopes = QVector<bool>();
bool sourceMasked = false;
int lineNum = 0;
QString overrideText;
bool isOverridden = false;
auto pragmaEngine = QJSEngine();
pragmaEngine.globalObject().setPrototype(
pragmaEngine.newQObject(new qs::scan::env::PreprocEnv())
);
auto postError = [&, this](QString error) {
this->scanErrors.append({.file = path, .message = std::move(error), .line = lineNum});
};
while (!stream.atEnd()) {
++lineNum;
bool hideMask = false;
auto rawLine = stream.readLine();
auto line = rawLine.trimmed();
if (!sourceMasked && inHeader) {
if (!singleton && line == "pragma Singleton") {
singleton = true;
} else if (line.startsWith("import")) {
// we dont care about "import qs" as we always load the root folder
if (auto importCursor = line.indexOf(" qs."); importCursor != -1) {
importCursor += 4;
QString path;
auto line = stream.readLine().trimmed();
if (!singleton && line == "pragma Singleton") {
singleton = true;
} else if (!internal && line == "//@ pragma Internal") {
internal = true;
} else if (line.startsWith("import")) {
// we dont care about "import qs" as we always load the root folder
if (auto importCursor = line.indexOf(" qs."); importCursor != -1) {
importCursor += 4;
QString path;
while (importCursor != line.length()) {
auto c = line.at(importCursor);
if (c == '.') c = '/';
else if (c == ' ') break;
else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
|| c == '_')
{
} else {
qCWarning(logQmlScanner) << "Import line contains invalid characters: " << line;
goto next;
}
path.append(c);
importCursor += 1;
while (importCursor != line.length()) {
auto c = line.at(importCursor);
if (c == '.') c = '/';
else if (c == ' ') break;
else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')
|| c == '_')
{
} else {
qCWarning(logQmlScanner) << "Import line contains invalid characters: " << line;
goto next;
}
imports.append(this->rootPath.filePath(path));
} else if (auto startQuot = line.indexOf('"');
startQuot != -1 && line.length() >= startQuot + 3)
{
auto endQuot = line.indexOf('"', startQuot + 1);
if (endQuot == -1) continue;
auto name = line.sliced(startQuot + 1, endQuot - startQuot - 1);
imports.push_back(name);
path.append(c);
importCursor += 1;
}
} else if (!internal && line == "//@ pragma Internal") {
internal = true;
} else if (line.contains('{')) {
inHeader = false;
imports.append(this->rootPath.filePath(path));
} else if (auto startQuot = line.indexOf('"');
startQuot != -1 && line.length() >= startQuot + 3)
{
auto endQuot = line.indexOf('"', startQuot + 1);
if (endQuot == -1) continue;
auto name = line.sliced(startQuot + 1, endQuot - startQuot - 1);
imports.push_back(name);
}
}
if (line.startsWith("//@ if ")) {
auto code = line.sliced(7);
auto value = pragmaEngine.evaluate(code, path, 1234);
bool mask = true;
if (value.isError()) {
postError(QString("Evaluating if: %0").arg(value.toString()));
} else if (!value.isBool()) {
postError(QString("If expression \"%0\" is not a boolean").arg(value.toString()));
} else if (value.toBool()) {
mask = false;
}
if (!sourceMasked && mask) hideMask = true;
mask = sourceMasked || mask; // cant unmask if a nested if passes
ifScopes.append(mask);
if (mask) isOverridden = true;
sourceMasked = mask;
} else if (line.startsWith("//@ endif")) {
if (ifScopes.isEmpty()) {
postError("endif without matching if");
} else {
ifScopes.pop_back();
if (ifScopes.isEmpty()) sourceMasked = false;
else sourceMasked = ifScopes.last();
}
}
if (!hideMask && sourceMasked) overrideText.append("// MASKED: " % rawLine % '\n');
else overrideText.append(rawLine % '\n');
} else if (line.contains('{')) break;
next:;
}
if (!ifScopes.isEmpty()) {
postError("unclosed preprocessor if block");
}
if (isOverridden) {
this->fileIntercepts.insert(path, overrideText);
}
file.close();
if (logQmlScanner().isDebugEnabled() && !imports.isEmpty()) {
qCDebug(logQmlScanner) << "Found imports" << imports;
}
auto currentdir = QDir(QFileInfo(path).absolutePath());
auto currentdir = QDir(QFileInfo(path).canonicalPath());
// the root can never be a singleton so it dosent matter if we skip it
this->scanDir(currentdir);
this->scanDir(currentdir.path());
for (auto& import: imports) {
QString ipath;
@@ -263,9 +179,9 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
}
auto pathInfo = QFileInfo(ipath);
auto cpath = pathInfo.absoluteFilePath();
auto cpath = pathInfo.canonicalFilePath();
if (!pathInfo.exists()) {
if (cpath.isEmpty()) {
qCWarning(logQmlScanner) << "Ignoring unresolvable import" << ipath << "from" << path;
continue;
}
@@ -275,11 +191,8 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
continue;
}
if (import.endsWith(".js")) {
this->scannedFiles.push_back(cpath);
QByteArray jsData;
this->readAndHashFile(cpath, jsData);
} else this->scanDir(cpath);
if (import.endsWith(".js")) this->scannedFiles.push_back(cpath);
else this->scanDir(cpath);
}
return true;
@@ -294,12 +207,14 @@ void QmlScanner::scanQmlRoot(const QString& path) {
bool QmlScanner::scanQmlJson(const QString& path) {
qCDebug(logQmlScanner) << "Scanning qml.json file" << path;
QByteArray data;
if (!this->readAndHashFile(path, data)) {
auto file = QFile(path);
if (!file.open(QFile::ReadOnly | QFile::Text)) {
qCWarning(logQmlScanner) << "Failed to open file" << path;
return false;
}
auto data = file.readAll();
// Importing this makes CI builds fail for some reason.
QJsonParseError error; // NOLINT (misc-include-cleaner)
auto json = QJsonDocument::fromJson(data, &error);
+4 -15
View File
@@ -1,6 +1,5 @@
#pragma once
#include <qbytearray.h>
#include <qcontainerfwd.h>
#include <qdir.h>
#include <qhash.h>
@@ -17,25 +16,15 @@ public:
QmlScanner() = default;
QmlScanner(const QDir& rootPath): rootPath(rootPath) {}
void scanDir(const QDir& dir);
// path must be canonical
void scanDir(const QString& path);
void scanQmlRoot(const QString& path);
QVector<QDir> scannedDirs;
QVector<QString> scannedDirs;
QVector<QString> scannedFiles;
QHash<QString, QByteArray> fileHashes;
QHash<QString, QString> fileIntercepts;
struct ScanError {
QString file;
QString message;
int line;
};
QVector<ScanError> scanErrors;
bool readAndHashFile(const QString& path, QByteArray& data);
[[nodiscard]] bool hasFileContentChanged(const QString& path) const;
private:
QDir rootPath;
-31
View File
@@ -1,31 +0,0 @@
#include "scanenv.hpp"
#include <qcontainerfwd.h>
#include <qtenvironmentvariables.h>
#include "build.hpp"
namespace qs::scan::env {
bool PreprocEnv::hasVersion(int major, int minor, const QStringList& features) {
if (QS_VERSION_MAJOR > major) return true;
if (QS_VERSION_MAJOR == major && QS_VERSION_MINOR > minor) return true;
auto availFeatures = QString(QS_UNRELEASED_FEATURES).split(';');
for (const auto& feature: features) {
if (!availFeatures.contains(feature)) return false;
}
return QS_VERSION_MAJOR == major && QS_VERSION_MINOR == minor;
}
QString PreprocEnv::env(const QString& variable) {
return qEnvironmentVariable(variable.toStdString().c_str());
}
bool PreprocEnv::isEnvSet(const QString& variable) {
return qEnvironmentVariableIsSet(variable.toStdString().c_str());
}
} // namespace qs::scan::env
-20
View File
@@ -1,20 +0,0 @@
#pragma once
#include <qcontainerfwd.h>
#include <qobject.h>
#include <qtmetamacros.h>
namespace qs::scan::env {
class PreprocEnv: public QObject {
Q_OBJECT;
public:
Q_INVOKABLE static bool
hasVersion(int major, int minor, const QStringList& features = QStringList());
Q_INVOKABLE static QString env(const QString& variable);
Q_INVOKABLE static bool isEnvSet(const QString& variable);
};
} // namespace qs::scan::env
+2 -2
View File
@@ -72,8 +72,8 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
do {
++iter;
} while (iter != this->mValues.end()
&& std::find_if(newIter, newValues.end(), eqPredicate(*iter))
== newValues.end());
&& std::find_if(newIter, newValues.end(), eqPredicate(*iter)) == newValues.end()
);
auto index = static_cast<qint32>(std::distance(this->mValues.begin(), iter));
auto startIndex = static_cast<qint32>(std::distance(this->mValues.begin(), startIter));
-98
View File
@@ -1,98 +0,0 @@
#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;
}
-26
View File
@@ -1,26 +0,0 @@
#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;
};
+1 -1
View File
@@ -54,7 +54,7 @@ bool QmlToolingSupport::lockTooling() {
return false;
}
struct flock lock = {
auto lock = flock {
.l_type = F_WRLCK,
.l_whence = SEEK_SET, // NOLINT (fcntl.h??)
.l_start = 0,
+32 -1
View File
@@ -29,7 +29,7 @@ struct StringLiteral16 {
}
[[nodiscard]] constexpr const QChar* qCharPtr() const noexcept {
return std::bit_cast<const QChar*>(&this->value); // NOLINT
return std::bit_cast<const QChar*>(&this->value);
}
[[nodiscard]] Q_ALWAYS_INLINE operator QString() const noexcept {
@@ -251,6 +251,37 @@ public:
GuardedEmitBlocker block() { return GuardedEmitBlocker(&this->blocked); }
};
template <auto member, auto destroyedSlot, auto changedSignal>
class SimpleObjectHandleOps {
using Traits = MemberPointerTraits<decltype(member)>;
public:
static bool setObject(Traits::Class* parent, Traits::Type value) {
if (value == parent->*member) return false;
if (parent->*member != nullptr) {
QObject::disconnect(parent->*member, &QObject::destroyed, parent, destroyedSlot);
}
parent->*member = value;
if (value != nullptr) {
QObject::connect(parent->*member, &QObject::destroyed, parent, destroyedSlot);
}
if constexpr (changedSignal != nullptr) {
emit(parent->*changedSignal)();
}
return true;
}
};
template <auto member, auto destroyedSlot, auto changedSignal = nullptr>
bool setSimpleObjectHandle(auto* parent, auto* value) {
return SimpleObjectHandleOps<member, destroyedSlot, changedSignal>::setObject(parent, value);
}
template <auto methodPtr>
class MethodFunctor {
using PtrMeta = MemberPointerTraits<decltype(methodPtr)>;
+5 -44
View File
@@ -6,51 +6,12 @@ qt_add_library(quickshell-crash STATIC
qs_pch(quickshell-crash SET large)
if (VENDOR_CPPTRACE)
message(STATUS "Vendoring cpptrace...")
include(FetchContent)
# For use without internet access see: https://cmake.org/cmake/help/latest/module/FetchContent.html#variable:FETCHCONTENT_SOURCE_DIR_%3CuppercaseName%3E
FetchContent_Declare(
cpptrace
GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
GIT_TAG v1.0.4
)
set(CPPTRACE_UNWIND_WITH_LIBUNWIND TRUE)
FetchContent_MakeAvailable(cpptrace)
else ()
find_package(cpptrace REQUIRED)
# useful for cross after you have already checked cpptrace is built correctly
if (NOT DO_NOT_CHECK_CPPTRACE_USABILITY)
try_run(CPPTRACE_SIGNAL_SAFE_UNWIND CPPTRACE_SIGNAL_SAFE_UNWIND_COMP
SOURCE_FROM_CONTENT check.cxx "
#include <cpptrace/basic.hpp>
int main() {
return cpptrace::can_signal_safe_unwind() ? 0 : 1;
}
"
LOG_DESCRIPTION "Checking ${CPPTRACE_SIGNAL_SAFE_UNWIND}"
LINK_LIBRARIES cpptrace::cpptrace
COMPILE_OUTPUT_VARIABLE CPPTRACE_SIGNAL_SAFE_UNWIND_LOG
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON
)
if (NOT CPPTRACE_SIGNAL_SAFE_UNWIND_COMP)
message(STATUS "${CPPTRACE_SIGNAL_SAFE_UNWIND_LOG}")
message(FATAL_ERROR "Failed to compile cpptrace signal safe unwind tester.")
endif()
if (NOT CPPTRACE_SIGNAL_SAFE_UNWIND EQUAL 0)
message(STATUS "Cpptrace signal safe unwind test exited with: ${CPPTRACE_SIGNAL_SAFE_UNWIND}")
message(FATAL_ERROR "Cpptrace was built without CPPTRACE_UNWIND_WITH_LIBUNWIND set to true. Enable libunwind support in the package or set VENDOR_CPPTRACE to true when building Quickshell.")
endif()
endif ()
endif ()
find_package(PkgConfig REQUIRED)
pkg_check_modules(breakpad REQUIRED IMPORTED_TARGET breakpad)
# only need client?? take only includes from pkg config todo
target_link_libraries(quickshell-crash PRIVATE PkgConfig::breakpad -lbreakpad_client)
# quick linked for pch compat
target_link_libraries(quickshell-crash PRIVATE quickshell-build Qt::Quick Qt::Widgets cpptrace::cpptrace)
target_link_libraries(quickshell-crash PRIVATE quickshell-build Qt::Quick Qt::Widgets)
target_link_libraries(quickshell PRIVATE quickshell-crash)
+100 -145
View File
@@ -1,13 +1,12 @@
#include "handler.hpp"
#include <algorithm>
#include <array>
#include <cerrno>
#include <csignal>
#include <cstdio>
#include <cstring>
#include <cpptrace/basic.hpp>
#include <cpptrace/forward.hpp>
#include <bits/types/sigset_t.h>
#include <breakpad/client/linux/handler/exception_handler.h>
#include <breakpad/client/linux/handler/minidump_descriptor.h>
#include <breakpad/common/linux/linux_libc_support.h>
#include <qdatastream.h>
#include <qfile.h>
#include <qlogging.h>
@@ -20,76 +19,96 @@
extern char** environ; // NOLINT
using namespace google_breakpad;
namespace qs::crash {
namespace {
QS_LOGGING_CATEGORY(logCrashHandler, "quickshell.crashhandler", QtWarningMsg);
}
void writeEnvInt(char* buf, const char* name, int value) {
// NOLINTBEGIN (cppcoreguidelines-pro-bounds-pointer-arithmetic)
while (*name != '\0') *buf++ = *name++;
*buf++ = '=';
struct CrashHandlerPrivate {
ExceptionHandler* exceptionHandler = nullptr;
int minidumpFd = -1;
int infoFd = -1;
if (value < 0) {
*buf++ = '-';
value = -value;
static bool minidumpCallback(const MinidumpDescriptor& descriptor, void* context, bool succeeded);
};
CrashHandler::CrashHandler(): d(new CrashHandlerPrivate()) {}
void CrashHandler::init() {
// MinidumpDescriptor has no move constructor and the copy constructor breaks fds.
auto createHandler = [this](const MinidumpDescriptor& desc) {
this->d->exceptionHandler = new ExceptionHandler(
desc,
nullptr,
&CrashHandlerPrivate::minidumpCallback,
this->d,
true,
-1
);
};
qCDebug(logCrashHandler) << "Starting crash handler...";
this->d->minidumpFd = memfd_create("quickshell:minidump", MFD_CLOEXEC);
if (this->d->minidumpFd == -1) {
qCCritical(logCrashHandler
) << "Failed to allocate minidump memfd, minidumps will be saved in the working directory.";
createHandler(MinidumpDescriptor("."));
} else {
qCDebug(logCrashHandler) << "Created memfd" << this->d->minidumpFd
<< "for holding possible minidumps.";
createHandler(MinidumpDescriptor(this->d->minidumpFd));
}
if (value == 0) {
*buf++ = '0';
*buf = '\0';
qCInfo(logCrashHandler) << "Crash handler initialized.";
}
void CrashHandler::setRelaunchInfo(const RelaunchInfo& info) {
this->d->infoFd = memfd_create("quickshell:instance_info", MFD_CLOEXEC);
if (this->d->infoFd == -1) {
qCCritical(logCrashHandler
) << "Failed to allocate instance info memfd, crash recovery will not work.";
return;
}
auto* start = buf;
while (value > 0) {
*buf++ = static_cast<char>('0' + (value % 10));
value /= 10;
QFile file;
if (!file.open(this->d->infoFd, QFile::ReadWrite)) {
qCCritical(logCrashHandler
) << "Failed to open instance info memfd, crash recovery will not work.";
}
*buf = '\0';
std::reverse(start, buf);
// NOLINTEND
QDataStream ds(&file);
ds << info;
file.flush();
qCDebug(logCrashHandler) << "Stored instance info in memfd" << this->d->infoFd;
}
void signalHandler(
int sig,
siginfo_t* /*info*/, // NOLINT (misc-include-cleaner)
void* /*context*/
CrashHandler::~CrashHandler() {
delete this->d->exceptionHandler;
delete this->d;
}
bool CrashHandlerPrivate::minidumpCallback(
const MinidumpDescriptor& /*descriptor*/,
void* context,
bool /*success*/
) {
if (CrashInfo::INSTANCE.traceFd != -1) {
auto traceBuffer = std::array<cpptrace::frame_ptr, 1024>();
auto frameCount = cpptrace::safe_generate_raw_trace(traceBuffer.data(), traceBuffer.size(), 1);
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
}
}
fail:;
}
// A fork that just dies to ensure the coredump is caught by the system.
auto coredumpPid = fork();
if (coredumpPid == 0) {
// NOLINTBEGIN (misc-include-cleaner)
sigset_t set;
sigfillset(&set);
sigprocmask(SIG_UNBLOCK, &set, nullptr);
// NOLINTEND
raise(sig);
_exit(-1);
return false;
}
auto* self = static_cast<CrashHandlerPrivate*>(context);
auto exe = std::array<char, 4096>();
if (readlink("/proc/self/exe", exe.data(), exe.size() - 1) == -1) {
perror("Failed to find crash reporter executable.\n");
@@ -101,19 +120,17 @@ void signalHandler(
auto env = std::array<char*, 4096>();
auto envi = 0;
// dup to remove CLOEXEC
auto infoFdStr = std::array<char, 48>();
writeEnvInt(infoFdStr.data(), "__QUICKSHELL_CRASH_INFO_FD", dup(CrashInfo::INSTANCE.infoFd));
auto infoFd = dup(self->infoFd);
auto infoFdStr = std::array<char, 38>();
memcpy(infoFdStr.data(), "__QUICKSHELL_CRASH_INFO_FD=-1" /*\0*/, 30);
if (infoFd != -1) my_uitos(&infoFdStr[27], infoFd, 10);
env[envi++] = infoFdStr.data();
auto corePidStr = std::array<char, 48>();
writeEnvInt(corePidStr.data(), "__QUICKSHELL_CRASH_DUMP_PID", coredumpPid);
auto corePidStr = std::array<char, 39>();
memcpy(corePidStr.data(), "__QUICKSHELL_CRASH_DUMP_PID=-1" /*\0*/, 31);
if (coredumpPid != -1) my_uitos(&corePidStr[28], coredumpPid, 10);
env[envi++] = corePidStr.data();
auto sigStr = std::array<char, 48>();
writeEnvInt(sigStr.data(), "__QUICKSHELL_CRASH_SIGNAL", sig);
env[envi++] = sigStr.data();
auto populateEnv = [&]() {
auto senvi = 0;
while (envi != 4095) {
@@ -125,18 +142,30 @@ void signalHandler(
env[envi] = nullptr;
};
sigset_t sigset;
sigemptyset(&sigset); // NOLINT (include)
sigprocmask(SIG_SETMASK, &sigset, nullptr); // NOLINT
auto pid = fork();
if (pid == -1) {
perror("Failed to fork and launch crash reporter.\n");
_exit(-1);
return false;
} else if (pid == 0) {
// dup to remove CLOEXEC
auto dumpFdStr = std::array<char, 48>();
auto logFdStr = std::array<char, 48>();
writeEnvInt(dumpFdStr.data(), "__QUICKSHELL_CRASH_DUMP_FD", dup(CrashInfo::INSTANCE.traceFd));
writeEnvInt(logFdStr.data(), "__QUICKSHELL_CRASH_LOG_FD", dup(CrashInfo::INSTANCE.logFd));
// if already -1 will return -1
auto dumpFd = dup(self->minidumpFd);
auto logFd = dup(CrashInfo::INSTANCE.logFd);
// allow up to 10 digits, which should never happen
auto dumpFdStr = std::array<char, 38>();
auto logFdStr = std::array<char, 37>();
memcpy(dumpFdStr.data(), "__QUICKSHELL_CRASH_DUMP_FD=-1" /*\0*/, 30);
memcpy(logFdStr.data(), "__QUICKSHELL_CRASH_LOG_FD=-1" /*\0*/, 29);
if (dumpFd != -1) my_uitos(&dumpFdStr[27], dumpFd, 10);
if (logFd != -1) my_uitos(&logFdStr[26], logFd, 10);
env[envi++] = dumpFdStr.data();
env[envi++] = logFdStr.data();
@@ -153,82 +182,8 @@ void signalHandler(
perror("Failed to relaunch quickshell.\n");
_exit(-1);
}
}
} // namespace
void CrashHandler::init() {
qCDebug(logCrashHandler) << "Starting crash handler...";
CrashInfo::INSTANCE.traceFd = memfd_create("quickshell:trace", MFD_CLOEXEC);
if (CrashInfo::INSTANCE.traceFd == -1) {
qCCritical(logCrashHandler) << "Failed to allocate trace memfd, stack traces will not be "
"available in crash reports.";
} else {
qCDebug(logCrashHandler) << "Created memfd" << CrashInfo::INSTANCE.traceFd
<< "for holding possible stack traces.";
}
{
// Preload anything dynamically linked to avoid malloc etc in the dynamic loader.
// See cpptrace documentation for more information.
auto buffer = std::array<cpptrace::frame_ptr, 10>();
cpptrace::safe_generate_raw_trace(buffer.data(), buffer.size());
auto frame = cpptrace::safe_object_frame();
cpptrace::get_safe_object_frame(buffer[0], &frame);
}
// NOLINTBEGIN (misc-include-cleaner)
// Set up alternate signal stack for stack overflow handling
auto ss = stack_t();
ss.ss_sp = new char[SIGSTKSZ];
ss.ss_size = SIGSTKSZ;
ss.ss_flags = 0;
sigaltstack(&ss, nullptr);
// Install signal handlers
struct sigaction sa {};
sa.sa_sigaction = &signalHandler;
sa.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESETHAND;
sigemptyset(&sa.sa_mask);
sigaction(SIGSEGV, &sa, nullptr);
sigaction(SIGABRT, &sa, nullptr);
sigaction(SIGFPE, &sa, nullptr);
sigaction(SIGILL, &sa, nullptr);
sigaction(SIGBUS, &sa, nullptr);
sigaction(SIGTRAP, &sa, nullptr);
// NOLINTEND (misc-include-cleaner)
qCInfo(logCrashHandler) << "Crash handler initialized.";
}
void CrashHandler::setRelaunchInfo(const RelaunchInfo& info) {
CrashInfo::INSTANCE.infoFd = memfd_create("quickshell:instance_info", MFD_CLOEXEC);
if (CrashInfo::INSTANCE.infoFd == -1) {
qCCritical(
logCrashHandler
) << "Failed to allocate instance info memfd, crash recovery will not work.";
return;
}
QFile file;
if (!file.open(CrashInfo::INSTANCE.infoFd, QFile::ReadWrite)) {
qCCritical(
logCrashHandler
) << "Failed to open instance info memfd, crash recovery will not work.";
}
QDataStream ds(&file);
ds << info;
file.flush();
qCDebug(logCrashHandler) << "Stored instance info in memfd" << CrashInfo::INSTANCE.infoFd;
return false; // should make sure it hits the system coredump handler
}
} // namespace qs::crash
+11 -2
View File
@@ -5,10 +5,19 @@
#include "../core/instanceinfo.hpp"
namespace qs::crash {
struct CrashHandlerPrivate;
class CrashHandler {
public:
static void init();
static void setRelaunchInfo(const RelaunchInfo& info);
explicit CrashHandler();
~CrashHandler();
Q_DISABLE_COPY_MOVE(CrashHandler);
void init();
void setRelaunchInfo(const RelaunchInfo& info);
private:
CrashHandlerPrivate* d;
};
} // namespace qs::crash
+15 -17
View File
@@ -5,7 +5,6 @@
#include <qapplication.h>
#include <qboxlayout.h>
#include <qconfig.h>
#include <qcontainerfwd.h>
#include <qdesktopservices.h>
#include <qfont.h>
#include <qfontinfo.h>
@@ -13,22 +12,11 @@
#include <qnamespace.h>
#include <qobject.h>
#include <qpushbutton.h>
#include <qtenvironmentvariables.h>
#include <qtversion.h>
#include <qwidget.h>
#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 {
public:
ReportLabel(const QString& label, const QString& content, QWidget* parent): QWidget(parent) {
@@ -78,17 +66,22 @@ CrashReporterGui::CrashReporterGui(QString reportFolder, int pid)
mainLayout->addSpacing(textHeight);
if (qtVersionMatches) {
mainLayout->addWidget(
new QLabel("Please open a bug report for this issue on the issue tracker.")
mainLayout->addWidget(new QLabel("Please open a bug report for this issue via github or email.")
);
} else {
mainLayout->addWidget(new QLabel(
"Please rebuild Quickshell against the current Qt version.\n"
"If this does not solve the problem, please open a bug report on the issue tracker."
"If this does not solve the problem, please open a bug report via github or email."
));
}
mainLayout->addWidget(new ReportLabel("Tracker:", crashreportUrl(), this));
mainLayout->addWidget(new ReportLabel(
"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);
buttons->setMinimumWidth(900);
@@ -118,5 +111,10 @@ void CrashReporterGui::openFolder() {
QDesktopServices::openUrl(QUrl::fromLocalFile(this->reportFolder));
}
void CrashReporterGui::openReportUrl() { QDesktopServices::openUrl(QUrl(crashreportUrl())); }
void CrashReporterGui::openReportUrl() {
QDesktopServices::openUrl(
QUrl("https://github.com/outfoxxed/quickshell/issues/new?template=crash.yml")
);
}
void CrashReporterGui::cancel() { QApplication::quit(); }
+49 -99
View File
@@ -1,11 +1,9 @@
#include "main.hpp"
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <cpptrace/basic.hpp>
#include <cpptrace/formatting.hpp>
#include <qapplication.h>
#include <qconfig.h>
#include <qcoreapplication.h>
#include <qdatastream.h>
#include <qdir.h>
@@ -14,18 +12,15 @@
#include <qloggingcategory.h>
#include <qtenvironmentvariables.h>
#include <qtextstream.h>
#include <qtypes.h>
#include <qtversion.h>
#include <sys/sendfile.h>
#include <sys/types.h>
#include <unistd.h>
#include "../core/debuginfo.hpp"
#include "../core/instanceinfo.hpp"
#include "../core/logcat.hpp"
#include "../core/logging.hpp"
#include "../core/logging_p.hpp"
#include "../core/paths.hpp"
#include "../core/ringbuf.hpp"
#include "build.hpp"
#include "interface.hpp"
namespace {
@@ -66,76 +61,6 @@ int tryDup(int fd, const QString& path) {
return 0;
}
QString readRecentLogs(int logFd, int maxLines, qint64 maxAgeSecs) {
QFile file;
if (!file.open(logFd, QFile::ReadOnly, QFile::AutoCloseHandle)) {
return QStringLiteral("(failed to open log fd)\n");
}
file.seek(0);
qs::log::EncodedLogReader reader;
reader.setDevice(&file);
bool readable = false;
quint8 logVersion = 0;
quint8 readerVersion = 0;
if (!reader.readHeader(&readable, &logVersion, &readerVersion) || !readable) {
return QStringLiteral("(failed to read log header)\n");
}
// Read all messages, keeping last maxLines in a ring buffer
auto tail = RingBuffer<qs::log::LogMessage>(maxLines);
qs::log::LogMessage message;
while (reader.read(&message)) {
tail.emplace(message);
}
if (tail.size() == 0) {
return QStringLiteral("(no logs)\n");
}
// Filter to only messages within maxAgeSecs of the newest message
auto cutoff = tail.at(0).time.addSecs(-maxAgeSecs);
QString result;
auto stream = QTextStream(&result);
for (auto i = tail.size() - 1; i != -1; i--) {
if (tail.at(i).time < cutoff) continue;
qs::log::LogMessage::formatMessage(stream, tail.at(i), false, true);
stream << '\n';
}
if (result.isEmpty()) {
return QStringLiteral("(no recent logs)\n");
}
return result;
}
cpptrace::stacktrace resolveStacktrace(int dumpFd) {
QFile sourceFile;
if (!sourceFile.open(dumpFd, QFile::ReadOnly, QFile::AutoCloseHandle)) {
qCCritical(logCrashReporter) << "Failed to open trace memfd.";
return {};
}
sourceFile.seek(0);
auto data = sourceFile.readAll();
auto frameCount = static_cast<size_t>(data.size()) / sizeof(cpptrace::safe_object_frame);
if (frameCount == 0) return {};
const auto* frames = reinterpret_cast<const cpptrace::safe_object_frame*>(data.constData());
cpptrace::object_trace objectTrace;
for (size_t i = 0; i < frameCount; i++) {
objectTrace.frames.push_back(frames[i].resolve()); // NOLINT
}
return objectTrace.resolve();
}
void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instance) {
qCDebug(logCrashReporter) << "Recording crash information at" << crashDir.path();
@@ -146,49 +71,74 @@ void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instance) {
}
auto crashProc = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_PID").toInt();
auto crashSignal = qEnvironmentVariable("__QUICKSHELL_CRASH_SIGNAL").toInt();
auto dumpFd = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_FD").toInt();
auto logFd = qEnvironmentVariable("__QUICKSHELL_CRASH_LOG_FD").toInt();
qCDebug(logCrashReporter) << "Resolving stacktrace from fd" << dumpFd;
auto stacktrace = resolveStacktrace(dumpFd);
qCDebug(logCrashReporter) << "Saving minidump from fd" << dumpFd;
auto dumpDupStatus = tryDup(dumpFd, crashDir.filePath("minidump.dmp.log"));
if (dumpDupStatus != 0) {
qCCritical(logCrashReporter) << "Failed to write minidump:" << dumpDupStatus;
}
qCDebug(logCrashReporter) << "Reading recent log lines from fd" << logFd;
auto logDupFd = dup(logFd);
auto recentLogs = readRecentLogs(logFd, 100, 10);
qCDebug(logCrashReporter) << "Saving log from fd" << logDupFd;
auto logDupStatus = tryDup(logDupFd, crashDir.filePath("log.qslog.log"));
qCDebug(logCrashReporter) << "Saving log from fd" << logFd;
auto logDupStatus = tryDup(logFd, crashDir.filePath("log.qslog.log"));
if (logDupStatus != 0) {
qCCritical(logCrashReporter) << "Failed to save log:" << logDupStatus;
}
auto copyBinStatus = 0;
if (!DISTRIBUTOR_DEBUGINFO_AVAILABLE) {
qCDebug(logCrashReporter) << "Copying binary to crash folder";
if (!QFile(QCoreApplication::applicationFilePath()).copy(crashDir.filePath("executable.txt"))) {
copyBinStatus = 1;
qCCritical(logCrashReporter) << "Failed to copy binary.";
}
}
{
auto extraInfoFile = QFile(crashDir.filePath("report.txt"));
auto extraInfoFile = QFile(crashDir.filePath("info.txt"));
if (!extraInfoFile.open(QFile::WriteOnly)) {
qCCritical(logCrashReporter) << "Failed to open crash info file for writing.";
} else {
auto stream = QTextStream(&extraInfoFile);
stream << qs::debuginfo::combinedInfo();
stream << "===== Build Information =====\n";
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===== Instance Information =====\n";
stream << "Signal: " << strsignal(crashSignal) << " (" << crashSignal << ")\n"; // NOLINT
stream << "\n===== Runtime Information =====\n";
stream << "Runtime Qt Version: " << qVersion() << '\n';
stream << "Crashed process ID: " << crashProc << '\n';
stream << "Run ID: " << instance.instanceId << '\n';
stream << "Shell ID: " << instance.shellId << '\n';
stream << "Config Path: " << instance.configPath << '\n';
stream << "\n===== Stacktrace =====\n";
if (stacktrace.empty()) {
stream << "(no trace available)\n";
stream << "\n===== Report Integrity =====\n";
stream << "Minidump save status: " << dumpDupStatus << '\n';
stream << "Log save status: " << logDupStatus << '\n';
stream << "Binary copy status: " << copyBinStatus << '\n';
stream << "\n===== System Information =====\n\n";
stream << "/etc/os-release:";
auto osReleaseFile = QFile("/etc/os-release");
if (osReleaseFile.open(QFile::ReadOnly)) {
stream << '\n' << osReleaseFile.readAll() << '\n';
osReleaseFile.close();
} else {
auto formatter = cpptrace::formatter().header(std::string());
auto traceStr = formatter.format(stacktrace);
stream << QString::fromStdString(traceStr) << '\n';
stream << "FAILED TO OPEN\n";
}
stream << "\n===== Log Tail =====\n";
stream << recentLogs;
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";
}
extraInfoFile.close();
}
+2 -4
View File
@@ -214,10 +214,8 @@ void DBusPropertyGroup::updatePropertySet(const QVariantMap& properties, bool co
}
}
void DBusPropertyGroup::tryUpdateProperty(
DBusPropertyCore* property,
const QVariant& variant
) const {
void DBusPropertyGroup::tryUpdateProperty(DBusPropertyCore* property, const QVariant& variant)
const {
property->mExists = true;
auto error = property->store(variant);
+1 -1
View File
@@ -217,7 +217,7 @@ protected:
private:
[[nodiscard]] constexpr Owner* owner() const {
auto* self = std::bit_cast<char*>(this); // NOLINT
auto* self = std::bit_cast<char*>(this);
return std::bit_cast<Owner*>(self - offset()); // NOLINT
}
+1 -1
View File
@@ -9,7 +9,7 @@
#include <qqmlinfo.h>
#include <qquickitem.h>
#include <qquickwindow.h>
#include <qstring.h>
#include <qstringliteral.h>
#include "../core/logcat.hpp"
+1 -1
View File
@@ -24,7 +24,7 @@ qt_add_qml_module(quickshell-io
qs_add_module_deps_light(quickshell-io Quickshell)
install_qml_module(quickshell-io)
target_link_libraries(quickshell-io PRIVATE Qt::Quick quickshell-ipc)
target_link_libraries(quickshell-io PRIVATE Qt::Quick)
target_link_libraries(quickshell PRIVATE quickshell-ioplugin)
qs_module_pch(quickshell-io)
-12
View File
@@ -190,14 +190,6 @@ QString WirePropertyDefinition::toString() const {
return "property " % this->name % ": " % this->type;
}
QString WireSignalDefinition::toString() const {
if (this->rettype.isEmpty()) {
return "signal " % this->name % "()";
} else {
return "signal " % this->name % "(" % this->retname % ": " % this->rettype % ')';
}
}
QString WireTargetDefinition::toString() const {
QString accum = "target " % this->name;
@@ -209,10 +201,6 @@ QString WireTargetDefinition::toString() const {
accum += "\n " % prop.toString();
}
for (const auto& sig: this->signalFunctions) {
accum += "\n " % sig.toString();
}
return accum;
}
+1 -18
View File
@@ -146,31 +146,14 @@ struct WirePropertyDefinition {
DEFINE_SIMPLE_DATASTREAM_OPS(WirePropertyDefinition, data.name, data.type);
struct WireSignalDefinition {
QString name;
QString retname;
QString rettype;
[[nodiscard]] QString toString() const;
};
DEFINE_SIMPLE_DATASTREAM_OPS(WireSignalDefinition, data.name, data.retname, data.rettype);
struct WireTargetDefinition {
QString name;
QVector<WireFunctionDefinition> functions;
QVector<WirePropertyDefinition> properties;
QVector<WireSignalDefinition> signalFunctions;
[[nodiscard]] QString toString() const;
};
DEFINE_SIMPLE_DATASTREAM_OPS(
WireTargetDefinition,
data.name,
data.functions,
data.properties,
data.signalFunctions
);
DEFINE_SIMPLE_DATASTREAM_OPS(WireTargetDefinition, data.name, data.functions, data.properties);
} // namespace qs::io::ipc
+5 -104
View File
@@ -1,11 +1,10 @@
#include "ipccomm.hpp"
#include <utility>
#include <cstdio>
#include <variant>
#include <qcontainerfwd.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qobject.h>
#include <qtextstream.h>
#include <qtypes.h>
@@ -20,6 +19,10 @@ using namespace qs::ipc;
namespace qs::io::ipc::comm {
struct NoCurrentGeneration: std::monostate {};
struct TargetNotFound: std::monostate {};
struct EntryNotFound: std::monostate {};
using QueryResponse = std::variant<
std::monostate,
NoCurrentGeneration,
@@ -311,106 +314,4 @@ int getProperty(IpcClient* client, const QString& target, const QString& propert
return -1;
}
int listenToSignal(IpcClient* client, const QString& target, const QString& signal, bool once) {
if (target.isEmpty()) {
qCCritical(logBare) << "Target required to listen for signals.";
return -1;
} else if (signal.isEmpty()) {
qCCritical(logBare) << "Signal required to listen.";
return -1;
}
client->sendMessage(IpcCommand(SignalListenCommand {.target = target, .signal = signal}));
while (true) {
SignalListenResponse slot;
if (!client->waitForResponse(slot)) return -1;
if (std::holds_alternative<SignalResponse>(slot)) {
auto& result = std::get<SignalResponse>(slot);
QTextStream(stdout) << result.response << Qt::endl;
if (once) return 0;
else continue;
} else if (std::holds_alternative<TargetNotFound>(slot)) {
qCCritical(logBare) << "Target not found.";
} else if (std::holds_alternative<EntryNotFound>(slot)) {
qCCritical(logBare) << "Signal not found.";
} else if (std::holds_alternative<NoCurrentGeneration>(slot)) {
qCCritical(logBare) << "Not ready to accept queries yet.";
} else {
qCCritical(logIpc) << "Received invalid IPC response from" << client;
}
break;
}
return -1;
}
void SignalListenCommand::exec(qs::ipc::IpcServerConnection* conn) {
auto resp = conn->responseStream<SignalListenResponse>();
if (auto* generation = EngineGeneration::currentGeneration()) {
auto* registry = IpcHandlerRegistry::forGeneration(generation);
auto* handler = registry->findHandler(this->target);
if (!handler) {
resp << TargetNotFound();
return;
}
auto* signal = handler->findSignal(this->signal);
if (!signal) {
resp << EntryNotFound();
return;
}
new RemoteSignalListener(conn, *this);
} else {
conn->respond(SignalListenResponse(NoCurrentGeneration()));
}
}
RemoteSignalListener::RemoteSignalListener(
qs::ipc::IpcServerConnection* conn,
SignalListenCommand command
)
: conn(conn)
, command(std::move(command)) {
conn->setParent(this);
QObject::connect(
IpcSignalRemoteListener::instance(),
&IpcSignalRemoteListener::triggered,
this,
&RemoteSignalListener::onSignal
);
QObject::connect(
conn,
&qs::ipc::IpcServerConnection::destroyed,
this,
&RemoteSignalListener::onConnDestroyed
);
qCDebug(logIpc) << "Remote listener created for" << this->command.target << this->command.signal
<< ":" << this;
}
RemoteSignalListener::~RemoteSignalListener() {
qCDebug(logIpc) << "Destroying remote listener" << this;
}
void RemoteSignalListener::onSignal(
const QString& target,
const QString& signal,
const QString& value
) {
if (target != this->command.target || signal != this->command.signal) return;
qCDebug(logIpc) << "Remote signal" << signal << "triggered on" << target << "with value" << value;
this->conn->respond(SignalListenResponse(SignalResponse {.response = value}));
}
void RemoteSignalListener::onConnDestroyed() { this->deleteLater(); }
} // namespace qs::io::ipc::comm
-50
View File
@@ -2,8 +2,6 @@
#include <qcontainerfwd.h>
#include <qflags.h>
#include <qobject.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../ipc/ipc.hpp"
@@ -50,52 +48,4 @@ DEFINE_SIMPLE_DATASTREAM_OPS(StringPropReadCommand, data.target, data.property);
int getProperty(qs::ipc::IpcClient* client, const QString& target, const QString& property);
struct SignalListenCommand {
QString target;
QString signal;
void exec(qs::ipc::IpcServerConnection* conn);
};
DEFINE_SIMPLE_DATASTREAM_OPS(SignalListenCommand, data.target, data.signal);
int listenToSignal(
qs::ipc::IpcClient* client,
const QString& target,
const QString& signal,
bool once
);
struct NoCurrentGeneration: std::monostate {};
struct TargetNotFound: std::monostate {};
struct EntryNotFound: std::monostate {};
struct SignalResponse {
QString response;
};
DEFINE_SIMPLE_DATASTREAM_OPS(SignalResponse, data.response);
using SignalListenResponse = std::
variant<std::monostate, NoCurrentGeneration, TargetNotFound, EntryNotFound, SignalResponse>;
class RemoteSignalListener: public QObject {
Q_OBJECT;
public:
explicit RemoteSignalListener(qs::ipc::IpcServerConnection* conn, SignalListenCommand command);
~RemoteSignalListener() override;
Q_DISABLE_COPY_MOVE(RemoteSignalListener);
private slots:
void onSignal(const QString& target, const QString& signal, const QString& value);
void onConnDestroyed();
private:
qs::ipc::IpcServerConnection* conn;
SignalListenCommand command;
};
} // namespace qs::io::ipc::comm
+8 -112
View File
@@ -1,7 +1,5 @@
#include "ipchandler.hpp"
#include <cstddef>
#include <memory>
#include <utility>
#include <qcontainerfwd.h>
#include <qdebug.h>
@@ -141,75 +139,6 @@ WirePropertyDefinition IpcProperty::wireDef() const {
return wire;
}
WireSignalDefinition IpcSignal::wireDef() const {
WireSignalDefinition wire;
wire.name = this->signal.name();
if (this->targetSlot != IpcSignalListener::SLOT_VOID) {
wire.retname = this->signal.parameterNames().value(0);
if (this->targetSlot == IpcSignalListener::SLOT_STRING) wire.rettype = "string";
else if (this->targetSlot == IpcSignalListener::SLOT_INT) wire.rettype = "int";
else if (this->targetSlot == IpcSignalListener::SLOT_BOOL) wire.rettype = "bool";
else if (this->targetSlot == IpcSignalListener::SLOT_REAL) wire.rettype = "real";
else if (this->targetSlot == IpcSignalListener::SLOT_COLOR) wire.rettype = "color";
}
return wire;
}
// NOLINTBEGIN (cppcoreguidelines-interfaces-global-init)
// clang-format off
const int IpcSignalListener::SLOT_VOID = IpcSignalListener::staticMetaObject.indexOfSlot("invokeVoid()");
const int IpcSignalListener::SLOT_STRING = IpcSignalListener::staticMetaObject.indexOfSlot("invokeString(QString)");
const int IpcSignalListener::SLOT_INT = IpcSignalListener::staticMetaObject.indexOfSlot("invokeInt(int)");
const int IpcSignalListener::SLOT_BOOL = IpcSignalListener::staticMetaObject.indexOfSlot("invokeBool(bool)");
const int IpcSignalListener::SLOT_REAL = IpcSignalListener::staticMetaObject.indexOfSlot("invokeReal(double)");
const int IpcSignalListener::SLOT_COLOR = IpcSignalListener::staticMetaObject.indexOfSlot("invokeColor(QColor)");
// clang-format on
// NOLINTEND
bool IpcSignal::resolve(QString& error) {
if (this->signal.parameterCount() > 1) {
error = "Due to technical limitations, IPC signals can have at most one argument.";
return false;
}
auto slot = IpcSignalListener::SLOT_VOID;
if (this->signal.parameterCount() == 1) {
auto paramType = this->signal.parameterType(0);
if (paramType == QMetaType::QString) slot = IpcSignalListener::SLOT_STRING;
else if (paramType == QMetaType::Int) slot = IpcSignalListener::SLOT_INT;
else if (paramType == QMetaType::Bool) slot = IpcSignalListener::SLOT_BOOL;
else if (paramType == QMetaType::Double) slot = IpcSignalListener::SLOT_REAL;
else if (paramType == QMetaType::QColor) slot = IpcSignalListener::SLOT_COLOR;
else {
error = QString("Type of argument (%2: %3) cannot be used across IPC.")
.arg(this->signal.parameterNames().value(0))
.arg(QMetaType(paramType).name());
return false;
}
}
this->targetSlot = slot;
return true;
}
void IpcSignal::connectListener(IpcHandler* handler) {
if (this->targetSlot == -1) {
qFatal() << "Tried to connect unresolved IPC signal";
}
this->listener = std::make_shared<IpcSignalListener>(this->signal.name());
QMetaObject::connect(handler, this->signal.methodIndex(), this->listener.get(), this->targetSlot);
QObject::connect(
this->listener.get(),
&IpcSignalListener::triggered,
handler,
&IpcHandler::onSignalTriggered
);
}
IpcCallStorage::IpcCallStorage(const IpcFunction& function): returnSlot(function.returnType) {
for (const auto& arg: function.argumentTypes) {
this->argumentSlots.emplace_back(arg);
@@ -243,28 +172,16 @@ void IpcHandler::onPostReload() {
// which should handle inheritance on the qml side.
for (auto i = smeta.methodCount(); i != meta->methodCount(); i++) {
const auto& method = meta->method(i);
if (method.methodType() == QMetaMethod::Slot) {
auto ipcFunc = IpcFunction(method);
QString error;
if (method.methodType() != QMetaMethod::Slot) continue;
if (!ipcFunc.resolve(error)) {
qmlWarning(this).nospace().noquote()
<< "Error parsing function \"" << method.name() << "\": " << error;
} else {
this->functionMap.insert(method.name(), ipcFunc);
}
} else if (method.methodType() == QMetaMethod::Signal) {
qmlDebug(this) << "Signal detected: " << method.name();
auto ipcSig = IpcSignal(method);
QString error;
auto ipcFunc = IpcFunction(method);
QString error;
if (!ipcSig.resolve(error)) {
qmlWarning(this).nospace().noquote()
<< "Error parsing signal \"" << method.name() << "\": " << error;
} else {
ipcSig.connectListener(this);
this->signalMap.emplace(method.name(), std::move(ipcSig));
}
if (!ipcFunc.resolve(error)) {
qmlWarning(this).nospace().noquote()
<< "Error parsing function \"" << method.name() << "\": " << error;
} else {
this->functionMap.insert(method.name(), ipcFunc);
}
}
@@ -305,11 +222,6 @@ IpcHandlerRegistry* IpcHandlerRegistry::forGeneration(EngineGeneration* generati
return dynamic_cast<IpcHandlerRegistry*>(ext);
}
void IpcHandler::onSignalTriggered(const QString& signal, const QString& value) const {
emit IpcSignalRemoteListener::instance()
-> triggered(this->registeredState.target, signal, value);
}
void IpcHandler::updateRegistration(bool destroying) {
if (!this->complete) return;
@@ -412,10 +324,6 @@ WireTargetDefinition IpcHandler::wireDef() const {
wire.properties += prop.wireDef();
}
for (const auto& sig: this->signalMap.values()) {
wire.signalFunctions += sig.wireDef();
}
return wire;
}
@@ -460,13 +368,6 @@ IpcProperty* IpcHandler::findProperty(const QString& name) {
else return &*itr;
}
IpcSignal* IpcHandler::findSignal(const QString& name) {
auto itr = this->signalMap.find(name);
if (itr == this->signalMap.end()) return nullptr;
else return &*itr;
}
IpcHandler* IpcHandlerRegistry::findHandler(const QString& target) {
return this->handlers.value(target);
}
@@ -481,9 +382,4 @@ QVector<WireTargetDefinition> IpcHandlerRegistry::wireTargets() const {
return wire;
}
IpcSignalRemoteListener* IpcSignalRemoteListener::instance() {
static auto* instance = new IpcSignalRemoteListener();
return instance;
}
} // namespace qs::io::ipc
+3 -79
View File
@@ -1,10 +1,8 @@
#pragma once
#include <cstddef>
#include <memory>
#include <vector>
#include <qcolor.h>
#include <qcontainerfwd.h>
#include <qdebug.h>
#include <qhash.h>
@@ -69,54 +67,6 @@ public:
const IpcType* type = nullptr;
};
class IpcSignalListener: public QObject {
Q_OBJECT;
public:
IpcSignalListener(QString signal): signal(std::move(signal)) {}
static const int SLOT_VOID;
static const int SLOT_STRING;
static const int SLOT_INT;
static const int SLOT_BOOL;
static const int SLOT_REAL;
static const int SLOT_COLOR;
signals:
void triggered(const QString& signal, const QString& value);
private slots:
void invokeVoid() { this->triggered(this->signal, "void"); }
void invokeString(const QString& value) { this->triggered(this->signal, value); }
void invokeInt(int value) { this->triggered(this->signal, QString::number(value)); }
void invokeBool(bool value) { this->triggered(this->signal, value ? "true" : "false"); }
void invokeReal(double value) { this->triggered(this->signal, QString::number(value)); }
void invokeColor(QColor value) { this->triggered(this->signal, value.name(QColor::HexArgb)); }
private:
QString signal;
};
class IpcHandler;
class IpcSignal {
public:
explicit IpcSignal(QMetaMethod signal): signal(signal) {}
bool resolve(QString& error);
[[nodiscard]] WireSignalDefinition wireDef() const;
QMetaMethod signal;
int targetSlot = -1;
void connectListener(IpcHandler* handler);
private:
void connectListener(QObject* handler, IpcSignalListener* listener) const;
std::shared_ptr<IpcSignalListener> listener;
};
class IpcHandlerRegistry;
///! Handler for IPC message calls.
@@ -150,11 +100,6 @@ class IpcHandlerRegistry;
/// - `real` will be converted to a string and returned.
/// - `color` will be converted to a hex string in the form `#AARRGGBB` and returned.
///
/// #### Signals
/// IPC handler signals can be observed remotely using `qs ipc wait` (one call)
/// and `qs ipc listen` (many calls). IPC signals may have zero or one argument, where
/// the argument is one of the types listed above, or no arguments for void.
///
/// #### Example
/// The following example creates ipc functions to control and retrieve the appearance
/// of a Rectangle.
@@ -174,18 +119,10 @@ class IpcHandlerRegistry;
///
/// function setColor(color: color): void { rect.color = color; }
/// function getColor(): color { return rect.color; }
///
/// function setAngle(angle: real): void { rect.rotation = angle; }
/// function getAngle(): real { return rect.rotation; }
///
/// function setRadius(radius: int): void {
/// rect.radius = radius;
/// this.radiusChanged(radius);
/// }
///
/// function setRadius(radius: int): void { rect.radius = radius; }
/// function getRadius(): int { return rect.radius; }
///
/// signal radiusChanged(newRadius: int);
/// }
/// }
/// ```
@@ -199,7 +136,6 @@ class IpcHandlerRegistry;
/// function getAngle(): real
/// function setRadius(radius: int): void
/// function getRadius(): int
/// signal radiusChanged(newRadius: int)
/// ```
///
/// and then invoked using `qs ipc call`.
@@ -243,15 +179,14 @@ public:
QString listMembers(qsizetype indent);
[[nodiscard]] IpcFunction* findFunction(const QString& name);
[[nodiscard]] IpcProperty* findProperty(const QString& name);
[[nodiscard]] IpcSignal* findSignal(const QString& name);
[[nodiscard]] WireTargetDefinition wireDef() const;
signals:
void enabledChanged();
void targetChanged();
public slots:
void onSignalTriggered(const QString& signal, const QString& value) const;
private slots:
//void handleIpcPropertyChange();
private:
void updateRegistration(bool destroying = false);
@@ -269,7 +204,6 @@ private:
QHash<QString, IpcFunction> functionMap;
QHash<QString, IpcProperty> propertyMap;
QHash<QString, IpcSignal> signalMap;
friend class IpcHandlerRegistry;
};
@@ -293,14 +227,4 @@ private:
QHash<QString, QVector<IpcHandler*>> knownHandlers;
};
class IpcSignalRemoteListener: public QObject {
Q_OBJECT;
public:
static IpcSignalRemoteListener* instance();
signals:
void triggered(const QString& target, const QString& signal, const QString& value);
};
} // namespace qs::io::ipc
+1 -1
View File
@@ -102,7 +102,7 @@ class Process: public PostReloadHook {
/// If the process is already running changing this property will affect the next
/// started process. If the property has been changed after starting a process it will
/// return the new value, not the one for the currently running process.
Q_PROPERTY(QVariantHash environment READ environment WRITE setEnvironment NOTIFY environmentChanged);
Q_PROPERTY(QHash<QString, QVariant> environment READ environment WRITE setEnvironment NOTIFY environmentChanged);
/// If the process's environment should be cleared prior to applying @@environment.
/// Defaults to false.
///
+1 -1
View File
@@ -13,7 +13,7 @@ namespace qs::io::process {
class ProcessContext {
Q_PROPERTY(QList<QString> command MEMBER command WRITE setCommand);
Q_PROPERTY(QVariantHash environment MEMBER environment WRITE setEnvironment);
Q_PROPERTY(QHash<QString, QVariant> environment MEMBER environment WRITE setEnvironment);
Q_PROPERTY(bool clearEnvironment MEMBER clearEnvironment WRITE setClearEnvironment);
Q_PROPERTY(QString workingDirectory MEMBER workingDirectory WRITE setWorkingDirectory);
Q_PROPERTY(bool unbindStdout MEMBER unbindStdout WRITE setUnbindStdout);
+2 -12
View File
@@ -3,7 +3,6 @@
#include <variant>
#include <qbuffer.h>
#include <qcoreapplication.h>
#include <qlocalserver.h>
#include <qlocalsocket.h>
#include <qlogging.h>
@@ -37,8 +36,7 @@ void IpcServer::start() {
auto path = run->filePath("ipc.sock");
new IpcServer(path);
} else {
qCCritical(
logIpc
qCCritical(logIpc
) << "Could not start IPC server as the instance runtime path could not be created.";
}
}
@@ -62,7 +60,6 @@ IpcServerConnection::IpcServerConnection(QLocalSocket* socket, IpcServer* server
void IpcServerConnection::onDisconnected() {
qCInfo(logIpc) << "IPC connection disconnected" << this;
this->deleteLater();
}
void IpcServerConnection::onReadyRead() {
@@ -86,11 +83,6 @@ void IpcServerConnection::onReadyRead() {
);
if (!this->stream.commitTransaction()) return;
// async connections reparent
if (dynamic_cast<IpcServer*>(this->parent()) != nullptr) {
this->deleteLater();
}
}
IpcClient::IpcClient(const QString& path) {
@@ -128,9 +120,7 @@ int IpcClient::connect(const QString& id, const std::function<void(IpcClient& cl
void IpcKillCommand::exec(IpcServerConnection* /*unused*/) {
qInfo() << "Exiting due to IPC request.";
auto* generation = EngineGeneration::currentGeneration();
if (generation) generation->quit();
else QCoreApplication::exit(0);
EngineGeneration::currentGeneration()->quit();
}
} // namespace qs::ipc
-1
View File
@@ -16,7 +16,6 @@ using IpcCommand = std::variant<
IpcKillCommand,
qs::io::ipc::comm::QueryMetadataCommand,
qs::io::ipc::comm::StringCallCommand,
qs::io::ipc::comm::SignalListenCommand,
qs::io::ipc::comm::StringPropReadCommand>;
} // namespace qs::ipc
+28 -43
View File
@@ -1,6 +1,7 @@
#include <algorithm>
#include <array>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <utility>
@@ -12,7 +13,6 @@
#include <qdebug.h>
#include <qdir.h>
#include <qfileinfo.h>
#include <qguiapplication.h>
#include <qjsonarray.h>
#include <qjsondocument.h>
#include <qjsonobject.h>
@@ -25,12 +25,12 @@
#include <qtversion.h>
#include <unistd.h>
#include "../core/debuginfo.hpp"
#include "../core/instanceinfo.hpp"
#include "../core/logging.hpp"
#include "../core/paths.hpp"
#include "../io/ipccomm.hpp"
#include "../ipc/ipc.hpp"
#include "build.hpp"
#include "launch_p.hpp"
namespace qs::launch {
@@ -89,9 +89,9 @@ int locateConfigFile(CommandState& cmd, QString& path) {
}
if (!manifestPath.isEmpty()) {
qWarning()
<< "Config manifests (manifest.conf) are deprecated and will be removed in a future "
"release.";
qWarning(
) << "Config manifests (manifest.conf) are deprecated and will be removed in a future "
"release.";
qWarning() << "Consider using symlinks to a subfolder of quickshell's XDG config dirs.";
auto file = QFile(manifestPath);
@@ -109,7 +109,7 @@ int locateConfigFile(CommandState& cmd, QString& path) {
}
if (split[0].trimmed() == *cmd.config.name) {
path = QDir(QFileInfo(file).absolutePath()).filePath(split[1].trimmed());
path = QDir(QFileInfo(file).canonicalPath()).filePath(split[1].trimmed());
break;
}
}
@@ -129,8 +129,7 @@ int locateConfigFile(CommandState& cmd, QString& path) {
if (path.isEmpty()) {
if (name == "default") {
qCCritical(
logBare
qCCritical(logBare
) << "Could not find \"default\" config directory or shell.qml in any valid config path.";
} else {
qCCritical(logBare) << "Could not find" << name
@@ -140,7 +139,8 @@ int locateConfigFile(CommandState& cmd, QString& path) {
return -1;
}
goto rpath;
path = QFileInfo(path).canonicalFilePath();
return 0;
}
}
@@ -153,8 +153,7 @@ int locateConfigFile(CommandState& cmd, QString& path) {
return -1;
}
rpath:
path = QFileInfo(path).absoluteFilePath();
path = QFileInfo(path).canonicalFilePath();
return 0;
}
@@ -179,8 +178,7 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
}
} else if (!cmd.instance.id->isEmpty()) {
path = basePath->filePath("by-pid");
auto [liveInstances, deadInstances] =
QsPaths::collectInstances(path, cmd.config.anyDisplay ? "" : getDisplayConnection());
auto [liveInstances, deadInstances] = QsPaths::collectInstances(path);
liveInstances.removeIf([&](const InstanceLockInfo& info) {
return !info.instance.instanceId.startsWith(*cmd.instance.id);
@@ -230,8 +228,7 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb
path = QDir(basePath->filePath("by-path")).filePath(pathId);
auto [liveInstances, deadInstances] =
QsPaths::collectInstances(path, cmd.config.anyDisplay ? "" : getDisplayConnection());
auto [liveInstances, deadInstances] = QsPaths::collectInstances(path);
auto instances = liveInstances;
if (instances.isEmpty() && deadFallback) {
@@ -314,10 +311,7 @@ int listInstances(CommandState& cmd) {
path = QDir(basePath->filePath("by-path")).filePath(pathId);
}
auto [liveInstances, deadInstances] = QsPaths::collectInstances(
path,
cmd.config.anyDisplay || cmd.instance.all ? "" : getDisplayConnection()
);
auto [liveInstances, deadInstances] = QsPaths::collectInstances(path);
sortInstances(liveInstances, cmd.config.newest);
@@ -379,7 +373,6 @@ int listInstances(CommandState& cmd) {
<< " Process ID: " << instance.instance.pid << '\n'
<< " Shell ID: " << instance.instance.shellId << '\n'
<< " Config path: " << instance.instance.configPath << '\n'
<< " Display connection: " << instance.instance.display << '\n'
<< " Launch time: " << launchTimeStr
<< (isDead ? "" : " (running for " + runtimeStr + ")") << '\n'
<< (gray ? "\033[0m" : "");
@@ -411,10 +404,6 @@ int ipcCommand(CommandState& cmd) {
return qs::io::ipc::comm::queryMetadata(&client, *cmd.ipc.target, *cmd.ipc.name);
} else if (*cmd.ipc.getprop) {
return qs::io::ipc::comm::getProperty(&client, *cmd.ipc.target, *cmd.ipc.name);
} else if (*cmd.ipc.wait) {
return qs::io::ipc::comm::listenToSignal(&client, *cmd.ipc.target, *cmd.ipc.name, true);
} else if (*cmd.ipc.listen) {
return qs::io::ipc::comm::listenToSignal(&client, *cmd.ipc.target, *cmd.ipc.name, false);
} else {
QVector<QString> arguments;
for (auto& arg: cmd.ipc.arguments) {
@@ -464,7 +453,7 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
QTextStream(stdout) << "\033[31mCOMPATIBILITY WARNING: Quickshell was built against Qt "
<< QT_VERSION_STR << " but the system has updated to Qt " << qVersion()
<< " without rebuilding the package. This is likely to cause crashes, so "
"you must rebuild the quickshell package.\n\033[0m";
"you must rebuild the quickshell package.\n";
return 1;
}
@@ -519,10 +508,20 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
}
if (state.misc.printVersion) {
if (state.log.verbosity == 0) {
qCInfo(logBare).noquote() << "Quickshell" << qs::debuginfo::qsVersion();
} else {
qCInfo(logBare).noquote() << qs::debuginfo::combinedInfo();
qCInfo(logBare).noquote().nospace()
<< "quickshell 0.2.1, revision " << GIT_REVISION << ", distributed by: " << DISTRIBUTOR;
if (state.log.verbosity > 1) {
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) {
return readLogFile(state);
@@ -546,18 +545,4 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
return 0;
}
QString getDisplayConnection() {
auto platform = qEnvironmentVariable("QT_QPA_PLATFORM");
auto wlDisplay = qEnvironmentVariable("WAYLAND_DISPLAY");
auto xDisplay = qEnvironmentVariable("DISPLAY");
if (platform == "wayland" || (platform.isEmpty() && !wlDisplay.isEmpty())) {
return "wayland," + wlDisplay;
} else if (platform == "xcb" || (platform.isEmpty() && !xDisplay.isEmpty())) {
return "x11," + xDisplay;
} else {
return "unk," + QGuiApplication::platformName();
}
}
} // namespace qs::launch
+7 -12
View File
@@ -27,7 +27,7 @@
#include "build.hpp"
#include "launch_p.hpp"
#if CRASH_HANDLER
#if CRASH_REPORTER
#include "../crash/handler.hpp"
#endif
@@ -78,7 +78,6 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
QHash<QString, QString> envOverrides;
QString dataDir;
QString stateDir;
QString cacheDir;
} pragmas;
auto stream = QTextStream(&file);
@@ -110,8 +109,6 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
pragmas.dataDir = pragma.sliced(8).trimmed();
} else if (pragma.startsWith("StateDir ")) {
pragmas.stateDir = pragma.sliced(9).trimmed();
} else if (pragma.startsWith("CacheDir ")) {
pragmas.cacheDir = pragma.sliced(9).trimmed();
} else {
qCritical() << "Unrecognized pragma" << pragma;
return -1;
@@ -134,17 +131,15 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
.shellId = shellId,
.launchTime = qs::Common::LAUNCH_TIME,
.pid = getpid(),
.display = getDisplayConnection(),
};
#if CRASH_HANDLER
if (qEnvironmentVariableIsSet("QS_DISABLE_CRASH_HANDLER")) {
qInfo() << "Crash handling disabled.";
} else {
crash::CrashHandler::init();
#if CRASH_REPORTER
auto crashHandler = crash::CrashHandler();
crashHandler.init();
{
auto* log = LogManager::instance();
crash::CrashHandler::setRelaunchInfo({
crashHandler.setRelaunchInfo({
.instance = InstanceInfo::CURRENT,
.noColor = !log->colorLogs,
.timestamp = log->timestampLogs,
@@ -155,7 +150,7 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
}
#endif
QsPaths::init(shellId, pathId, pragmas.dataDir, pragmas.stateDir, pragmas.cacheDir);
QsPaths::init(shellId, pathId, pragmas.dataDir, pragmas.stateDir);
QsPaths::instance()->linkRunDir();
QsPaths::instance()->linkPathDir();
LogManager::initFs();
-5
View File
@@ -50,7 +50,6 @@ struct CommandState {
QStringOption manifest;
QStringOption name;
bool newest = false;
bool anyDisplay = false;
} config;
struct {
@@ -74,8 +73,6 @@ struct CommandState {
CLI::App* show = nullptr;
CLI::App* call = nullptr;
CLI::App* getprop = nullptr;
CLI::App* wait = nullptr;
CLI::App* listen = nullptr;
bool showOld = false;
QStringOption target;
QStringOption name;
@@ -109,8 +106,6 @@ void exitDaemon(int code);
int parseCommand(int argc, char** argv, CommandState& state);
int runCommand(int argc, char** argv, QCoreApplication* coreApplication);
QString getDisplayConnection();
int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplication);
} // namespace qs::launch
+3 -3
View File
@@ -16,7 +16,7 @@
#include "build.hpp"
#include "launch_p.hpp"
#if CRASH_HANDLER
#if CRASH_REPORTER
#include "../crash/main.hpp"
#endif
@@ -25,7 +25,7 @@ namespace qs::launch {
namespace {
void checkCrashRelaunch(char** argv, QCoreApplication* coreApplication) {
#if CRASH_HANDLER
#if CRASH_REPORTER
auto lastInfoFdStr = qEnvironmentVariable("__QUICKSHELL_CRASH_INFO_FD");
if (!lastInfoFdStr.isEmpty()) {
@@ -104,7 +104,7 @@ void exitDaemon(int code) {
int main(int argc, char** argv) {
QCoreApplication::setApplicationName("quickshell");
#if CRASH_HANDLER
#if CRASH_REPORTER
qsCheckCrash(argc, argv);
#endif
+19 -48
View File
@@ -1,7 +1,6 @@
#include <cstddef>
#include <limits>
#include <memory>
#include <utility>
#include <CLI/App.hpp>
#include <CLI/CLI.hpp> // NOLINT: Need to include this for impls of some CLI11 classes
@@ -17,7 +16,7 @@ int parseCommand(int argc, char** argv, CommandState& state) {
.argv = argv,
};
auto addConfigSelection = [&](CLI::App* cmd, bool filtering = false) {
auto addConfigSelection = [&](CLI::App* cmd, bool withNewestOption = false) {
auto* group =
cmd->add_option_group("Config Selection")
->description(
@@ -44,23 +43,15 @@ int parseCommand(int argc, char** argv, CommandState& state) {
->excludes(path);
group->add_option("-m,--manifest", state.config.manifest)
->description(
"[DEPRECATED] Path to a quickshell manifest.\n"
"If a manifest is specified, configs named by -c will point to its entries.\n"
"Defaults to $XDG_CONFIG_HOME/quickshell/manifest.conf"
)
->description("[DEPRECATED] Path to a quickshell manifest.\n"
"If a manifest is specified, configs named by -c will point to its entries.\n"
"Defaults to $XDG_CONFIG_HOME/quickshell/manifest.conf")
->envname("QS_MANIFEST")
->excludes(path);
if (filtering) {
if (withNewestOption) {
group->add_flag("-n,--newest", state.config.newest)
->description("Operate on the most recently launched instance instead of the oldest");
group->add_flag("--any-display", state.config.anyDisplay)
->description(
"If passed, instances will not be filtered by the display connection they "
"were launched on."
);
}
return group;
@@ -84,11 +75,9 @@ int parseCommand(int argc, char** argv, CommandState& state) {
auto* group = noGroup ? cmd : cmd->add_option_group(noDisplay ? "" : "Logging");
group->add_flag("--no-color", state.log.noColor)
->description(
"Disables colored logging.\n"
"Colored logging can also be disabled by specifying a non empty value "
"for the NO_COLOR environment variable."
);
->description("Disables colored logging.\n"
"Colored logging can also be disabled by specifying a non empty value "
"for the NO_COLOR environment variable.");
group->add_flag("--log-times", state.log.timestamp)
->description("Log timestamps with each message.");
@@ -97,11 +86,9 @@ int parseCommand(int argc, char** argv, CommandState& state) {
->description("Log rules to apply, in the format of QT_LOGGING_RULES.");
group->add_flag("-v,--verbose", [&](size_t count) { state.log.verbosity = count; })
->description(
"Increases log verbosity.\n"
"-v will show INFO level internal logs.\n"
"-vv will show DEBUG level internal logs."
);
->description("Increases log verbosity.\n"
"-v will show INFO level internal logs.\n"
"-vv will show DEBUG level internal logs.");
auto* hgroup = cmd->add_option_group("");
hgroup->add_flag("--no-detailed-logs", state.log.sparse);
@@ -111,11 +98,9 @@ int parseCommand(int argc, char** argv, CommandState& state) {
auto* group = cmd->add_option_group("Instance Selection");
group->add_option("-i,--id", state.instance.id)
->description(
"The instance id to operate on.\n"
"You may also use a substring the id as long as it is unique, "
"for example \"abc\" will select \"abcdefg\"."
);
->description("The instance id to operate on.\n"
"You may also use a substring the id as long as it is unique, "
"for example \"abc\" will select \"abcdefg\".");
group->add_option("--pid", state.instance.pid)
->description("The process id of the instance to operate on.");
@@ -172,11 +157,9 @@ int parseCommand(int argc, char** argv, CommandState& state) {
auto* sub = cli->add_subcommand("list", "List running quickshell instances.");
auto* all = sub->add_flag("-a,--all", state.instance.all)
->description(
"List all instances.\n"
"If unspecified, only instances of"
"the selected config will be listed."
);
->description("List all instances.\n"
"If unspecified, only instances of"
"the selected config will be listed.");
sub->add_flag("-j,--json", state.output.json, "Output the list as a json.");
@@ -227,16 +210,6 @@ int parseCommand(int argc, char** argv, CommandState& state) {
->allow_extra_args();
}
auto signalCmd = [&](std::string cmd, std::string desc) {
auto* scmd = sub->add_subcommand(std::move(cmd), std::move(desc));
scmd->add_option("target", state.ipc.target, "The target to listen on.");
scmd->add_option("signal", state.ipc.name, "The signal to listen for.");
return scmd;
};
state.ipc.wait = signalCmd("wait", "Wait for one IpcHandler signal.");
state.ipc.listen = signalCmd("listen", "Listen for IpcHandler signals.");
{
auto* prop =
sub->add_subcommand("prop", "Manipulate IpcHandler properties.")->require_subcommand();
@@ -262,10 +235,8 @@ int parseCommand(int argc, char** argv, CommandState& state) {
->allow_extra_args();
sub->add_flag("-s,--show", state.ipc.showOld)
->description(
"Print information about a function or target if given, or all available "
"targets if not."
);
->description("Print information about a function or target if given, or all available "
"targets if not.");
auto* instance = addInstanceSelection(sub);
addConfigSelection(sub, true)->excludes(instance);
-24
View File
@@ -1,24 +0,0 @@
add_subdirectory(nm)
qt_add_library(quickshell-network STATIC
network.cpp
device.cpp
wifi.cpp
)
target_include_directories(quickshell-network PRIVATE
${CMAKE_CURRENT_BINARY_DIR}
)
qt_add_qml_module(quickshell-network
URI Quickshell.Networking
VERSION 0.1
DEPENDENCIES QtQml
)
qs_add_module_deps_light(quickshell-network Quickshell)
install_qml_module(quickshell-network)
target_link_libraries(quickshell-network PRIVATE quickshell-network-nm Qt::Qml Qt::DBus)
qs_add_link_dependencies(quickshell-network quickshell-dbus)
target_link_libraries(quickshell PRIVATE quickshell-networkplugin)
qs_module_pch(quickshell-network SET dbus)
-82
View File
@@ -1,82 +0,0 @@
#include "device.hpp"
#include <qdebug.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qobject.h>
#include <qstring.h>
#include <qtmetamacros.h>
#include "../core/logcat.hpp"
namespace qs::network {
namespace {
QS_LOGGING_CATEGORY(logNetworkDevice, "quickshell.network.device", QtWarningMsg);
} // namespace
QString DeviceConnectionState::toString(DeviceConnectionState::Enum state) {
switch (state) {
case Unknown: return QStringLiteral("Unknown");
case Connecting: return QStringLiteral("Connecting");
case Connected: return QStringLiteral("Connected");
case Disconnecting: return QStringLiteral("Disconnecting");
case Disconnected: return QStringLiteral("Disconnected");
default: return QStringLiteral("Unknown");
}
}
QString DeviceType::toString(DeviceType::Enum type) {
switch (type) {
case None: return QStringLiteral("None");
case Wifi: return QStringLiteral("Wifi");
default: return QStringLiteral("Unknown");
}
}
QString NMDeviceState::toString(NMDeviceState::Enum state) {
switch (state) {
case Unknown: return QStringLiteral("Unknown");
case Unmanaged: return QStringLiteral("Not managed by NetworkManager");
case Unavailable: return QStringLiteral("Unavailable");
case Disconnected: return QStringLiteral("Disconnected");
case Prepare: return QStringLiteral("Preparing to connect");
case Config: return QStringLiteral("Connecting to a network");
case NeedAuth: return QStringLiteral("Waiting for authentication");
case IPConfig: return QStringLiteral("Requesting IPv4 and/or IPv6 addresses from the network");
case IPCheck:
return QStringLiteral("Checking if further action is required for the requested connection");
case Secondaries:
return QStringLiteral("Waiting for a required secondary connection to activate");
case Activated: return QStringLiteral("Connected");
case Deactivating: return QStringLiteral("Disconnecting");
case Failed: return QStringLiteral("Failed to connect");
default: return QStringLiteral("Unknown");
};
}
NetworkDevice::NetworkDevice(DeviceType::Enum type, QObject* parent): QObject(parent), mType(type) {
this->bindableConnected().setBinding([this]() {
return this->bState == DeviceConnectionState::Connected;
});
};
void NetworkDevice::setAutoconnect(bool autoconnect) {
if (this->bAutoconnect == autoconnect) return;
emit this->requestSetAutoconnect(autoconnect);
}
void NetworkDevice::disconnect() {
if (this->bState == DeviceConnectionState::Disconnected) {
qCCritical(logNetworkDevice) << "Device" << this << "is already disconnected";
return;
}
if (this->bState == DeviceConnectionState::Disconnecting) {
qCCritical(logNetworkDevice) << "Device" << this << "is already disconnecting";
return;
}
qCDebug(logNetworkDevice) << "Disconnecting from device" << this;
this->requestDisconnect();
}
} // namespace qs::network
-133
View File
@@ -1,133 +0,0 @@
#pragma once
#include <qobject.h>
#include <qproperty.h>
#include <qqmlintegration.h>
#include <qtmetamacros.h>
#include <qtypes.h>
namespace qs::network {
///! Connection state of a NetworkDevice.
class DeviceConnectionState: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
Unknown = 0,
Connecting = 1,
Connected = 2,
Disconnecting = 3,
Disconnected = 4,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(DeviceConnectionState::Enum state);
};
///! Type of network device.
class DeviceType: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
None = 0,
Wifi = 1,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(DeviceType::Enum type);
};
///! NetworkManager-specific device state.
/// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceState.
class NMDeviceState: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
Unknown = 0,
Unmanaged = 10,
Unavailable = 20,
Disconnected = 30,
Prepare = 40,
Config = 50,
NeedAuth = 60,
IPConfig = 70,
IPCheck = 80,
Secondaries = 90,
Activated = 100,
Deactivating = 110,
Failed = 120,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(NMDeviceState::Enum state);
};
///! A network device.
/// When @@type is `Wifi`, the device is a @@WifiDevice, which can be used to scan for and connect to access points.
class NetworkDevice: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_UNCREATABLE("Devices can only be acquired through Network");
// clang-format off
/// The device type.
Q_PROPERTY(DeviceType::Enum type READ type CONSTANT);
/// The name of the device's control interface.
Q_PROPERTY(QString name READ name NOTIFY nameChanged BINDABLE bindableName);
/// The hardware address of the device in the XX:XX:XX:XX:XX:XX format.
Q_PROPERTY(QString address READ default NOTIFY addressChanged BINDABLE bindableAddress);
/// True if the device is connected.
Q_PROPERTY(bool connected READ default NOTIFY connectedChanged BINDABLE bindableConnected);
/// Connection state of the device.
Q_PROPERTY(qs::network::DeviceConnectionState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
/// A more specific device state when the backend is NetworkManager.
Q_PROPERTY(qs::network::NMDeviceState::Enum nmState READ default NOTIFY nmStateChanged BINDABLE bindableNmState);
/// True if the device is allowed to autoconnect.
Q_PROPERTY(bool autoconnect READ autoconnect WRITE setAutoconnect NOTIFY autoconnectChanged);
// clang-format on
public:
explicit NetworkDevice(DeviceType::Enum type, QObject* parent = nullptr);
/// Disconnects the device and prevents it from automatically activating further connections.
Q_INVOKABLE void disconnect();
[[nodiscard]] DeviceType::Enum type() const { return this->mType; };
QBindable<QString> bindableName() { return &this->bName; };
[[nodiscard]] QString name() const { return this->bName; };
QBindable<QString> bindableAddress() { return &this->bAddress; };
QBindable<bool> bindableConnected() { return &this->bConnected; };
QBindable<DeviceConnectionState::Enum> bindableState() { return &this->bState; };
QBindable<NMDeviceState::Enum> bindableNmState() { return &this->bNmState; };
[[nodiscard]] bool autoconnect() const { return this->bAutoconnect; };
QBindable<bool> bindableAutoconnect() { return &this->bAutoconnect; };
void setAutoconnect(bool autoconnect);
signals:
void requestDisconnect();
void requestSetAutoconnect(bool autoconnect);
void nameChanged();
void addressChanged();
void connectedChanged();
void stateChanged();
void nmStateChanged();
void autoconnectChanged();
private:
DeviceType::Enum mType;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, QString, bName, &NetworkDevice::nameChanged);
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, QString, bAddress, &NetworkDevice::addressChanged);
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, bool, bConnected, &NetworkDevice::connectedChanged);
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, DeviceConnectionState::Enum, bState, &NetworkDevice::stateChanged);
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, NMDeviceState::Enum, bNmState, &NetworkDevice::nmStateChanged);
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, bool, bAutoconnect, &NetworkDevice::autoconnectChanged);
// clang-format on
};
} // namespace qs::network
-13
View File
@@ -1,13 +0,0 @@
name = "Quickshell.Networking"
description = "Network API"
headers = [
"network.hpp",
"device.hpp",
"wifi.hpp",
]
-----
This module exposes Network management APIs provided by a supported network backend.
For now, the only backend available is the NetworkManager DBus interface.
Both DBus and NetworkManager must be running to use it.
See the @@Quickshell.Networking.Networking singleton.
-65
View File
@@ -1,65 +0,0 @@
#include "network.hpp"
#include <utility>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qobject.h>
#include <qstring.h>
#include <qtmetamacros.h>
#include "../core/logcat.hpp"
#include "device.hpp"
#include "nm/backend.hpp"
namespace qs::network {
namespace {
QS_LOGGING_CATEGORY(logNetwork, "quickshell.network", QtWarningMsg);
} // namespace
QString NetworkState::toString(NetworkState::Enum state) {
switch (state) {
case NetworkState::Connecting: return QStringLiteral("Connecting");
case NetworkState::Connected: return QStringLiteral("Connected");
case NetworkState::Disconnecting: return QStringLiteral("Disconnecting");
case NetworkState::Disconnected: return QStringLiteral("Disconnected");
default: return QStringLiteral("Unknown");
}
}
Networking::Networking(QObject* parent): QObject(parent) {
// Try to create the NetworkManager backend and bind to it.
auto* nm = new NetworkManager(this);
if (nm->isAvailable()) {
QObject::connect(nm, &NetworkManager::deviceAdded, this, &Networking::deviceAdded);
QObject::connect(nm, &NetworkManager::deviceRemoved, this, &Networking::deviceRemoved);
QObject::connect(this, &Networking::requestSetWifiEnabled, nm, &NetworkManager::setWifiEnabled);
this->bindableWifiEnabled().setBinding([nm]() { return nm->wifiEnabled(); });
this->bindableWifiHardwareEnabled().setBinding([nm]() { return nm->wifiHardwareEnabled(); });
this->mBackend = nm;
this->mBackendType = NetworkBackendType::NetworkManager;
return;
} else {
delete nm;
}
qCCritical(logNetwork) << "Network will not work. Could not find an available backend.";
}
void Networking::deviceAdded(NetworkDevice* dev) { this->mDevices.insertObject(dev); }
void Networking::deviceRemoved(NetworkDevice* dev) { this->mDevices.removeObject(dev); }
void Networking::setWifiEnabled(bool enabled) {
if (this->bWifiEnabled == enabled) return;
emit this->requestSetWifiEnabled(enabled);
}
Network::Network(QString name, QObject* parent): QObject(parent), mName(std::move(name)) {
this->bStateChanging.setBinding([this] {
auto state = this->bState.value();
return state == NetworkState::Connecting || state == NetworkState::Disconnecting;
});
};
} // namespace qs::network
-142
View File
@@ -1,142 +0,0 @@
#pragma once
#include <qobject.h>
#include <qproperty.h>
#include <qqmlintegration.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../core/model.hpp"
#include "device.hpp"
namespace qs::network {
///! The connection state of a Network.
class NetworkState: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
Unknown = 0,
Connecting = 1,
Connected = 2,
Disconnecting = 3,
Disconnected = 4,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(NetworkState::Enum state);
};
///! The backend supplying the Network service.
class NetworkBackendType: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
None = 0,
NetworkManager = 1,
};
Q_ENUM(Enum);
};
class NetworkBackend: public QObject {
Q_OBJECT;
public:
[[nodiscard]] virtual bool isAvailable() const = 0;
protected:
explicit NetworkBackend(QObject* parent = nullptr): QObject(parent) {};
};
///! The Network service.
/// An interface to a network backend (currently only NetworkManager),
/// which can be used to view, configure, and connect to various networks.
class Networking: public QObject {
Q_OBJECT;
QML_SINGLETON;
QML_ELEMENT;
// clang-format off
/// A list of all network devices.
QSDOC_TYPE_OVERRIDE(ObjectModel<qs::network::NetworkDevice>*);
Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT);
/// The backend being used to power the Network service.
Q_PROPERTY(qs::network::NetworkBackendType::Enum backend READ backend CONSTANT);
/// Switch for the rfkill software block of all wireless devices.
Q_PROPERTY(bool wifiEnabled READ wifiEnabled WRITE setWifiEnabled NOTIFY wifiEnabledChanged);
/// State of the rfkill hardware block of all wireless devices.
Q_PROPERTY(bool wifiHardwareEnabled READ default NOTIFY wifiHardwareEnabledChanged BINDABLE bindableWifiHardwareEnabled);
// clang-format on
public:
explicit Networking(QObject* parent = nullptr);
[[nodiscard]] ObjectModel<NetworkDevice>* devices() { return &this->mDevices; };
[[nodiscard]] NetworkBackendType::Enum backend() const { return this->mBackendType; };
QBindable<bool> bindableWifiEnabled() { return &this->bWifiEnabled; };
[[nodiscard]] bool wifiEnabled() const { return this->bWifiEnabled; };
void setWifiEnabled(bool enabled);
QBindable<bool> bindableWifiHardwareEnabled() { return &this->bWifiHardwareEnabled; };
signals:
void requestSetWifiEnabled(bool enabled);
void wifiEnabledChanged();
void wifiHardwareEnabledChanged();
private slots:
void deviceAdded(NetworkDevice* dev);
void deviceRemoved(NetworkDevice* dev);
private:
ObjectModel<NetworkDevice> mDevices {this};
NetworkBackend* mBackend = nullptr;
NetworkBackendType::Enum mBackendType = NetworkBackendType::None;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bWifiEnabled, &Networking::wifiEnabledChanged);
Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bWifiHardwareEnabled, &Networking::wifiHardwareEnabledChanged);
// clang-format on
};
///! A network.
class Network: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_UNCREATABLE("BaseNetwork can only be aqcuired through network devices");
// clang-format off
/// The name of the network.
Q_PROPERTY(QString name READ name CONSTANT);
/// True if the network is connected.
Q_PROPERTY(bool connected READ default NOTIFY connectedChanged BINDABLE bindableConnected);
/// The connectivity state of the network.
Q_PROPERTY(NetworkState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
/// If the network is currently connecting or disconnecting. Shorthand for checking @@state.
Q_PROPERTY(bool stateChanging READ default NOTIFY stateChangingChanged BINDABLE bindableStateChanging);
// clang-format on
public:
explicit Network(QString name, QObject* parent = nullptr);
[[nodiscard]] QString name() const { return this->mName; };
QBindable<bool> bindableConnected() { return &this->bConnected; }
QBindable<NetworkState::Enum> bindableState() { return &this->bState; }
QBindable<bool> bindableStateChanging() { return &this->bStateChanging; }
signals:
void connectedChanged();
void stateChanged();
void stateChangingChanged();
protected:
QString mName;
Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bConnected, &Network::connectedChanged);
Q_OBJECT_BINDABLE_PROPERTY(Network, NetworkState::Enum, bState, &Network::stateChanged);
Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bStateChanging, &Network::stateChangingChanged);
};
} // namespace qs::network
-79
View File
@@ -1,79 +0,0 @@
set_source_files_properties(org.freedesktop.NetworkManager.xml PROPERTIES
CLASSNAME DBusNetworkManagerProxy
NO_NAMESPACE TRUE
INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/dbus_types.hpp
)
qt_add_dbus_interface(NM_DBUS_INTERFACES
org.freedesktop.NetworkManager.xml
dbus_nm_backend
)
set_source_files_properties(org.freedesktop.NetworkManager.Device.xml PROPERTIES
CLASSNAME DBusNMDeviceProxy
NO_NAMESPACE TRUE
)
qt_add_dbus_interface(NM_DBUS_INTERFACES
org.freedesktop.NetworkManager.Device.xml
dbus_nm_device
)
set_source_files_properties(org.freedesktop.NetworkManager.Device.Wireless.xml PROPERTIES
CLASSNAME DBusNMWirelessProxy
NO_NAMESPACE TRUE
)
qt_add_dbus_interface(NM_DBUS_INTERFACES
org.freedesktop.NetworkManager.Device.Wireless.xml
dbus_nm_wireless
)
set_source_files_properties(org.freedesktop.NetworkManager.AccessPoint.xml PROPERTIES
CLASSNAME DBusNMAccessPointProxy
NO_NAMESPACE TRUE
)
qt_add_dbus_interface(NM_DBUS_INTERFACES
org.freedesktop.NetworkManager.AccessPoint.xml
dbus_nm_accesspoint
)
set_source_files_properties(org.freedesktop.NetworkManager.Settings.Connection.xml PROPERTIES
CLASSNAME DBusNMConnectionSettingsProxy
NO_NAMESPACE TRUE
INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/dbus_types.hpp
)
qt_add_dbus_interface(NM_DBUS_INTERFACES
org.freedesktop.NetworkManager.Settings.Connection.xml
dbus_nm_connection_settings
)
set_source_files_properties(org.freedesktop.NetworkManager.Connection.Active.xml PROPERTIES
CLASSNAME DBusNMActiveConnectionProxy
NO_NAMESPACE TRUE
)
qt_add_dbus_interface(NM_DBUS_INTERFACES
org.freedesktop.NetworkManager.Connection.Active.xml
dbus_nm_active_connection
)
qt_add_library(quickshell-network-nm STATIC
backend.cpp
device.cpp
connection.cpp
accesspoint.cpp
wireless.cpp
utils.cpp
enums.hpp
${NM_DBUS_INTERFACES}
)
target_include_directories(quickshell-network-nm PUBLIC
${CMAKE_CURRENT_BINARY_DIR}
)
target_link_libraries(quickshell-network-nm PRIVATE Qt::Qml Qt::DBus)
qs_add_link_dependencies(quickshell-network-nm quickshell-dbus)
-71
View File
@@ -1,71 +0,0 @@
#include "accesspoint.hpp"
#include <qdbusconnection.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qstring.h>
#include <qtypes.h>
#include "../../core/logcat.hpp"
#include "../../dbus/properties.hpp"
#include "dbus_nm_accesspoint.h"
#include "enums.hpp"
namespace qs::network {
using namespace qs::dbus;
namespace {
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
}
NMAccessPoint::NMAccessPoint(const QString& path, QObject* parent): QObject(parent) {
this->proxy = new DBusNMAccessPointProxy(
"org.freedesktop.NetworkManager",
path,
QDBusConnection::systemBus(),
this
);
if (!this->proxy->isValid()) {
qCWarning(logNetworkManager) << "Cannot create DBus interface for access point at" << path;
return;
}
QObject::connect(
&this->accessPointProperties,
&DBusPropertyGroup::getAllFinished,
this,
&NMAccessPoint::loaded,
Qt::SingleShotConnection
);
this->accessPointProperties.setInterface(this->proxy);
this->accessPointProperties.updateAllViaGetAll();
}
bool NMAccessPoint::isValid() const { return this->proxy && this->proxy->isValid(); }
QString NMAccessPoint::address() const { return this->proxy ? this->proxy->service() : QString(); }
QString NMAccessPoint::path() const { return this->proxy ? this->proxy->path() : QString(); }
} // namespace qs::network
namespace qs::dbus {
DBusResult<qs::network::NM80211ApFlags::Enum>
DBusDataTransform<qs::network::NM80211ApFlags::Enum>::fromWire(quint32 wire) {
return DBusResult(static_cast<qs::network::NM80211ApFlags::Enum>(wire));
}
DBusResult<qs::network::NM80211ApSecurityFlags::Enum>
DBusDataTransform<qs::network::NM80211ApSecurityFlags::Enum>::fromWire(quint32 wire) {
return DBusResult(static_cast<qs::network::NM80211ApSecurityFlags::Enum>(wire));
}
DBusResult<qs::network::NM80211Mode::Enum>
DBusDataTransform<qs::network::NM80211Mode::Enum>::fromWire(quint32 wire) {
return DBusResult(static_cast<qs::network::NM80211Mode::Enum>(wire));
}
} // namespace qs::dbus
-92
View File
@@ -1,92 +0,0 @@
#pragma once
#include <qdbusextratypes.h>
#include <qobject.h>
#include <qproperty.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../../dbus/properties.hpp"
#include "../wifi.hpp"
#include "dbus_nm_accesspoint.h"
#include "enums.hpp"
namespace qs::dbus {
template <>
struct DBusDataTransform<qs::network::NM80211ApFlags::Enum> {
using Wire = quint32;
using Data = qs::network::NM80211ApFlags::Enum;
static DBusResult<Data> fromWire(Wire wire);
};
template <>
struct DBusDataTransform<qs::network::NM80211ApSecurityFlags::Enum> {
using Wire = quint32;
using Data = qs::network::NM80211ApSecurityFlags::Enum;
static DBusResult<Data> fromWire(Wire wire);
};
template <>
struct DBusDataTransform<qs::network::NM80211Mode::Enum> {
using Wire = quint32;
using Data = qs::network::NM80211Mode::Enum;
static DBusResult<Data> fromWire(Wire wire);
};
} // namespace qs::dbus
namespace qs::network {
/// Proxy of a /org/freedesktop/NetworkManager/AccessPoint/* object.
class NMAccessPoint: public QObject {
Q_OBJECT;
public:
explicit NMAccessPoint(const QString& path, QObject* parent = nullptr);
[[nodiscard]] bool isValid() const;
[[nodiscard]] QString path() const;
[[nodiscard]] QString address() const;
[[nodiscard]] QByteArray ssid() const { return this->bSsid; };
[[nodiscard]] quint8 signalStrength() const { return this->bSignalStrength; };
[[nodiscard]] NM80211ApFlags::Enum flags() const { return this->bFlags; };
[[nodiscard]] NM80211ApSecurityFlags::Enum wpaFlags() const { return this->bWpaFlags; };
[[nodiscard]] NM80211ApSecurityFlags::Enum rsnFlags() const { return this->bRsnFlags; };
[[nodiscard]] NM80211Mode::Enum mode() const { return this->bMode; };
[[nodiscard]] QBindable<WifiSecurityType::Enum> bindableSecurity() { return &this->bSecurity; };
[[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; };
signals:
void loaded();
void ssidChanged(const QByteArray& ssid);
void signalStrengthChanged(quint8 signal);
void flagsChanged(NM80211ApFlags::Enum flags);
void wpaFlagsChanged(NM80211ApSecurityFlags::Enum wpaFlags);
void rsnFlagsChanged(NM80211ApSecurityFlags::Enum rsnFlags);
void modeChanged(NM80211Mode::Enum mode);
void securityChanged(WifiSecurityType::Enum security);
private:
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, QByteArray, bSsid, &NMAccessPoint::ssidChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, quint8, bSignalStrength, &NMAccessPoint::signalStrengthChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, NM80211ApFlags::Enum, bFlags, &NMAccessPoint::flagsChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, NM80211ApSecurityFlags::Enum, bWpaFlags, &NMAccessPoint::wpaFlagsChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, NM80211ApSecurityFlags::Enum, bRsnFlags, &NMAccessPoint::rsnFlagsChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, NM80211Mode::Enum, bMode, &NMAccessPoint::modeChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, WifiSecurityType::Enum, bSecurity, &NMAccessPoint::securityChanged);
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMAccessPointAdapter, accessPointProperties);
QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pSsid, bSsid, accessPointProperties, "Ssid");
QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pSignalStrength, bSignalStrength, accessPointProperties, "Strength");
QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pFlags, bFlags, accessPointProperties, "Flags");
QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pWpaFlags, bWpaFlags, accessPointProperties, "WpaFlags");
QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pRsnFlags, bRsnFlags, accessPointProperties, "RsnFlags");
QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pMode, bMode, accessPointProperties, "Mode");
// clang-format on
DBusNMAccessPointProxy* proxy = nullptr;
};
} // namespace qs::network
-270
View File
@@ -1,270 +0,0 @@
#include "backend.hpp"
#include <qdbusconnection.h>
#include <qdbusextratypes.h>
#include <qdbusmetatype.h>
#include <qdbuspendingcall.h>
#include <qdbuspendingreply.h>
#include <qlist.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qobject.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../../core/logcat.hpp"
#include "../../dbus/properties.hpp"
#include "../device.hpp"
#include "../network.hpp"
#include "../wifi.hpp"
#include "dbus_nm_backend.h"
#include "dbus_nm_device.h"
#include "dbus_types.hpp"
#include "device.hpp"
#include "enums.hpp"
#include "wireless.hpp"
namespace qs::network {
namespace {
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
}
NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) {
qDBusRegisterMetaType<ConnectionSettingsMap>();
auto bus = QDBusConnection::systemBus();
if (!bus.isConnected()) {
qCWarning(
logNetworkManager
) << "Could not connect to DBus. NetworkManager backend will not work.";
return;
}
this->proxy = new DBusNetworkManagerProxy(
"org.freedesktop.NetworkManager",
"/org/freedesktop/NetworkManager",
bus,
this
);
if (!this->proxy->isValid()) {
qCDebug(
logNetworkManager
) << "NetworkManager is not currently running. This network backend will not work";
} else {
this->init();
}
}
void NetworkManager::init() {
// clang-format off
QObject::connect(this->proxy, &DBusNetworkManagerProxy::DeviceAdded, this, &NetworkManager::onDevicePathAdded);
QObject::connect(this->proxy, &DBusNetworkManagerProxy::DeviceRemoved, this, &NetworkManager::onDevicePathRemoved);
// clang-format on
this->dbusProperties.setInterface(this->proxy);
this->dbusProperties.updateAllViaGetAll();
this->registerDevices();
}
void NetworkManager::registerDevices() {
auto pending = this->proxy->GetAllDevices();
auto* call = new QDBusPendingCallWatcher(pending, this);
auto responseCallback = [this](QDBusPendingCallWatcher* call) {
const QDBusPendingReply<QList<QDBusObjectPath>> reply = *call;
if (reply.isError()) {
qCWarning(logNetworkManager) << "Failed to get devices: " << reply.error().message();
} else {
for (const QDBusObjectPath& devicePath: reply.value()) {
this->registerDevice(devicePath.path());
}
}
delete call;
};
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
}
void NetworkManager::registerDevice(const QString& path) {
if (this->mDevices.contains(path)) {
qCDebug(logNetworkManager) << "Skipping duplicate registration of device" << path;
return;
}
auto* temp = new DBusNMDeviceProxy(
"org.freedesktop.NetworkManager",
path,
QDBusConnection::systemBus(),
this
);
auto callback = [this, path, temp](uint value, const QDBusError& error) {
if (error.isValid()) {
qCWarning(logNetworkManager) << "Failed to get device type:" << error;
} else {
auto type = static_cast<qs::network::NMDeviceType::Enum>(value);
NMDevice* dev = nullptr;
this->mDevices.insert(path, nullptr);
switch (type) {
case NMDeviceType::Wifi: dev = new NMWirelessDevice(path); break;
default: break;
}
if (dev) {
if (!dev->isValid()) {
qCWarning(logNetworkManager) << "Ignoring invalid registration of" << path;
delete dev;
} else {
this->mDevices[path] = dev;
// Only register a frontend device while it's managed by NM.
auto onManagedChanged = [this, dev, type](bool managed) {
managed ? this->registerFrontendDevice(type, dev) : this->removeFrontendDevice(dev);
};
// clang-format off
QObject::connect(dev, &NMDevice::addAndActivateConnection, this, &NetworkManager::addAndActivateConnection);
QObject::connect(dev, &NMDevice::activateConnection, this, &NetworkManager::activateConnection);
QObject::connect(dev, &NMDevice::managedChanged, this, onManagedChanged);
// clang-format on
if (dev->managed()) this->registerFrontendDevice(type, dev);
}
}
temp->deleteLater();
}
};
qs::dbus::asyncReadProperty<uint>(*temp, "DeviceType", callback);
}
void NetworkManager::registerFrontendDevice(NMDeviceType::Enum type, NMDevice* dev) {
NetworkDevice* frontendDev = nullptr;
switch (type) {
case NMDeviceType::Wifi: {
auto* frontendWifiDev = new WifiDevice(dev);
auto* wifiDev = qobject_cast<NMWirelessDevice*>(dev);
// Bind WifiDevice-specific properties
auto translateMode = [wifiDev]() {
switch (wifiDev->mode()) {
case NM80211Mode::Unknown: return WifiDeviceMode::Unknown;
case NM80211Mode::Adhoc: return WifiDeviceMode::AdHoc;
case NM80211Mode::Infra: return WifiDeviceMode::Station;
case NM80211Mode::Ap: return WifiDeviceMode::AccessPoint;
case NM80211Mode::Mesh: return WifiDeviceMode::Mesh;
}
};
// clang-format off
frontendWifiDev->bindableMode().setBinding(translateMode);
wifiDev->bindableScanning().setBinding([frontendWifiDev]() { return frontendWifiDev->scannerEnabled(); });
QObject::connect(wifiDev, &NMWirelessDevice::networkAdded, frontendWifiDev, &WifiDevice::networkAdded);
QObject::connect(wifiDev, &NMWirelessDevice::networkRemoved, frontendWifiDev, &WifiDevice::networkRemoved);
// clang-format on
frontendDev = frontendWifiDev;
break;
}
default: return;
}
// Bind generic NetworkDevice properties
auto translateState = [dev]() {
switch (dev->state()) {
case 0 ... 20: return DeviceConnectionState::Unknown;
case 30: return DeviceConnectionState::Disconnected;
case 40 ... 90: return DeviceConnectionState::Connecting;
case 100: return DeviceConnectionState::Connected;
case 110 ... 120: return DeviceConnectionState::Disconnecting;
}
};
// clang-format off
frontendDev->bindableName().setBinding([dev]() { return dev->interface(); });
frontendDev->bindableAddress().setBinding([dev]() { return dev->hwAddress(); });
frontendDev->bindableNmState().setBinding([dev]() { return dev->state(); });
frontendDev->bindableState().setBinding(translateState);
frontendDev->bindableAutoconnect().setBinding([dev]() { return dev->autoconnect(); });
QObject::connect(frontendDev, &WifiDevice::requestDisconnect, dev, &NMDevice::disconnect);
QObject::connect(frontendDev, &NetworkDevice::requestSetAutoconnect, dev, &NMDevice::setAutoconnect);
// clang-format on
this->mFrontendDevices.insert(dev->path(), frontendDev);
emit this->deviceAdded(frontendDev);
}
void NetworkManager::removeFrontendDevice(NMDevice* dev) {
auto* frontendDev = this->mFrontendDevices.take(dev->path());
if (frontendDev) {
emit this->deviceRemoved(frontendDev);
frontendDev->deleteLater();
}
}
void NetworkManager::onDevicePathAdded(const QDBusObjectPath& path) {
this->registerDevice(path.path());
}
void NetworkManager::onDevicePathRemoved(const QDBusObjectPath& path) {
auto iter = this->mDevices.find(path.path());
if (iter == this->mDevices.end()) {
qCWarning(logNetworkManager) << "Sent removal signal for" << path.path()
<< "which is not registered.";
} else {
auto* dev = iter.value();
this->mDevices.erase(iter);
if (dev) {
this->removeFrontendDevice(dev);
delete dev;
}
}
}
void NetworkManager::activateConnection(
const QDBusObjectPath& connPath,
const QDBusObjectPath& devPath
) {
auto pending = this->proxy->ActivateConnection(connPath, devPath, QDBusObjectPath("/"));
auto* call = new QDBusPendingCallWatcher(pending, this);
auto responseCallback = [](QDBusPendingCallWatcher* call) {
const QDBusPendingReply<QDBusObjectPath> reply = *call;
if (reply.isError()) {
qCWarning(logNetworkManager) << "Failed to activate connection:" << reply.error().message();
}
delete call;
};
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
}
void NetworkManager::addAndActivateConnection(
const ConnectionSettingsMap& settings,
const QDBusObjectPath& devPath,
const QDBusObjectPath& specificObjectPath
) {
auto pending = this->proxy->AddAndActivateConnection(settings, devPath, specificObjectPath);
auto* call = new QDBusPendingCallWatcher(pending, this);
auto responseCallback = [](QDBusPendingCallWatcher* call) {
const QDBusPendingReply<QDBusObjectPath, QDBusObjectPath> reply = *call;
if (reply.isError()) {
qCWarning(logNetworkManager)
<< "Failed to add and activate connection:" << reply.error().message();
}
delete call;
};
QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback);
}
void NetworkManager::setWifiEnabled(bool enabled) {
if (enabled == this->bWifiEnabled) return;
this->bWifiEnabled = enabled;
this->pWifiEnabled.write();
}
bool NetworkManager::isAvailable() const { return this->proxy && this->proxy->isValid(); };
} // namespace qs::network
-67
View File
@@ -1,67 +0,0 @@
#pragma once
#include <qdbusextratypes.h>
#include <qhash.h>
#include <qobject.h>
#include <qproperty.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../../dbus/properties.hpp"
#include "../network.hpp"
#include "dbus_nm_backend.h"
#include "device.hpp"
namespace qs::network {
class NetworkManager: public NetworkBackend {
Q_OBJECT;
public:
explicit NetworkManager(QObject* parent = nullptr);
[[nodiscard]] bool isAvailable() const override;
[[nodiscard]] bool wifiEnabled() const { return this->bWifiEnabled; };
[[nodiscard]] bool wifiHardwareEnabled() const { return this->bWifiHardwareEnabled; };
signals:
void deviceAdded(NetworkDevice* device);
void deviceRemoved(NetworkDevice* device);
void wifiEnabledChanged(bool enabled);
void wifiHardwareEnabledChanged(bool enabled);
public slots:
void setWifiEnabled(bool enabled);
private slots:
void onDevicePathAdded(const QDBusObjectPath& path);
void onDevicePathRemoved(const QDBusObjectPath& path);
void activateConnection(const QDBusObjectPath& connPath, const QDBusObjectPath& devPath);
void addAndActivateConnection(
const ConnectionSettingsMap& settings,
const QDBusObjectPath& devPath,
const QDBusObjectPath& specificObjectPath
);
private:
void init();
void registerDevices();
void registerDevice(const QString& path);
void registerFrontendDevice(NMDeviceType::Enum type, NMDevice* dev);
void removeFrontendDevice(NMDevice* dev);
QHash<QString, NMDevice*> mDevices;
QHash<QString, NetworkDevice*> mFrontendDevices;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, bool, bWifiEnabled, &NetworkManager::wifiEnabledChanged);
Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, bool, bWifiHardwareEnabled, &NetworkManager::wifiHardwareEnabledChanged);
QS_DBUS_BINDABLE_PROPERTY_GROUP(NetworkManager, dbusProperties);
QS_DBUS_PROPERTY_BINDING(NetworkManager, pWifiEnabled, bWifiEnabled, dbusProperties, "WirelessEnabled");
QS_DBUS_PROPERTY_BINDING(NetworkManager, pWifiHardwareEnabled, bWifiHardwareEnabled, dbusProperties, "WirelessHardwareEnabled");
// clang-format on
DBusNetworkManagerProxy* proxy = nullptr;
};
} // namespace qs::network
-151
View File
@@ -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
-105
View File
@@ -1,105 +0,0 @@
#pragma once
#include <qdbusextratypes.h>
#include <qdbuspendingcall.h>
#include <qdbuspendingreply.h>
#include <qobject.h>
#include <qproperty.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../../dbus/properties.hpp"
#include "../wifi.hpp"
#include "dbus_nm_active_connection.h"
#include "dbus_nm_connection_settings.h"
#include "dbus_types.hpp"
#include "enums.hpp"
namespace qs::dbus {
template <>
struct DBusDataTransform<qs::network::NMConnectionState::Enum> {
using Wire = quint32;
using Data = qs::network::NMConnectionState::Enum;
static DBusResult<Data> fromWire(Wire wire);
};
} // namespace qs::dbus
namespace qs::network {
// Proxy of a /org/freedesktop/NetworkManager/Settings/Connection/* object.
class NMConnectionSettings: public QObject {
Q_OBJECT;
public:
explicit NMConnectionSettings(const QString& path, QObject* parent = nullptr);
void forget();
[[nodiscard]] bool isValid() const;
[[nodiscard]] QString path() const;
[[nodiscard]] QString address() const;
[[nodiscard]] ConnectionSettingsMap settings() const { return this->bSettings; };
[[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; };
[[nodiscard]] QBindable<WifiSecurityType::Enum> bindableSecurity() { return &this->bSecurity; };
signals:
void loaded();
void settingsChanged(ConnectionSettingsMap settings);
void securityChanged(WifiSecurityType::Enum security);
void ssidChanged(QString ssid);
private:
bool mLoaded = false;
void updateSettings();
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(NMConnectionSettings, ConnectionSettingsMap, bSettings, &NMConnectionSettings::settingsChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMConnectionSettings, WifiSecurityType::Enum, bSecurity, &NMConnectionSettings::securityChanged);
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMConnectionSettings, connectionSettingsProperties);
// clang-format on
DBusNMConnectionSettingsProxy* proxy = nullptr;
};
// Proxy of a /org/freedesktop/NetworkManager/ActiveConnection/* object.
class NMActiveConnection: public QObject {
Q_OBJECT;
public:
explicit NMActiveConnection(const QString& path, QObject* parent = nullptr);
[[nodiscard]] bool isValid() const;
[[nodiscard]] QString path() const;
[[nodiscard]] QString address() const;
[[nodiscard]] QDBusObjectPath connection() const { return this->bConnection; };
[[nodiscard]] NMConnectionState::Enum state() const { return this->bState; };
[[nodiscard]] NMConnectionStateReason::Enum stateReason() const { return this->mStateReason; };
signals:
void loaded();
void connectionChanged(QDBusObjectPath path);
void stateChanged(NMConnectionState::Enum state);
void stateReasonChanged(NMConnectionStateReason::Enum reason);
void uuidChanged(const QString& uuid);
private slots:
void onStateChanged(quint32 state, quint32 reason);
private:
NMConnectionStateReason::Enum mStateReason = NMConnectionStateReason::Unknown;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(NMActiveConnection, QDBusObjectPath, bConnection, &NMActiveConnection::connectionChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMActiveConnection, QString, bUuid, &NMActiveConnection::uuidChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMActiveConnection, NMConnectionState::Enum, bState, &NMActiveConnection::stateChanged);
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMActiveConnection, activeConnectionProperties);
QS_DBUS_PROPERTY_BINDING(NMActiveConnection, pConnection, bConnection, activeConnectionProperties, "Connection");
QS_DBUS_PROPERTY_BINDING(NMActiveConnection, pUuid, bUuid, activeConnectionProperties, "Uuid");
QS_DBUS_PROPERTY_BINDING(NMActiveConnection, pState, bState, activeConnectionProperties, "State");
// clang-format on
DBusNMActiveConnectionProxy* proxy = nullptr;
};
} // namespace qs::network
-9
View File
@@ -1,9 +0,0 @@
#pragma once
#include <qdbusextratypes.h>
#include <qmap.h>
#include <qstring.h>
#include <qvariant.h>
using ConnectionSettingsMap = QMap<QString, QVariantMap>;
Q_DECLARE_METATYPE(ConnectionSettingsMap);
-143
View File
@@ -1,143 +0,0 @@
#include "device.hpp"
#include <qdbusconnection.h>
#include <qdbusextratypes.h>
#include <qlist.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qset.h>
#include <qstring.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../../core/logcat.hpp"
#include "../../dbus/properties.hpp"
#include "../device.hpp"
#include "connection.hpp"
#include "dbus_nm_device.h"
namespace qs::network {
using namespace qs::dbus;
namespace {
QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg);
}
NMDevice::NMDevice(const QString& path, QObject* parent): QObject(parent) {
this->deviceProxy = new DBusNMDeviceProxy(
"org.freedesktop.NetworkManager",
path,
QDBusConnection::systemBus(),
this
);
if (!this->deviceProxy->isValid()) {
qCWarning(logNetworkManager) << "Cannot create DBus interface for device at" << path;
return;
}
// clang-format off
QObject::connect(this, &NMDevice::availableConnectionPathsChanged, this, &NMDevice::onAvailableConnectionPathsChanged);
QObject::connect(this, &NMDevice::activeConnectionPathChanged, this, &NMDevice::onActiveConnectionPathChanged);
// clang-format on
this->deviceProperties.setInterface(this->deviceProxy);
this->deviceProperties.updateAllViaGetAll();
}
void NMDevice::onActiveConnectionPathChanged(const QDBusObjectPath& path) {
const QString stringPath = path.path();
// Remove old active connection
if (this->mActiveConnection) {
QObject::disconnect(this->mActiveConnection, nullptr, this, nullptr);
delete this->mActiveConnection;
this->mActiveConnection = nullptr;
}
// Create new active connection
if (stringPath != "/") {
auto* active = new NMActiveConnection(stringPath, this);
if (!active->isValid()) {
qCWarning(logNetworkManager) << "Ignoring invalid registration of" << stringPath;
delete active;
} else {
this->mActiveConnection = active;
QObject::connect(
active,
&NMActiveConnection::loaded,
this,
[this, active]() { emit this->activeConnectionLoaded(active); },
Qt::SingleShotConnection
);
}
}
}
void NMDevice::onAvailableConnectionPathsChanged(const QList<QDBusObjectPath>& paths) {
QSet<QString> newPathSet;
for (const QDBusObjectPath& path: paths) {
newPathSet.insert(path.path());
}
const auto existingPaths = this->mConnections.keys();
const QSet<QString> existingPathSet(existingPaths.begin(), existingPaths.end());
const auto addedConnections = newPathSet - existingPathSet;
const auto removedConnections = existingPathSet - newPathSet;
for (const QString& path: addedConnections) {
this->registerConnection(path);
}
for (const QString& path: removedConnections) {
auto* connection = this->mConnections.take(path);
if (!connection) {
qCDebug(logNetworkManager) << "Sent removal signal for" << path << "which is not registered.";
} else {
delete connection;
}
};
}
void NMDevice::registerConnection(const QString& path) {
auto* connection = new NMConnectionSettings(path, this);
if (!connection->isValid()) {
qCWarning(logNetworkManager) << "Ignoring invalid registration of" << path;
delete connection;
} else {
this->mConnections.insert(path, connection);
QObject::connect(
connection,
&NMConnectionSettings::loaded,
this,
[this, connection]() { emit this->connectionLoaded(connection); },
Qt::SingleShotConnection
);
}
}
void NMDevice::disconnect() { this->deviceProxy->Disconnect(); }
void NMDevice::setAutoconnect(bool autoconnect) {
if (autoconnect == this->bAutoconnect) return;
this->bAutoconnect = autoconnect;
this->pAutoconnect.write();
}
bool NMDevice::isValid() const { return this->deviceProxy && this->deviceProxy->isValid(); }
QString NMDevice::address() const {
return this->deviceProxy ? this->deviceProxy->service() : QString();
}
QString NMDevice::path() const { return this->deviceProxy ? this->deviceProxy->path() : QString(); }
} // namespace qs::network
namespace qs::dbus {
DBusResult<qs::network::NMDeviceState::Enum>
DBusDataTransform<qs::network::NMDeviceState::Enum>::fromWire(quint32 wire) {
return DBusResult(static_cast<qs::network::NMDeviceState::Enum>(wire));
}
} // namespace qs::dbus
-100
View File
@@ -1,100 +0,0 @@
#pragma once
#include <qdbusextratypes.h>
#include <qhash.h>
#include <qobject.h>
#include <qproperty.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../../dbus/properties.hpp"
#include "connection.hpp"
#include "dbus_nm_device.h"
namespace qs::dbus {
template <>
struct DBusDataTransform<qs::network::NMDeviceState::Enum> {
using Wire = quint32;
using Data = qs::network::NMDeviceState::Enum;
static DBusResult<Data> fromWire(Wire wire);
};
} // namespace qs::dbus
namespace qs::network {
// Proxy of a /org/freedesktop/NetworkManager/Device/* object.
// Only the members from the org.freedesktop.NetworkManager.Device interface.
// Owns the lifetime of NMActiveConnection(s) and NMConnectionSetting(s).
class NMDevice: public QObject {
Q_OBJECT;
public:
explicit NMDevice(const QString& path, QObject* parent = nullptr);
[[nodiscard]] virtual bool isValid() const;
[[nodiscard]] QString path() const;
[[nodiscard]] QString address() const;
[[nodiscard]] QString interface() const { return this->bInterface; };
[[nodiscard]] QString hwAddress() const { return this->bHwAddress; };
[[nodiscard]] bool managed() const { return this->bManaged; };
[[nodiscard]] NMDeviceState::Enum state() const { return this->bState; };
[[nodiscard]] bool autoconnect() const { return this->bAutoconnect; };
[[nodiscard]] NMActiveConnection* activeConnection() const { return this->mActiveConnection; };
signals:
void activateConnection(const QDBusObjectPath& connPath, const QDBusObjectPath& devPath);
void addAndActivateConnection(
const ConnectionSettingsMap& settings,
const QDBusObjectPath& devPath,
const QDBusObjectPath& apPath
);
void connectionLoaded(NMConnectionSettings* connection);
void connectionRemoved(NMConnectionSettings* connection);
void availableConnectionPathsChanged(QList<QDBusObjectPath> paths);
void activeConnectionPathChanged(const QDBusObjectPath& connection);
void activeConnectionLoaded(NMActiveConnection* active);
void interfaceChanged(const QString& interface);
void hwAddressChanged(const QString& hwAddress);
void managedChanged(bool managed);
void stateChanged(NMDeviceState::Enum state);
void autoconnectChanged(bool autoconnect);
public slots:
void disconnect();
void setAutoconnect(bool autoconnect);
private slots:
void onAvailableConnectionPathsChanged(const QList<QDBusObjectPath>& paths);
void onActiveConnectionPathChanged(const QDBusObjectPath& path);
private:
void registerConnection(const QString& path);
QHash<QString, NMConnectionSettings*> mConnections;
NMActiveConnection* mActiveConnection = nullptr;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QString, bInterface, &NMDevice::interfaceChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QString, bHwAddress, &NMDevice::hwAddressChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, bool, bManaged, &NMDevice::managedChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, NMDeviceState::Enum, bState, &NMDevice::stateChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, bool, bAutoconnect, &NMDevice::autoconnectChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QList<QDBusObjectPath>, bAvailableConnections, &NMDevice::availableConnectionPathsChanged);
Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QDBusObjectPath, bActiveConnection, &NMDevice::activeConnectionPathChanged);
QS_DBUS_BINDABLE_PROPERTY_GROUP(NMDeviceAdapter, deviceProperties);
QS_DBUS_PROPERTY_BINDING(NMDevice, pName, bInterface, deviceProperties, "Interface");
QS_DBUS_PROPERTY_BINDING(NMDevice, pAddress, bHwAddress, deviceProperties, "HwAddress");
QS_DBUS_PROPERTY_BINDING(NMDevice, pManaged, bManaged, deviceProperties, "Managed");
QS_DBUS_PROPERTY_BINDING(NMDevice, pState, bState, deviceProperties, "State");
QS_DBUS_PROPERTY_BINDING(NMDevice, pAutoconnect, bAutoconnect, deviceProperties, "Autoconnect");
QS_DBUS_PROPERTY_BINDING(NMDevice, pAvailableConnections, bAvailableConnections, deviceProperties, "AvailableConnections");
QS_DBUS_PROPERTY_BINDING(NMDevice, pActiveConnection, bActiveConnection, deviceProperties, "ActiveConnection");
// clang-format on
DBusNMDeviceProxy* deviceProxy = nullptr;
};
} // namespace qs::network
-156
View File
@@ -1,156 +0,0 @@
#pragma once
#include <qobject.h>
#include <qqmlintegration.h>
#include <qtmetamacros.h>
#include <qtypes.h>
namespace qs::network {
// Indicates the type of hardware represented by a device object.
class NMDeviceType: public QObject {
Q_OBJECT;
public:
enum Enum : quint8 {
Unknown = 0,
Ethernet = 1,
Wifi = 2,
Unused1 = 3,
Unused2 = 4,
Bluetooth = 5,
OlpcMesh = 6,
Wimax = 7,
Modem = 8,
InfiniBand = 9,
Bond = 10,
Vlan = 11,
Adsl = 12,
Bridge = 13,
Generic = 14,
Team = 15,
Tun = 16,
IpTunnel = 17,
MacVlan = 18,
VxLan = 19,
Veth = 20,
MacSec = 21,
Dummy = 22,
Ppp = 23,
OvsInterface = 24,
OvsPort = 25,
OvsBridge = 26,
Wpan = 27,
Lowpan = 28,
Wireguard = 29,
WifiP2P = 30,
Vrf = 31,
Loopback = 32,
Hsr = 33,
IpVlan = 34,
};
Q_ENUM(Enum);
};
// 802.11 specific device encryption and authentication capabilities.
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceWifiCapabilities.
class NMWirelessCapabilities: public QObject {
Q_OBJECT;
public:
enum Enum : quint16 {
None = 0,
CipherWep40 = 1,
CipherWep104 = 2,
CipherTkip = 4,
CipherCcmp = 8,
Wpa = 16,
Rsn = 32,
Ap = 64,
Adhoc = 128,
FreqValid = 256,
Freq2Ghz = 512,
Freq5Ghz = 1024,
Freq6Ghz = 2048,
Mesh = 4096,
IbssRsn = 8192,
};
Q_ENUM(Enum);
};
// Indicates the 802.11 mode an access point is currently in.
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NM80211Mode.
class NM80211Mode: public QObject {
Q_OBJECT;
public:
enum Enum : quint8 {
Unknown = 0,
Adhoc = 1,
Infra = 2,
Ap = 3,
Mesh = 4,
};
Q_ENUM(Enum);
};
// 802.11 access point flags.
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NM80211ApSecurityFlags.
class NM80211ApFlags: public QObject {
Q_OBJECT;
public:
enum Enum : quint8 {
None = 0,
Privacy = 1,
Wps = 2,
WpsPbc = 4,
WpsPin = 8,
};
Q_ENUM(Enum);
};
// 802.11 access point security and authentication flags.
// These flags describe the current system requirements of an access point as determined from the access point's beacon.
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NM80211ApSecurityFlags.
class NM80211ApSecurityFlags: public QObject {
Q_OBJECT;
public:
enum Enum : quint16 {
None = 0,
PairWep40 = 1,
PairWep104 = 2,
PairTkip = 4,
PairCcmp = 8,
GroupWep40 = 16,
GroupWep104 = 32,
GroupTkip = 64,
GroupCcmp = 128,
KeyMgmtPsk = 256,
KeyMgmt8021x = 512,
KeyMgmtSae = 1024,
KeyMgmtOwe = 2048,
KeyMgmtOweTm = 4096,
KeyMgmtEapSuiteB192 = 8192,
};
Q_ENUM(Enum);
};
// Indicates the state of a connection to a specific network while it is starting, connected, or disconnected from that network.
// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMActiveConnectionState.
class NMConnectionState: public QObject {
Q_OBJECT;
public:
enum Enum : quint8 {
Unknown = 0,
Activating = 1,
Activated = 2,
Deactivating = 3,
Deactivated = 4
};
Q_ENUM(Enum);
};
} // namespace qs::network
@@ -1,4 +0,0 @@
<node>
<interface name="org.freedesktop.NetworkManager.AccessPoint">
</interface>
</node>
@@ -1,8 +0,0 @@
<node>
<interface name="org.freedesktop.NetworkManager.Connection.Active">
<signal name="StateChanged">
<arg name="state" type="u"/>
<arg name="reason" type="u"/>
</signal>
</interface>
</node>
Loaded 100 of 238 files, more files were not shown because too many files have changed in this diff. Show more