1 Commits
Author SHA1 Message Date
outfoxxed da4ea5ff95 crash: switch to cpptrace from breakpad 2026-03-02 09:58:26 -08:00
283 changed files with 3449 additions and 9525 deletions

No files matched your search

-1
View File
@@ -20,7 +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,
+78 -13
View File
@@ -1,17 +1,82 @@
name: Crash Report (v1)
description: Quickshell has crashed (old)
labels: ["unactionable"]
description: Quickshell has crashed
labels: ["bug", "crash"]
body:
- type: markdown
- type: textarea
id: crashinfo
attributes:
value: |
Thank you for taking the time to click the report button.
At this point most of the worst issues in 0.2.1 and before have been fixed and we are
preparing for a new release. Please do not submit this report.
- type: checkboxes
id: donotcheck
label: General crash information
description: |
Paste the contents of the `info.txt` file in your crash folder here.
value: "<details> <summary>General information</summary>
```
<Paste the contents of the file here inside of the triple backticks>
```
</details>"
validations:
required: true
- type: textarea
id: userinfo
attributes:
label: Read the text above. Do not submit the report.
options:
- label: Yes I want this report to be deleted.
required: true
label: What caused the crash
description: |
Any information likely to help debug the crash. What were you doing when the crash occurred,
what changes did you make, can you get it to happen again?
- type: textarea
id: dump
attributes:
label: Minidump
description: |
Attach `minidump.dmp.log` here. If it is too big to upload, compress it.
You may skip this step if quickshell crashed while processing a password
or other sensitive information. If you skipped it write why instead.
validations:
required: true
- type: textarea
id: logs
attributes:
label: Log file
description: |
Attach `log.qslog.log` here. If it is too big to upload, compress it.
You can preview the log if you'd like using `quickshell read-log <path-to-log>`.
validations:
required: true
- type: textarea
id: config
attributes:
label: Configuration
description: |
Attach your configuration here, preferrably in full (not just one file).
Compress it into a zip, tar, etc.
This will help us reproduce the crash ourselves.
- type: textarea
id: bt
attributes:
label: Backtrace
description: |
If you have gdb installed and use systemd, or otherwise know how to get a backtrace,
we would appreciate one. (You may have gdb installed without knowing it)
1. Run `coredumpctl debug <pid>` where `pid` is the number shown after "Crashed process ID"
in the crash reporter.
2. Once it loads, type `bt -full` (then enter)
3. Copy the output and attach it as a file or in a spoiler.
- type: textarea
id: exe
attributes:
label: Executable
description: |
If the crash folder contains a executable.txt file, upload it here. If not you can ignore this field.
If it is too big to upload, compress it.
Note: executable.txt is the quickshell binary. It has a .txt extension due to github's limitations on
filetypes.
+11 -5
View File
@@ -9,15 +9,21 @@ body:
description: |
Any information likely to help debug the crash. What were you doing when the crash occurred,
what changes did you make, can you get it to happen again?
- type: upload
- 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 `report.txt` here.
Attach `log.qslog.log` here. If it is too big to upload, compress it.
Please ensure the report does not contain personal information,
as reports may contain detailed logs leading up to the crash.
You can preview the log if you'd like using `quickshell read-log <path-to-log>`.
validations:
required: true
- type: textarea
@@ -25,7 +31,7 @@ body:
attributes:
label: Configuration
description: |
Attach or link your configuration here, preferrably in full (not just one file).
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.
-31
View File
@@ -1,31 +0,0 @@
<!--
Read https://github.com/quickshell-mirror/quickshell/blob/master/CONTRIBUTING.md before opening a PR.
Failing to do so may result in a ban from opening PRs.
-->
### Describe your changes
<!-- High level overview of what you are changing and why. AI may not be used to write this. -->
### Details and descisions
<!--
Put details of why you took a given approach to problems here if its not obvious.
Reviewing PRs is easier if we don't have to reverse engineer your reasoning.
Consider if things you put here should be code comments instead.
If an AI was heavily used to make the change and its context might be useful,
put it in a spoiler under your own reasoning.
-->
### Open concerns
<!-- Anything you're not sure of or want help with? -->
### Participation
<!--
With the advent of AI, we get a lot more drive-bys that don't care about participating
in a traditional review process, and just want to get a change in.
If you care about your code or learning how the project works to contribute in the future,
please say so. If not, we'll assume you don't particularly care.
-->
+19 -14
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,15 +41,11 @@ 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)
- `cli11` (static library)
Quickshell relies on private Qt APIs and **MUST** be rebuilt against the each Qt release
or crashes via ABI mismatches will occur.
Build time dependencies and static libraries don't have to exist at runtime,
however build time dependencies must be compiled for the architecture of
the builder, while static libraries must be compiled for the architecture
@@ -70,14 +74,7 @@ To disable: `-DCRASH_HANDLER=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.
This still requires `libunwind` to build.
### Jemalloc
We recommend leaving Jemalloc enabled as it will mask memory fragmentation caused
@@ -100,6 +97,13 @@ To disable: `-DSOCKETS=OFF`
### Wayland
This feature enables wayland support. Subfeatures exist for each particular wayland integration.
WARNING: Wayland integration relies on features that are not part of the public Qt API and which
may break in minor releases. Updating quickshell's dependencies without ensuring without ensuring
that the current Qt version is supported WILL result in quickshell failing to build or misbehaving
at runtime.
Currently supported Qt versions: `6.6`, `6.7`.
To disable: `-DWAYLAND=OFF`
Dependencies:
@@ -143,6 +147,7 @@ Enables streaming video from monitors and toplevel windows through various proto
To disable: `-DSCREENCOPY=OFF`
Dependencies:
- `libdrm`
- `libgbm`
- `vulkan-headers` (build-time)
@@ -238,7 +243,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
+3 -6
View File
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.20)
project(quickshell VERSION "0.3.2" LANGUAGES CXX C)
project(quickshell VERSION "0.2.1" LANGUAGES CXX C)
set(UNRELEASED_FEATURES)
@@ -9,9 +9,6 @@ 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,6 +40,7 @@ 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
@@ -87,9 +85,8 @@ include(cmake/util.cmake)
add_compile_options(-Wall -Wextra -Wno-vla-cxx-extension)
# pipewire defines these, breaking PCH
# pipewire defines this, breaking PCH
add_compile_definitions(_REENTRANT)
add_compile_options(-fno-strict-overflow)
if (FRAME_POINTERS)
add_compile_options(-fno-omit-frame-pointer)
+220 -28
View File
@@ -1,43 +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.
- Change descriptions MUST NOT be written by AI. Summarize what you changed in your own words.
If you think an AI summary of a chat session etc may provide useful context, include it in a spoiler.
Quickshell also uses `just` for common development command aliases.
- Changes MUST be submitted by a human who will be responsible for them. Changes submitted without
a human in the loop such as automated tooling and AI Agents are **strictly disallowed**. Accounts
responsible for such contribution attempts **will be banned**.
The dependencies are also available as a nix shell or nix flake which we recommend
using with nix-direnv.
- 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.
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 follow the guidelines outlined in [HACKING.md](HACKING.md) for style and substance.
### Formatting
All contributions should be formatted similarly to what already exists.
Group related functionality together.
- 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.
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`.
## Acceptable Non-code Contributions
#### 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.
- Bug and crash reports. You must follow the instructions in the issue templates and provide the
information requested.
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.
- Feature requests can be made via Issues. Please check to ensure nobody else has requested the same feature.
```cpp
auto x = <expr>; // ok
auto x = QString::number(3); // ok
QString x; // ok
QString x = "foo"; // ok
auto x = QString("foo"); // ok
- Do not make insubstantial or pointless changes.
auto x = QString(); // avoid
QString x(); // avoid
QString x("foo"); // avoid
```
- 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.
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
## Merge timelines
auto x = <expr>; // unit 1
emit this->y(); // 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 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
+53 -9
View File
@@ -1,14 +1,58 @@
## Breaking Changes
### Config paths are no longer canonicalized
This fixes nix configs changing shell-ids on rebuild as the shell id is now derived from
the symlink path. Configs with a symlink in their path will have a different shell id.
Shell ids are used to derive the default config / state / cache folders, so those files
will need to be manually moved if using a config behind a symlinked path without an explicitly
set shell id.
## New Features
- Added support for creating Polkit agents.
- Added support for creating wayland idle inhibitors.
- Added support for wayland idle timeouts.
- Added support for inhibiting wayland compositor shortcuts for focused windows.
- Added the ability to override Quickshell.cacheDir with a custom path.
- Added minimized, maximized, and fullscreen properties to FloatingWindow.
- Added the ability to handle move and resize events to FloatingWindow.
- Pipewire service now reconnects if pipewire dies or a protocol error occurs.
- Added pipewire audio peak detection.
- Added initial support for network management.
- Added support for grabbing focus from popup windows.
- Added support for IPC signal listeners.
- Added Quickshell version checking and version gated preprocessing.
- Added a way to detect if an icon is from the system icon theme or not.
- Added vulkan support to screencopy.
## Other Changes
- `SystemTrayItem.activate()` now forwards Wayland activation tokens to applications.
- FreeBSD is now partially supported.
- IPC operations filter available instances to the current display connection by default.
- PwNodeLinkTracker ignores sound level monitoring programs.
- Replaced breakpad with cpptrace.
## Bug Fixes
- Fixed ScriptModel not sending role updates on changed data that compares equal by key or structure.
- Fixed Hyprland workspaces keeping their old id after Hyprland changes it.
- Fixed desktop entries being parsed incorrectly with trailing whitespace or CRLF line endings.
- Fixed `WlSessionLock.locked` changes not being emitted on unlock.
- Fixed a crash when unplugging a screen being captured by ScreencopyView or trying to capture the fallback screen.
- Fixed most broken qml types in qmlls/qmllint for Qt 6.12.
- Fixed missing thread entry for Pipewire event loop resulting in Pipewire connection failing.
- Fixed Greetd treating responses for a cancelled session as responses for the next one.
- Fixed volume control breaking with pipewire pro audio mode.
- Fixed volume control breaking with bluez streams and potentially others.
- Fixed volume control breaking for devices without route definitions.
- Fixed escape sequence handling in desktop entries.
- Fixed volumes not initializing if a pipewire device was already loaded before its node.
- Fixed hyprland active toplevel not resetting after window closes.
- Fixed hyprland ipc window names and titles being reversed.
- Fixed missing signals for system tray item title and description updates.
- Fixed asynchronous loaders not working after reload.
- Fixed asynchronous loaders not working before window creation.
- Fixed memory leak in IPC handlers.
- Fixed ClippingRectangle related crashes.
- Fixed crashes when monitors are unplugged.
- Fixed crashes when default pipewire devices are lost.
## Packaging Changes
- `glib` and `polkit` have been added as dependencies when compiling with polkit agent support.
- `vulkan-headers` has been added as a build-time dependency for screencopy (Vulkan backend support).
- `breakpad` has been replaced by `cpptrace`, which is far easier to package, and the `CRASH_REPORTER` cmake variable has been replaced with `CRASH_HANDLER` to stop this from being easy to ignore.
-89
View File
@@ -1,89 +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 network management support.
- Added support for grabbing focus from popup windows.
- Added support for IPC signal listeners.
- Added Quickshell version checking and version gated preprocessing.
- Added a way to detect if an icon is from the system icon theme or not.
- Added vulkan support to screencopy.
- Added generic WindowManager interface implementing ext-workspace.
- Added ext-background-effect window blur support.
- Added per-corner radius support to Region.
- Added ColorQuantizer region selection.
- Added dialog window support to FloatingWindow.
- Added lua config support to Hyprland module.
## Other Changes
- IPC operations filter available instances to the current display connection by default.
- PwNodeLinkTracker ignores sound level monitoring programs.
- Replaced breakpad with cpptrace.
- Reloads are prevented if no file content has changed.
- Added `QS_DISABLE_FILE_WATCHER` environment variable to disable file watching.
- Added `QS_DISABLE_CRASH_HANDLER` environment variable to disable crash handling.
- Added `QS_CRASHREPORT_URL` environment variable to allow overriding the crash reporter link.
- Added `AppId` pragma and `QS_APP_ID` environment variable to allow overriding the desktop application ID.
- Added `DropExpensiveFonts` pragma and `QS_DROP_EXPENSIVE_FONTS` environment variable which avoids loading fonts which may cause lag and excessive memory usage if many variants are used.
- Added `DefaultEnv` pragma which sets an environment variable if not already set.
- Unrecognized pragmas are no longer a hard error for future backward compatibility.
## Bug Fixes
- Fixed volume control breaking with pipewire pro audio mode.
- Fixed volume control breaking with bluez streams and potentially others.
- Fixed volume control breaking for devices without route definitions.
- Fixed escape sequence handling in desktop entries.
- Fixed volumes not initializing if a pipewire device was already loaded before its node.
- Fixed hyprland active toplevel not resetting after window closes.
- Fixed hyprland ipc window names and titles being reversed.
- Fixed a hyprland ipc crash when refreshing toplevels before workspaces.
- Fixed missing signals for system tray item title and description updates.
- Fixed asynchronous loaders not working after reload.
- Fixed asynchronous loaders not working before window creation.
- Fixed memory leak in IPC handlers.
- Fixed ClippingRectangle related crashes.
- Fixed crashes when monitors are unplugged.
- Fixed crashes when default pipewire devices are lost.
- Fixed ToplevelManager not clearing activeToplevel on deactivation.
- Desktop action order is now preserved.
- Fixed partial socket reads in greetd and hyprland on slow machines.
- Worked around Qt bug causing crashes when plugging and unplugging monitors.
- Fixed HyprlandFocusGrab crashing if windows were destroyed after being passed to it.
- Fixed ScreencopyView pixelation when scaled.
- Fixed JsonAdapter crashing and providing bad data on read when using JsonObject.
- Fixed JsonAdapter sending unnecessary property changes for primitive values.
- Fixed JsonAdapter serialization for lists.
- Fixed pipewire crashes after hotplugging devices and changing default outputs.
- Fixed launches failing for `--daemonize` on some systems.
- Fixed screencopy crashing when used across GPUs.
- Fixed pipewire volumes not working for some pw-pulse clients.
- Fixed nulls in Toplevel.screens after unplugging a monitor.
- Fixed some DbusMenu updates being dropped from apps.
## Packaging Changes
- `glib` and `polkit` have been added as dependencies when compiling with polkit agent support.
- `vulkan-headers` has been added as a build-time dependency for screencopy (Vulkan backend support).
- `breakpad` has been replaced by `cpptrace`, which is far easier to package, and the `CRASH_REPORTER` cmake variable has been replaced with `CRASH_HANDLER` to stop this from being easy to ignore.
- `DISTRIBUTOR_DEBUGINFO_AVAILABLE` was removed as it is no longer important without breakpad.
- `libdrm` is now unconditionally required as a direct dependency.
-23
View File
@@ -1,23 +0,0 @@
## Bug Fixes
- Fixed `ScreencopyView` not displaying when only lock surfaces are shown.
- Fixed `WlSessionLockSurface.visible` crashing if accessed before backing surface creation.
- Fixed `MprisPlayer` returning `rate` for `minRate` and `maxRate`.
- Fixed missing/wrong change signals on various properties.
- Fixed session lock crashes on sleep, wake, DPMS, and unlocking.
- `QsWindow.updatesEnabled` makes sure windows are redrawn when set to true.
- Fixed potential crashes from usage of `WindowsetProjection.screens` during monitor unplug.
- Fixed crashes from accessing freed objects laundered through a `ScriptModel`.
- Fixed crashes when a wifi network disappear.
- Fixed unhandled notifications sending `NotificationClosed` out of order.
- Fixed `qs kill` not waiting for the process to exit.
- Fixed IPC calls from children of a crashed and relaunched process crashing.
- Fixed `JsonAdapter` crashing when deserializing new objects into an array.
- Fixed `Toplevel.unsetRectangle` crashing when calle.
- Fixed intermittent `FileView` crashes when updating watched files.
- Fixed `ColorQuantizer` deletion crashing if an operation was live.
- Fixed crashes when failing to create a `ScreencopyView`.
- Fixed `PwNodePeakMonitor` crashing when sampling a stream with mismatched channels from the node.
- Fixed unsetting `PopupAnchor.item` causing a crash.
- Fixed hiding the last `PanelWindow` on screen causing a crash under X11.
- Fixed crashes when `ScriptModel.values` is set while processing previously set values.
-17
View File
@@ -1,17 +0,0 @@
## Other Changes
- Added support for Qt 6.12
- Added support for Hyprland's new workspace address format.
## Bug Fixes
- Fixed main process crashes on pam subprocess misbehavior.
- Fixed crashes when attempting to create or modify session locks reentrantly.
- Fixed networking state breaking after restarting NetworkManager.
- Fixed accessibility information not being provided for Quickshell windows.
- Fixed `WlSessionLockSurface.screen` being null and never updating with a real screen on monitor plug.
- Fixed `NotificationAction.text` not accepting text updates.
- Fixed PipeWire route volume writes for devices without a reported volume step.
- Fixed a crash when quitting or reloading while a `Loader` was still loading asynchronously.
- Fixed future `Socket` connection attempts after a failed connection attempt.
- Fixed Open wifi networks showing as Unknown security.
+2 -21
View File
@@ -21,27 +21,8 @@ endfunction()
# DEPENDENCIES introduces a cmake dependency which we don't need with static modules.
# This greatly improves comp speed by not introducing those dependencies.
function (qs_add_module_deps_light target)
cmake_parse_arguments(PARSE_ARGV 1 arg "" "" "URIS")
set(uris)
# Add dep metatypes files to target so it picks up the dep type data
foreach (dep IN LISTS arg_UNPARSED_ARGUMENTS)
get_target_property(uri ${dep} QT_QML_MODULE_URI)
get_target_property(metatypes ${dep} INTERFACE_QT_META_TYPES_BUILD_FILE)
if (NOT uri OR NOT metatypes)
message(FATAL_ERROR "${dep} must be a qml module defined before ${target}")
endif()
list(APPEND uris ${uri})
target_sources(${target} PRIVATE ${metatypes})
endforeach()
list(APPEND uris ${arg_URIS})
foreach (uri IN LISTS uris)
string(APPEND qmldir_extra "depends ${uri}\n")
foreach (dep IN LISTS ARGN)
string(APPEND qmldir_extra "depends ${dep}\n")
endforeach()
qs_append_qmldir(${target} "${qmldir_extra}")
+13 -11
View File
@@ -19,7 +19,6 @@
wayland-protocols,
wayland-scanner,
xorg,
libxcb ? xorg.libxcb,
libdrm,
libgbm ? null,
vulkan-headers,
@@ -56,7 +55,7 @@
unwrapped = stdenv.mkDerivation {
pname = "quickshell${lib.optionalString debug "-debug"}";
version = "0.3.2";
version = "0.2.1";
src = nix-gitignore.gitignoreSource "/default.nix\n" ./.;
dontWrapQtApps = true; # see wrappers
@@ -76,21 +75,24 @@
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.optionals withCrashHandler [
(cpptrace.overrideAttrs (prev: {
cmakeFlags = prev.cmakeFlags ++ [
"-DCPPTRACE_UNWIND_WITH_LIBUNWIND=TRUE"
];
buildInputs = prev.buildInputs ++ [ libunwind ];
}))
libunwind
libdwarf
]
++ lib.optional withJemalloc jemalloc
++ lib.optional (withWayland && lib.strings.compareVersions qt6.qtbase.version "6.10.0" == -1) qt6.qtwayland
++ lib.optionals withWayland [ wayland wayland-protocols ]
++ lib.optionals (withWayland && libgbm != null) [ libgbm vulkan-headers ]
++ lib.optional withX11 libxcb
++ lib.optionals (withWayland && libgbm != null) [ libdrm libgbm vulkan-headers ]
++ lib.optional withX11 xorg.libxcb
++ lib.optional withPam pam
++ lib.optional withPipewire pipewire
++ lib.optionals withPolkit [ polkit glib ];
+77
View File
@@ -0,0 +1,77 @@
(define-module (quickshell)
#:use-module ((guix licenses) #:prefix license:)
#:use-module (gnu packages cpp)
#:use-module (gnu packages freedesktop)
#:use-module (gnu packages gcc)
#:use-module (gnu packages gl)
#:use-module (gnu packages jemalloc)
#:use-module (gnu packages linux)
#:use-module (gnu packages ninja)
#:use-module (gnu packages pkg-config)
#:use-module (gnu packages qt)
#:use-module (gnu packages vulkan)
#:use-module (gnu packages xdisorg)
#:use-module (gnu packages xorg)
#:use-module (guix build-system cmake)
#:use-module (guix download)
#:use-module (guix gexp)
#:use-module (guix git-download)
#:use-module (guix packages)
#:use-module (guix packages)
#:use-module (guix utils))
(define-public quickshell-git
(package
(name "quickshell")
(version "git")
(source (local-file "." "quickshell-checkout"
#:recursive? #t
#:select? (or (git-predicate (current-source-directory))
(const #t))))
(build-system cmake-build-system)
(propagated-inputs (list qtbase qtdeclarative qtsvg))
(native-inputs (list ninja
gcc-14
pkg-config
qtshadertools
spirv-tools
wayland-protocols
cli11))
(inputs (list jemalloc
libdrm
libxcb
libxkbcommon
linux-pam
polkit
mesa
pipewire
qtbase
qtdeclarative
qtwayland
vulkan-headers
wayland))
(arguments
(list #:tests? #f
#:configure-flags
#~(list "-GNinja"
"-DDISTRIBUTOR=\"In-tree Guix channel\""
"-DDISTRIBUTOR_DEBUGINFO_AVAILABLE=NO"
"-DCRASH_HANDLER=OFF")
#:phases
#~(modify-phases %standard-phases
(replace 'build (lambda _ (invoke "cmake" "--build" ".")))
(replace 'install (lambda _ (invoke "cmake" "--install" ".")))
(add-after 'install 'wrap-program
(lambda* (#:key inputs #:allow-other-keys)
(wrap-program (string-append #$output "/bin/quickshell")
`("QML_IMPORT_PATH" ":"
= (,(getenv "QML_IMPORT_PATH")))))))))
(home-page "https://quickshell.outfoxxed.me")
(synopsis "QtQuick-based desktop shell toolkit")
(description
"Quickshell is a flexible QtQuick-based toolkit for creating and
customizing toolbars, notification centers, and other desktop
environment tools in a live programming environment.")
(license license:lgpl3)))
quickshell-git
-5
View File
@@ -11,7 +11,6 @@ add_subdirectory(window)
add_subdirectory(io)
add_subdirectory(widgets)
add_subdirectory(ui)
add_subdirectory(windowmanager)
if (CRASH_HANDLER)
add_subdirectory(crash)
@@ -29,10 +28,6 @@ if (X11)
add_subdirectory(x11)
endif()
if (I3)
add_subdirectory(x11/i3)
endif()
add_subdirectory(services)
if (BLUETOOTH)
-2
View File
@@ -29,8 +29,6 @@ qt_add_qml_module(quickshell-bluetooth
DEPENDENCIES QtQml
)
qs_add_module_deps_light(quickshell-bluetooth quickshell-core)
install_qml_module(quickshell-bluetooth)
# dbus headers
+2 -2
View File
@@ -32,7 +32,7 @@ public:
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(qs::bluetooth::BluetoothAdapterState::Enum state);
Q_INVOKABLE static QString toString(BluetoothAdapterState::Enum state);
};
} // namespace qs::bluetooth
@@ -64,7 +64,7 @@ class BluetoothAdapter: public QObject {
/// True if the adapter is currently enabled. More detailed state is available from @@state.
Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged);
/// Detailed power state of the adapter.
Q_PROPERTY(qs::bluetooth::BluetoothAdapterState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
Q_PROPERTY(BluetoothAdapterState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
/// True if the adapter can be discovered by other bluetooth devices.
Q_PROPERTY(bool discoverable READ discoverable WRITE setDiscoverable NOTIFY discoverableChanged);
/// Timeout in seconds for how long the adapter stays discoverable after @@discoverable is set to true.
+1 -1
View File
@@ -66,7 +66,7 @@ class BluezQml: public QObject {
QML_SINGLETON;
// clang-format off
/// The default bluetooth adapter. Usually there is only one.
Q_PROPERTY(qs::bluetooth::BluetoothAdapter* defaultAdapter READ default NOTIFY defaultAdapterChanged BINDABLE bindableDefaultAdapter);
Q_PROPERTY(BluetoothAdapter* defaultAdapter READ default NOTIFY defaultAdapterChanged BINDABLE bindableDefaultAdapter);
QSDOC_TYPE_OVERRIDE(ObjectModel<qs::bluetooth::BluetoothAdapter>*);
/// A list of all bluetooth adapters. See @@defaultAdapter for the default.
Q_PROPERTY(UntypedObjectModel* adapters READ adapters CONSTANT);
+3 -3
View File
@@ -29,7 +29,7 @@ public:
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(qs::bluetooth::BluetoothDeviceState::Enum state);
Q_INVOKABLE static QString toString(BluetoothDeviceState::Enum state);
};
struct BatteryPercentage {};
@@ -70,7 +70,7 @@ class BluetoothDevice: public QObject {
/// System icon representing the device type. Use @@Quickshell.Quickshell.iconPath() to display this in an image.
Q_PROPERTY(QString icon READ default NOTIFY iconChanged BINDABLE bindableIcon);
/// Connection state of the device.
Q_PROPERTY(qs::bluetooth::BluetoothDeviceState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
Q_PROPERTY(BluetoothDeviceState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
/// True if the device is currently connected to the computer.
///
/// Setting this property is equivalent to calling @@connect() and @@disconnect().
@@ -100,7 +100,7 @@ class BluetoothDevice: public QObject {
/// Battery level of the connected device, from `0.0` to `1.0`. Only valid if @@batteryAvailable is true.
Q_PROPERTY(qreal battery READ default NOTIFY batteryChanged BINDABLE bindableBattery);
/// The Bluetooth adapter this device belongs to.
Q_PROPERTY(qs::bluetooth::BluetoothAdapter* adapter READ adapter NOTIFY adapterChanged);
Q_PROPERTY(BluetoothAdapter* adapter READ adapter NOTIFY adapterChanged);
/// DBus path of the device under the `org.bluez` system service.
Q_PROPERTY(QString dbusPath READ path CONSTANT);
// clang-format on
+6
View File
@@ -15,6 +15,12 @@ else()
set(CRASH_HANDLER_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)
target_include_directories(quickshell-build INTERFACE ${CMAKE_CURRENT_BINARY_DIR})
+1 -1
View File
@@ -8,10 +8,10 @@
#define QS_UNRELEASED_FEATURES "@UNRELEASED_FEATURES@"
#define GIT_REVISION "@GIT_REVISION@"
#define DISTRIBUTOR "@DISTRIBUTOR@"
#define DISTRIBUTOR_DEBUGINFO_AVAILABLE @DEBUGINFO_AVAILABLE@
#define CRASH_HANDLER @CRASH_HANDLER_DEF@
#define BUILD_TYPE "@CMAKE_BUILD_TYPE@"
#define COMPILER "@CMAKE_CXX_COMPILER_ID@ (@CMAKE_CXX_COMPILER_VERSION@)"
#define COMPILE_FLAGS "@CMAKE_CXX_FLAGS@"
#define BUILD_CONFIGURATION "@QS_BUILD_OPTIONS@"
#define CRASHREPORT_URL "@CRASHREPORT_URL@"
// NOLINTEND
+1 -4
View File
@@ -1,4 +1,3 @@
pkg_check_modules(libdrm REQUIRED IMPORTED_TARGET libdrm)
qt_add_library(quickshell-core STATIC
plugin.cpp
shell.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::QuickPrivate Qt::Widgets quickshell-build)
qs_module_pch(quickshell-core SET large)
+9 -40
View File
@@ -1,6 +1,5 @@
#include "colorquantizer.hpp"
#include <algorithm>
#include <utility>
#include <qatomic.h>
#include <qcolor.h>
@@ -14,7 +13,6 @@
#include <qnumeric.h>
#include <qobject.h>
#include <qqmllist.h>
#include <qrect.h>
#include <qrgb.h>
#include <qthreadpool.h>
#include <qtmetamacros.h>
@@ -26,30 +24,19 @@ namespace {
QS_LOGGING_CATEGORY(logColorQuantizer, "quickshell.colorquantizer", QtWarningMsg);
}
ColorQuantizerOperation::ColorQuantizerOperation(
QUrl source,
qreal depth,
QRect imageRect,
qreal rescaleSize
)
: source(std::move(source))
ColorQuantizerOperation::ColorQuantizerOperation(QUrl* source, qreal depth, qreal rescaleSize)
: source(source)
, maxDepth(depth)
, imageRect(imageRect)
, rescaleSize(rescaleSize) {
this->setAutoDelete(false);
}
void ColorQuantizerOperation::quantizeImage(const QAtomicInteger<bool>& shouldCancel) {
if (shouldCancel.loadAcquire() || this->source.isEmpty()) return;
if (shouldCancel.loadAcquire() || this->source->isEmpty()) return;
this->colors.clear();
auto image = QImage(this->source.toLocalFile());
if (this->imageRect.isValid()) {
image = image.copy(this->imageRect);
}
auto image = QImage(this->source->toLocalFile());
if ((image.width() > this->rescaleSize || image.height() > this->rescaleSize)
&& this->rescaleSize > 0)
{
@@ -62,7 +49,7 @@ void ColorQuantizerOperation::quantizeImage(const QAtomicInteger<bool>& shouldCa
}
if (image.isNull()) {
qCWarning(logColorQuantizer) << "Failed to load image from" << this->source.toString();
qCWarning(logColorQuantizer) << "Failed to load image from" << this->source->toString();
return;
}
@@ -192,8 +179,6 @@ void ColorQuantizerOperation::run() {
void ColorQuantizerOperation::tryCancel() { this->shouldCancel.storeRelease(true); }
ColorQuantizer::~ColorQuantizer() { this->cancelAsync(); }
void ColorQuantizer::componentComplete() {
this->componentCompleted = true;
if (!this->mSource.isEmpty()) this->quantizeAsync();
@@ -213,27 +198,16 @@ void ColorQuantizer::setDepth(qreal depth) {
this->mDepth = depth;
emit this->depthChanged();
if (this->componentCompleted && !this->mSource.isEmpty()) this->quantizeAsync();
if (this->componentCompleted) this->quantizeAsync();
}
}
void ColorQuantizer::setImageRect(QRect imageRect) {
if (this->mImageRect != imageRect) {
this->mImageRect = imageRect;
emit this->imageRectChanged();
if (this->componentCompleted && !this->mSource.isEmpty()) this->quantizeAsync();
}
}
void ColorQuantizer::resetImageRect() { this->setImageRect(QRect()); }
void ColorQuantizer::setRescaleSize(int rescaleSize) {
if (this->mRescaleSize != rescaleSize) {
this->mRescaleSize = rescaleSize;
emit this->rescaleSizeChanged();
if (this->componentCompleted && !this->mSource.isEmpty()) this->quantizeAsync();
if (this->componentCompleted) this->quantizeAsync();
}
}
@@ -247,13 +221,8 @@ void ColorQuantizer::quantizeAsync() {
if (this->liveOperation) this->cancelAsync();
qCDebug(logColorQuantizer) << "Starting color quantization asynchronously";
this->liveOperation = new ColorQuantizerOperation(
this->mSource,
this->mDepth,
this->mImageRect,
this->mRescaleSize
);
this->liveOperation =
new ColorQuantizerOperation(&this->mSource, this->mDepth, this->mRescaleSize);
QObject::connect(
this->liveOperation,
+3 -21
View File
@@ -5,9 +5,7 @@
#include <qproperty.h>
#include <qqmlintegration.h>
#include <qqmlparserstatus.h>
#include <qrect.h>
#include <qrunnable.h>
#include <qtclasshelpermacros.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include <qurl.h>
@@ -18,7 +16,7 @@ class ColorQuantizerOperation
Q_OBJECT;
public:
explicit ColorQuantizerOperation(QUrl source, qreal depth, QRect imageRect, qreal rescaleSize);
explicit ColorQuantizerOperation(QUrl* source, qreal depth, qreal rescaleSize);
void run() override;
void tryCancel();
@@ -44,9 +42,8 @@ private:
QAtomicInteger<bool> shouldCancel = false;
QList<QColor> colors;
QUrl source;
QUrl* source;
qreal maxDepth;
QRect imageRect;
qreal rescaleSize;
};
@@ -81,22 +78,13 @@ class ColorQuantizer
/// binary split of the color space
Q_PROPERTY(qreal depth READ depth WRITE setDepth NOTIFY depthChanged);
// clang-format off
/// Rectangle that the source image is cropped to.
///
/// Can be set to `undefined` to reset.
Q_PROPERTY(QRect imageRect READ imageRect WRITE setImageRect RESET resetImageRect NOTIFY imageRectChanged);
// clang-format on
/// The size to rescale the image to, when rescaleSize is 0 then no scaling will be done.
/// > [!NOTE] Results from color quantization doesn't suffer much when rescaling, it's
/// > recommended to rescale, otherwise the quantization process will take much longer.
/// > reccommended to rescale, otherwise the quantization process will take much longer.
Q_PROPERTY(qreal rescaleSize READ rescaleSize WRITE setRescaleSize NOTIFY rescaleSizeChanged);
public:
explicit ColorQuantizer(QObject* parent = nullptr): QObject(parent) {}
~ColorQuantizer() override;
Q_DISABLE_COPY_MOVE(ColorQuantizer);
void componentComplete() override;
void classBegin() override {}
@@ -109,10 +97,6 @@ public:
[[nodiscard]] qreal depth() const { return this->mDepth; }
void setDepth(qreal depth);
[[nodiscard]] QRect imageRect() const { return this->mImageRect; }
void setImageRect(QRect imageRect);
void resetImageRect();
[[nodiscard]] qreal rescaleSize() const { return this->mRescaleSize; }
void setRescaleSize(int rescaleSize);
@@ -120,7 +104,6 @@ signals:
void colorsChanged();
void sourceChanged();
void depthChanged();
void imageRectChanged();
void rescaleSizeChanged();
public slots:
@@ -134,7 +117,6 @@ private:
ColorQuantizerOperation* liveOperation = nullptr;
QUrl mSource;
qreal mDepth = 0;
QRect mImageRect;
qreal mRescaleSize = 0;
Q_OBJECT_BINDABLE_PROPERTY(
-176
View File
@@ -1,176 +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, 6> {
"QS_",
"QT_",
"QML_",
"QML2_",
"QSG_",
"XDG_CURRENT_DESKTOP=",
};
for (const auto& prefix: prefixes) {
if (strncmp(prefix.data(), *envp, prefix.length()) == 0) goto print;
}
continue;
print:
stream << *envp << '\n';
}
return info;
}
QString combinedInfo() {
QString info;
auto stream = QTextStream(&info);
stream << "===== Version Information =====\n";
stream << "Quickshell: " << qsVersion() << '\n';
stream << "Qt: " << qtVersion() << '\n';
stream << "\n===== Build Information =====\n";
stream << "Build Type: " << BUILD_TYPE << '\n';
stream << "Compiler: " << COMPILER << '\n';
stream << "Compile Flags: " << COMPILE_FLAGS << '\n';
stream << "Configuration:\n" << BUILD_CONFIGURATION << '\n';
stream << "\n===== System Information =====\n";
stream << systemInfo();
stream << "\n===== Environment (trimmed) =====\n";
stream << envInfo();
return info;
}
} // namespace qs::debuginfo
-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 -29
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,30 +147,29 @@ ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString&
}
}
pendingActions.insert(actionName, action);
data.actions.insert(actionName, action);
}
entries.clear();
};
for (auto& line: text.split(u'\n', Qt::SkipEmptyParts)) {
const auto trimmed = line.trimmed();
if (trimmed.isEmpty() || trimmed.startsWith(u'#')) continue;
if (line.startsWith(u'#')) continue;
if (trimmed.startsWith(u'[') && trimmed.endsWith(u']')) {
if (line.startsWith(u'[') && line.endsWith(u']')) {
finishCategory();
groupName = trimmed.sliced(1, trimmed.length() - 2);
groupName = line.sliced(1, line.length() - 2);
continue;
}
auto splitIdx = trimmed.indexOf(u'=');
auto splitIdx = line.indexOf(u'=');
if (splitIdx == -1) {
qCWarning(logDesktopEntry) << "Encountered invalid line in desktop entry (no =)" << line;
continue;
}
auto key = trimmed.sliced(0, splitIdx);
const auto& value = trimmed.sliced(splitIdx + 1);
auto key = line.sliced(0, splitIdx);
const auto& value = line.sliced(splitIdx + 1);
auto localeIdx = key.indexOf('[');
Locale locale;
@@ -198,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;
}
@@ -228,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();
@@ -250,7 +237,6 @@ void DesktopEntry::updateActions(const QVector<DesktopActionData>& newActions) {
Qt::endPropertyUpdateGroup();
act->mEntries = d.entries;
this->mActions.append(act);
}
for (auto* leftover: old) {
@@ -264,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;
+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 -44
View File
@@ -11,7 +11,6 @@
#include <qlist.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qqmlcontext.h>
#include <qqmlengine.h>
@@ -63,10 +62,6 @@ EngineGeneration::EngineGeneration(const QDir& rootPath, QmlScanner scanner)
EngineGeneration::EngineGeneration(): EngineGeneration(QDir(), QmlScanner()) {}
EngineGeneration::~EngineGeneration() {
for (auto* extension: this->extensions.values()) {
delete extension;
}
if (this->engine != nullptr) {
qFatal() << this << "destroyed without calling destroy()";
}
@@ -83,37 +78,30 @@ void EngineGeneration::destroy() {
this->watcher = nullptr;
}
for (auto* extension: this->extensions.values()) {
delete extension;
}
if (this->root != nullptr) {
// prevent further js execution during teardown of the root's children, gc and the engine.
QObject::connect(this->root, &QObject::destroyed, this, [this]() {
// prevent further js execution between garbage collection and engine destruction.
this->engine->setInterrupted(true);
g_generations.remove(this->engine);
// Garbage is not collected during engine destruction.
this->engine->collectGarbage();
delete this->engine;
this->engine = nullptr;
auto terminate = this->shouldTerminate;
auto code = this->exitCode;
delete this;
if (terminate) QCoreApplication::exit(code);
});
// QObject::destroyed is emitted before the root's children are destroyed.
// The engine must outlive them, as objects such as Loaders with in-flight
// incubations access it in their destructors.
QObject::connect(
this->root,
&QObject::destroyed,
this,
[this]() {
g_generations.remove(this->engine);
// Garbage is not collected during engine destruction.
this->engine->collectGarbage();
delete this->engine;
this->engine = nullptr;
auto terminate = this->shouldTerminate;
auto code = this->exitCode;
delete this;
if (terminate) QCoreApplication::exit(code);
},
Qt::QueuedConnection
);
this->root->deleteLater();
this->root = nullptr;
} else {
@@ -210,7 +198,6 @@ void EngineGeneration::setWatchingFiles(bool watching) {
}
} else {
if (this->watcher != nullptr) {
QObject::disconnect(this->watcher, nullptr, this, nullptr);
this->watcher->deleteLater();
this->watcher = nullptr;
}
@@ -222,8 +209,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);
}
}
@@ -244,11 +229,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();
}
}
@@ -257,11 +237,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;
}
+1 -1
View File
@@ -14,7 +14,7 @@
#include "generation.hpp"
// QMenu re-calls pixmap() every time the mouse moves so it's important to cache it.
// QMenu re-calls pixmap() every time the mouse moves so its important to cache it.
class PixmapCacheIconEngine: public QIconEngine {
void paint(
QPainter* /*unused*/,
+4 -4
View File
@@ -3,14 +3,14 @@
#include <qdatastream.h>
QDataStream& operator<<(QDataStream& stream, const InstanceInfo& info) {
stream << info.instanceId << info.configPath << info.shellId << info.appId << info.launchTime
<< info.pid << info.display;
stream << info.instanceId << info.configPath << info.shellId << info.launchTime << info.pid
<< info.display;
return stream;
}
QDataStream& operator>>(QDataStream& stream, InstanceInfo& info) {
stream >> info.instanceId >> info.configPath >> info.shellId >> info.appId >> info.launchTime
>> info.pid >> info.display;
stream >> info.instanceId >> info.configPath >> info.shellId >> info.launchTime >> info.pid
>> info.display;
return stream;
}
-1
View File
@@ -9,7 +9,6 @@ struct InstanceInfo {
QString instanceId;
QString configPath;
QString shellId;
QString appId;
QDateTime launchTime;
pid_t pid = -1;
QString display;
+24 -48
View File
@@ -14,7 +14,6 @@
#include <qlist.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qmutex.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qobjectdefs.h>
@@ -32,9 +31,6 @@
#include <sys/sendfile.h>
#include <sys/types.h>
#endif
#ifdef __FreeBSD__
#include <unistd.h>
#endif
#include "instanceinfo.hpp"
#include "logcat.hpp"
@@ -71,7 +67,7 @@ bool copyFileData(int sourceFd, int destFd, qint64 size) {
return true;
#else
std::array<char, 64 * 1024> buffer = {};
auto remaining = usize;
auto remaining = totalTarget;
while (remaining > 0) {
auto chunk = std::min(remaining, buffer.size());
@@ -221,7 +217,6 @@ void LogManager::messageHandler(
}
if (display) {
auto locker = QMutexLocker(&self->stdoutMutex);
LogMessage::formatMessage(
self->stdoutStream,
message,
@@ -257,21 +252,9 @@ void LogManager::filterCategory(QLoggingCategory* category) {
filter.warn = filter.info || instance->mDefaultLevel == QtWarningMsg || defaultLevel == QtWarningMsg;
filter.critical = filter.warn || instance->mDefaultLevel == QtCriticalMsg || defaultLevel == QtCriticalMsg;
// clang-format on
} else {
if (instance->lastCategoryFilter) {
instance->lastCategoryFilter(category);
filter = CategoryFilter(category);
}
// Hides virtual/override property warnings.
// Getting rid of this is blocked by https://qt-project.atlassian.net/browse/QTBUG-145977
// for internal Quickshell types, and may still be desired for shells that want to maintain
// compatibility with Qt versions prior to 6.11.
if (categoryName == QLatin1StringView("qt.qml.propertyCache.append")
&& !qEnvironmentVariableIsSet("QS_NO_FILTER_QT_LOGS"))
{
filter.warn = false;
}
} else if (instance->lastCategoryFilter) {
instance->lastCategoryFilter(category);
filter = CategoryFilter(category);
}
for (const auto& rule: *instance->rules) {
@@ -304,10 +287,6 @@ void LogManager::init(
const QString& rules,
const QString& prefix
) {
static bool alreadyInitialized = false;
if (alreadyInitialized) return;
alreadyInitialized = true;
auto* instance = LogManager::instance();
instance->colorLogs = color;
instance->timestampLogs = timestamp;
@@ -326,32 +305,22 @@ void LogManager::init(
instance->rules->append(parser.rules());
}
instance->lastCategoryFilter = QLoggingCategory::installFilter(&LogManager::filterCategory);
if (instance->lastCategoryFilter == &LogManager::filterCategory) {
qCFatal(logLogging) << "Quickshell's log filter has been installed twice. This is a bug.";
instance->lastCategoryFilter = nullptr;
}
qInstallMessageHandler(&LogManager::messageHandler);
instance->threadLogging = new ThreadLogging(instance);
instance->threadLogging->init();
qCDebug(logLogging) << "Logger initialized.";
}
void LogManager::initThreadLogging() {
auto* instance = LogManager::instance();
qCDebug(logLogging) << "Moving logger to dedicated thread...";
instance->lastCategoryFilter = QLoggingCategory::installFilter(&LogManager::filterCategory);
qCDebug(logLogging) << "Creating offthread logger...";
auto* thread = new QThread();
instance->threadLogging->setParent(nullptr);
instance->threadLogging->moveToThread(thread);
instance->threadProxy.moveToThread(thread);
thread->start();
qCDebug(logLogging) << "Logger thread initialized.";
QMetaObject::invokeMethod(
&instance->threadProxy,
&LoggingThreadProxy::initInThread,
Qt::BlockingQueuedConnection
);
qCDebug(logLogging) << "Logger initialized.";
}
void initLogCategoryLevel(const char* name, QtMsgType defaultLevel) {
@@ -360,8 +329,8 @@ void initLogCategoryLevel(const char* name, QtMsgType defaultLevel) {
void LogManager::initFs() {
QMetaObject::invokeMethod(
LogManager::instance()->threadLogging,
&ThreadLogging::initFs,
&LogManager::instance()->threadProxy,
"initFs",
Qt::BlockingQueuedConnection
);
}
@@ -374,6 +343,13 @@ CategoryFilter LogManager::getFilter(QLatin1StringView category) {
return this->allFilters.value(category);
}
void LoggingThreadProxy::initInThread() {
this->logging = new ThreadLogging(this);
this->logging->init();
}
void LoggingThreadProxy::initFs() { this->logging->initFs(); }
void ThreadLogging::init() {
auto logMfd = memfd_create("quickshell:logs", 0);
@@ -544,7 +520,7 @@ void ThreadLogging::initFs() {
Qt::QueuedConnection
);
qCDebug(logLogging) << "Switched threaded logger to queued event loop connection.";
qCDebug(logLogging) << "Switched threaded logger to queued eventloop connection.";
}
void ThreadLogging::onMessage(const LogMessage& msg, bool showInSparse) {
+15 -4
View File
@@ -10,7 +10,6 @@
#include <qlatin1stringview.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qmutex.h>
#include <qobject.h>
#include <qtmetamacros.h>
@@ -55,6 +54,20 @@ size_t qHash(const LogMessage& message);
class ThreadLogging;
class LoggingThreadProxy: public QObject {
Q_OBJECT;
public:
explicit LoggingThreadProxy() = default;
public slots:
void initInThread();
void initFs();
private:
ThreadLogging* logging = nullptr;
};
namespace qt_logging_registry {
class QLoggingRule;
}
@@ -90,7 +103,6 @@ public:
const QString& prefix = ""
);
static void initThreadLogging();
static void initFs();
static LogManager* instance();
@@ -123,8 +135,7 @@ private:
QHash<QLatin1StringView, CategoryFilter> allFilters;
QTextStream stdoutStream;
QMutex stdoutMutex;
ThreadLogging* threadLogging = nullptr;
LoggingThreadProxy threadProxy;
friend void initLogCategoryLevel(const char* name, QtMsgType defaultLevel);
};
+2 -4
View File
@@ -114,13 +114,11 @@ public:
explicit ThreadLogging(QObject* parent): QObject(parent) {}
void init();
void initFs();
void setupFileLogging();
public slots:
void initFs();
private slots:
void onMessage(const qs::log::LogMessage& msg, bool showInSparse);
void onMessage(const LogMessage& msg, bool showInSparse);
private:
QFile* file = nullptr;
-4
View File
@@ -138,10 +138,6 @@ public:
qsizetype oi = 0;
for (auto* object: newValues) {
if (this->mValuesList.length() == oi || this->mValuesList.at(oi) != object) {
// object may already be present further down (reorder case);
// drop the old row first so the same pointer is not inserted twice.
auto old = this->mValuesList.indexOf(object, oi);
if (old != -1) this->removeAt(old);
this->insertObject(object, oi);
}
+9 -12
View File
@@ -1,7 +1,6 @@
#include "paths.hpp"
#include <cerrno>
#include <cstdio>
#include <tuple>
#include <utility>
#include <fcntl.h>
@@ -10,6 +9,7 @@
#include <qdir.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qpair.h>
#include <qstandardpaths.h>
#include <qtenvironmentvariables.h>
#include <qtversionchecks.h>
@@ -64,7 +64,7 @@ QDir* QsPaths::baseRunDir() {
if (this->baseRunState == DirState::Unknown) {
auto runtimeDir = qEnvironmentVariable("XDG_RUNTIME_DIR");
if (runtimeDir.isEmpty()) {
runtimeDir = QString("/run/user/%1").arg(getuid());
runtimeDir = QString("/run/user/$1").arg(getuid());
qCInfo(logPaths) << "XDG_RUNTIME_DIR was not set, defaulting to" << runtimeDir;
}
@@ -412,11 +412,10 @@ bool QsPaths::checkLock(const QString& path, InstanceLockInfo* info, bool allowD
return true;
}
std::tuple<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
QPair<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
QsPaths::collectInstances(const QString& path, const QString& display) {
qCDebug(logPaths) << "Collecting instances from" << path;
auto liveInstances = QVector<InstanceLockInfo>();
auto mismatchedInstances = QVector<InstanceLockInfo>();
auto deadInstances = QVector<InstanceLockInfo>();
auto dir = QDir(path);
@@ -428,22 +427,20 @@ QsPaths::collectInstances(const QString& path, const QString& display) {
qCDebug(logPaths).nospace() << "Found instance " << info.instance.instanceId << " (pid "
<< info.pid << ") at " << path;
if (info.pid == -1) {
deadInstances.push_back(info);
continue;
}
if (!display.isEmpty() && info.instance.display != display) {
qCDebug(logPaths) << "Skipped instance with mismatched display at" << path;
mismatchedInstances.push_back(info);
continue;
}
liveInstances.push_back(info);
if (info.pid == -1) {
deadInstances.push_back(info);
} else {
liveInstances.push_back(info);
}
} else {
qCDebug(logPaths) << "Skipped potential instance at" << path;
}
}
return {liveInstances, mismatchedInstances, deadInstances};
return qMakePair(liveInstances, deadInstances);
}
+1 -4
View File
@@ -1,7 +1,4 @@
#pragma once
#include <tuple>
#include <qcontainerfwd.h>
#include <qdatetime.h>
#include <qdir.h>
#include <qpair.h>
@@ -32,7 +29,7 @@ public:
static QString ipcPath(const QString& id);
static bool
checkLock(const QString& path, InstanceLockInfo* info = nullptr, bool allowDead = false);
static std::tuple<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
static QPair<QVector<InstanceLockInfo>, QVector<InstanceLockInfo>>
collectInstances(const QString& path, const QString& display);
QDir* baseRunDir();
+4 -1
View File
@@ -18,6 +18,7 @@
#include <qwindow.h>
#include "../window/proxywindow.hpp"
#include "../window/windowinterface.hpp"
#include "iconprovider.hpp"
#include "model.hpp"
#include "platformmenu_p.hpp"
@@ -90,8 +91,10 @@ bool PlatformMenuEntry::display(QObject* parentWindow, int relativeX, int relati
} else if (parentWindow == nullptr) {
qCritical() << "Cannot display PlatformMenuEntry with null parent window.";
return false;
} else if (auto* proxy = ProxyWindowBase::forObject(parentWindow)) {
} else if (auto* proxy = qobject_cast<ProxyWindowBase*>(parentWindow)) {
window = proxy->backingWindow();
} else if (auto* interface = qobject_cast<WindowInterface*>(parentWindow)) {
window = interface->proxyWindow()->backingWindow();
} else {
qCritical() << "PlatformMenuEntry.display() must be called with a window.";
return false;
-16
View File
@@ -9,18 +9,6 @@ static QVector<QsEnginePlugin*> plugins; // NOLINT
void QsEnginePlugin::registerPlugin(QsEnginePlugin& plugin) { plugins.push_back(&plugin); }
void QsEnginePlugin::preinitPluginsOnly() {
plugins.removeIf([](QsEnginePlugin* plugin) { return !plugin->applies(); });
std::ranges::sort(plugins, [](QsEnginePlugin* a, QsEnginePlugin* b) {
return b->dependencies().contains(a->name());
});
for (QsEnginePlugin* plugin: plugins) {
plugin->preinit();
}
}
void QsEnginePlugin::initPlugins() {
plugins.removeIf([](QsEnginePlugin* plugin) { return !plugin->applies(); });
@@ -28,10 +16,6 @@ void QsEnginePlugin::initPlugins() {
return b->dependencies().contains(a->name());
});
for (QsEnginePlugin* plugin: plugins) {
plugin->preinit();
}
for (QsEnginePlugin* plugin: plugins) {
plugin->init();
}
-2
View File
@@ -18,14 +18,12 @@ public:
virtual QString name() { return QString(); }
virtual QList<QString> dependencies() { return {}; }
virtual bool applies() { return true; }
virtual void preinit() {}
virtual void init() {}
virtual void registerTypes() {}
virtual void constructGeneration(EngineGeneration& /*unused*/) {} // NOLINT
virtual void onReload() {}
static void registerPlugin(QsEnginePlugin& plugin);
static void preinitPluginsOnly();
static void initPlugins();
static void runConstructGeneration(EngineGeneration& generation);
static void runOnReload();
+5 -4
View File
@@ -11,6 +11,7 @@
#include <qwindow.h>
#include "../window/proxywindow.hpp"
#include "../window/windowinterface.hpp"
#include "types.hpp"
bool PopupAnchorState::operator==(const PopupAnchorState& other) const {
@@ -39,8 +40,10 @@ void PopupAnchor::setWindowInternal(QObject* window) {
}
if (window) {
if (auto* proxy = ProxyWindowBase::forObject(window)) {
if (auto* proxy = qobject_cast<ProxyWindowBase*>(window)) {
this->bProxyWindow = proxy;
} else if (auto* interface = qobject_cast<WindowInterface*>(window)) {
this->bProxyWindow = interface->proxyWindow();
} else {
qWarning() << "Tried to set popup anchor window to" << window
<< "which is not a quickshell window.";
@@ -93,8 +96,6 @@ void PopupAnchor::setItem(QQuickItem* item) {
QObject::connect(item, &QObject::destroyed, this, &PopupAnchor::onItemDestroyed);
QObject::connect(item, &QQuickItem::windowChanged, this, &PopupAnchor::onItemWindowChanged);
}
emit this->itemChanged();
}
void PopupAnchor::onWindowDestroyed() {
@@ -111,7 +112,7 @@ void PopupAnchor::onItemDestroyed() {
}
void PopupAnchor::onItemWindowChanged() {
if (auto* window = qobject_cast<ProxiedWindow*>(this->mItem ? this->mItem->window() : nullptr)) {
if (auto* window = qobject_cast<ProxiedWindow*>(this->mItem->window())) {
this->setWindowInternal(window->proxy());
} else {
this->setWindowInternal(nullptr);
+7 -42
View File
@@ -18,17 +18,14 @@
#include <qscreen.h>
#include <qtenvironmentvariables.h>
#include <qtmetamacros.h>
#include <qtversion.h>
#include <qtypes.h>
#include <qvariant.h>
#include <qversionnumber.h>
#include <qwindowdefs.h>
#include <unistd.h>
#include "../io/processcore.hpp"
#include "generation.hpp"
#include "iconimageprovider.hpp"
#include "instanceinfo.hpp"
#include "paths.hpp"
#include "qmlscreen.hpp"
#include "rootwrapper.hpp"
@@ -63,9 +60,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;
@@ -88,8 +83,6 @@ QuickshellTracked::QuickshellTracked() {
}
}
// FIXME: Some callers can potentially pass a freed QScreen here which is why we don't init a new one.
// The QuickshellTracked::init() in launch.cpp makes sure our screenAdded handler runs first for now.
QuickshellScreenInfo* QuickshellTracked::screenInfo(QScreen* screen) const {
for (auto* info: this->screens) {
if (info->screen == screen) return info;
@@ -98,19 +91,13 @@ QuickshellScreenInfo* QuickshellTracked::screenInfo(QScreen* screen) const {
return nullptr;
}
namespace {
static QuickshellTracked* qsTrackedInstance = nullptr; // NOLINT
}
void QuickshellTracked::init() {
if (qsTrackedInstance) qFatal() << "Tried to reinitialize QuickshellTracked";
qsTrackedInstance = new QuickshellTracked();
QJSEngine::setObjectOwnership(qsTrackedInstance, QJSEngine::CppOwnership);
}
QuickshellTracked* QuickshellTracked::instance() {
if (!qsTrackedInstance) qFatal() << "Tried to get QuickshellTracked instance before init";
return qsTrackedInstance;
static QuickshellTracked* instance = nullptr; // NOLINT
if (instance == nullptr) {
QJSEngine::setObjectOwnership(instance, QJSEngine::CppOwnership);
instance = new QuickshellTracked();
}
return instance;
}
void QuickshellTracked::updateScreens() {
@@ -164,22 +151,6 @@ qint32 QuickshellGlobal::processId() const { // NOLINT
return getpid();
}
QString QuickshellGlobal::instanceId() const { // NOLINT
return InstanceInfo::CURRENT.instanceId;
}
QString QuickshellGlobal::shellId() const { // NOLINT
return InstanceInfo::CURRENT.shellId;
}
QString QuickshellGlobal::appId() const { // NOLINT
return InstanceInfo::CURRENT.appId;
}
QDateTime QuickshellGlobal::launchTime() const { // NOLINT
return InstanceInfo::CURRENT.launchTime;
}
qsizetype QuickshellGlobal::screensCount(QQmlListProperty<QuickshellScreenInfo>* /*unused*/) {
return QuickshellTracked::instance()->screens.size();
}
@@ -353,12 +324,6 @@ bool QuickshellGlobal::hasVersion(qint32 major, qint32 minor) {
return QuickshellGlobal::hasVersion(major, minor, QStringList());
}
bool QuickshellGlobal::hasQtVersion(int major, int minor) {
auto qtVersion = QVersionNumber::fromString(qVersion());
auto requiredVersion = QVersionNumber(major, minor);
return qtVersion >= requiredVersion;
}
QuickshellGlobal* QuickshellGlobal::create(QQmlEngine* engine, QJSEngine* /*unused*/) {
auto* qsg = new QuickshellGlobal();
auto* generation = EngineGeneration::findEngineGeneration(engine);
+1 -33
View File
@@ -17,7 +17,6 @@
#include "../io/processcore.hpp"
#include "doc.hpp"
#include "instanceinfo.hpp"
#include "qmlscreen.hpp"
///! Accessor for some options under the Quickshell type.
@@ -70,7 +69,6 @@ public:
QVector<QuickshellScreenInfo*> screens;
QuickshellScreenInfo* screenInfo(QScreen* screen) const;
static void init();
static QuickshellTracked* instance();
private slots:
@@ -85,21 +83,6 @@ class QuickshellGlobal: public QObject {
// clang-format off
/// Quickshell's process id.
Q_PROPERTY(qint32 processId READ processId CONSTANT);
/// A unique identifier for this Quickshell instance
Q_PROPERTY(QString instanceId READ instanceId CONSTANT)
/// The shell ID, used to differentiate between different shell configurations.
///
/// Defaults to a stable value derived from the config path.
/// Can be overridden with `//@ pragma ShellId <id>` in the root qml file.
Q_PROPERTY(QString shellId READ shellId CONSTANT)
/// The desktop application ID.
///
/// Defaults to `org.quickshell`.
/// Can be overridden with `//@ pragma AppId <id>` in the root qml file
/// or the `QS_APP_ID` environment variable.
Q_PROPERTY(QString appId READ appId CONSTANT)
/// The time at which this Quickshell instance was launched.
Q_PROPERTY(QDateTime launchTime READ launchTime CONSTANT)
/// All currently connected screens.
///
/// This property updates as connected screens change.
@@ -166,10 +149,6 @@ class QuickshellGlobal: public QObject {
public:
[[nodiscard]] qint32 processId() const;
[[nodiscard]] QString instanceId() const;
[[nodiscard]] QString shellId() const;
[[nodiscard]] QString appId() const;
[[nodiscard]] QDateTime launchTime() const;
QQmlListProperty<QuickshellScreenInfo> screens();
@@ -245,7 +224,7 @@ public:
/// it is assumed that all unreleased features are present. The unreleased feature list
/// may be omitted.
///
/// > [!NOTE] You can version gate code blocks using Quickshell's preprocessor which
/// > [!NOTE] You can feature gate code blocks using Quickshell's preprocessor which
/// > has the same function available.
/// >
/// > ```qml
@@ -255,17 +234,6 @@ public:
/// > ```
Q_INVOKABLE static bool hasVersion(qint32 major, qint32 minor, const QStringList& features);
Q_INVOKABLE static bool hasVersion(qint32 major, qint32 minor);
/// Check if Qt's version is at least `major.minor`.
///
/// > [!NOTE] You can version gate code blocks using Quickshell's preprocessor which
/// > has the same function available.
/// >
/// > ```qml
/// > //@ if hasVersion(6, 10)
/// > ...
/// > //@ endif
/// > ```
Q_INVOKABLE static bool hasQtVersion(qint32 major, qint32 minor);
void clearReloadPopupInhibit() { this->mInhibitReloadPopup = false; }
[[nodiscard]] bool isReloadPopupInhibited() const { return this->mInhibitReloadPopup; }
+1 -1
View File
@@ -15,7 +15,7 @@
/// Monitor object useful for setting the monitor for a @@QsWindow
/// or querying information about the monitor.
///
/// > [!WARNING] If the monitor is disconnected, then any stored copies of its ShellMonitor will
/// > [!WARNING] If the monitor is disconnected than any stored copies of its ShellMonitor will
/// > be marked as dangling and all properties will return default values.
/// > Reconnecting the monitor will not reconnect it to the ShellMonitor object.
///
+1 -7
View File
@@ -74,7 +74,6 @@ void QsMenuAnchor::onClosed() {
this->mOpen = false;
if (this->platformMenu) {
QObject::disconnect(this->platformMenu, nullptr, this, nullptr);
this->platformMenu->deleteLater();
this->platformMenu = nullptr;
}
@@ -102,12 +101,7 @@ void QsMenuAnchor::setMenu(QsMenuHandle* menu) {
if (menu == this->mMenu) return;
if (this->mMenu != nullptr) {
if (this->platformMenu != nullptr) {
QObject::disconnect(this->platformMenu, nullptr, this, nullptr);
this->platformMenu->deleteLater();
this->platformMenu = nullptr;
}
if (this->platformMenu != nullptr) this->platformMenu->deleteLater();
QObject::disconnect(this->mMenu, nullptr, this, nullptr);
}
+1 -134
View File
@@ -1,5 +1,4 @@
#include "region.hpp"
#include <algorithm>
#include <cmath>
#include <qobject.h>
@@ -19,11 +18,6 @@ PendingRegion::PendingRegion(QObject* parent): QObject(parent) {
QObject::connect(this, &PendingRegion::yChanged, this, &PendingRegion::changed);
QObject::connect(this, &PendingRegion::widthChanged, this, &PendingRegion::changed);
QObject::connect(this, &PendingRegion::heightChanged, this, &PendingRegion::changed);
QObject::connect(this, &PendingRegion::radiusChanged, this, &PendingRegion::changed);
QObject::connect(this, &PendingRegion::topLeftRadiusChanged, this, &PendingRegion::changed);
QObject::connect(this, &PendingRegion::topRightRadiusChanged, this, &PendingRegion::changed);
QObject::connect(this, &PendingRegion::bottomLeftRadiusChanged, this, &PendingRegion::changed);
QObject::connect(this, &PendingRegion::bottomRightRadiusChanged, this, &PendingRegion::changed);
QObject::connect(this, &PendingRegion::childrenChanged, this, &PendingRegion::changed);
}
@@ -51,79 +45,6 @@ void PendingRegion::onItemDestroyed() { this->mItem = nullptr; }
void PendingRegion::onChildDestroyed() { this->mRegions.removeAll(this->sender()); }
qint32 PendingRegion::radius() const { return this->mRadius; }
void PendingRegion::setRadius(qint32 radius) {
if (radius == this->mRadius) return;
this->mRadius = radius;
emit this->radiusChanged();
if (!(this->mCornerOverrides & TopLeft)) emit this->topLeftRadiusChanged();
if (!(this->mCornerOverrides & TopRight)) emit this->topRightRadiusChanged();
if (!(this->mCornerOverrides & BottomLeft)) emit this->bottomLeftRadiusChanged();
if (!(this->mCornerOverrides & BottomRight)) emit this->bottomRightRadiusChanged();
}
qint32 PendingRegion::topLeftRadius() const {
return (this->mCornerOverrides & TopLeft) ? this->mTopLeftRadius : this->mRadius;
}
void PendingRegion::setTopLeftRadius(qint32 radius) {
this->mTopLeftRadius = radius;
this->mCornerOverrides |= TopLeft;
emit this->topLeftRadiusChanged();
}
void PendingRegion::resetTopLeftRadius() {
this->mCornerOverrides &= ~TopLeft;
emit this->topLeftRadiusChanged();
}
qint32 PendingRegion::topRightRadius() const {
return (this->mCornerOverrides & TopRight) ? this->mTopRightRadius : this->mRadius;
}
void PendingRegion::setTopRightRadius(qint32 radius) {
this->mTopRightRadius = radius;
this->mCornerOverrides |= TopRight;
emit this->topRightRadiusChanged();
}
void PendingRegion::resetTopRightRadius() {
this->mCornerOverrides &= ~TopRight;
emit this->topRightRadiusChanged();
}
qint32 PendingRegion::bottomLeftRadius() const {
return (this->mCornerOverrides & BottomLeft) ? this->mBottomLeftRadius : this->mRadius;
}
void PendingRegion::setBottomLeftRadius(qint32 radius) {
this->mBottomLeftRadius = radius;
this->mCornerOverrides |= BottomLeft;
emit this->bottomLeftRadiusChanged();
}
void PendingRegion::resetBottomLeftRadius() {
this->mCornerOverrides &= ~BottomLeft;
emit this->bottomLeftRadiusChanged();
}
qint32 PendingRegion::bottomRightRadius() const {
return (this->mCornerOverrides & BottomRight) ? this->mBottomRightRadius : this->mRadius;
}
void PendingRegion::setBottomRightRadius(qint32 radius) {
this->mBottomRightRadius = radius;
this->mCornerOverrides |= BottomRight;
emit this->bottomRightRadiusChanged();
}
void PendingRegion::resetBottomRightRadius() {
this->mCornerOverrides &= ~BottomRight;
emit this->bottomRightRadiusChanged();
}
QQmlListProperty<PendingRegion> PendingRegion::regions() {
return QQmlListProperty<PendingRegion>(
this,
@@ -169,60 +90,6 @@ QRegion PendingRegion::build() const {
region = QRegion(this->mX, this->mY, this->mWidth, this->mHeight, type);
}
if (this->mShape == RegionShape::Rect && !region.isEmpty()) {
auto tl = std::max(this->topLeftRadius(), 0);
auto tr = std::max(this->topRightRadius(), 0);
auto bl = std::max(this->bottomLeftRadius(), 0);
auto br = std::max(this->bottomRightRadius(), 0);
if (tl > 0 || tr > 0 || bl > 0 || br > 0) {
auto rect = region.boundingRect();
auto x = rect.x();
auto y = rect.y();
auto w = rect.width();
auto h = rect.height();
// Normalize so adjacent corners don't exceed their shared edge.
// Each corner is scaled by the tightest constraint of its two edges.
auto topScale = tl + tr > w ? static_cast<double>(w) / (tl + tr) : 1.0;
auto bottomScale = bl + br > w ? static_cast<double>(w) / (bl + br) : 1.0;
auto leftScale = tl + bl > h ? static_cast<double>(h) / (tl + bl) : 1.0;
auto rightScale = tr + br > h ? static_cast<double>(h) / (tr + br) : 1.0;
tl = static_cast<qint32>(tl * std::min(topScale, leftScale));
tr = static_cast<qint32>(tr * std::min(topScale, rightScale));
bl = static_cast<qint32>(bl * std::min(bottomScale, leftScale));
br = static_cast<qint32>(br * std::min(bottomScale, rightScale));
// Unlock each corner: subtract (cornerBox - quarterEllipse) from the
// full rect. Each corner only modifies pixels inside its own box,
// so no diagonal overlap is possible.
if (tl > 0) {
auto box = QRegion(x, y, tl, tl);
auto ellipse = QRegion(x, y, tl * 2, tl * 2, QRegion::Ellipse);
region -= box - (ellipse & box);
}
if (tr > 0) {
auto box = QRegion(x + w - tr, y, tr, tr);
auto ellipse = QRegion(x + w - tr * 2, y, tr * 2, tr * 2, QRegion::Ellipse);
region -= box - (ellipse & box);
}
if (bl > 0) {
auto box = QRegion(x, y + h - bl, bl, bl);
auto ellipse = QRegion(x, y + h - bl * 2, bl * 2, bl * 2, QRegion::Ellipse);
region -= box - (ellipse & box);
}
if (br > 0) {
auto box = QRegion(x + w - br, y + h - br, br, br);
auto ellipse = QRegion(x + w - br * 2, y + h - br * 2, br * 2, br * 2, QRegion::Ellipse);
region -= box - (ellipse & box);
}
}
}
for (const auto& childRegion: this->mRegions) {
region = childRegion->applyTo(region);
}
@@ -242,7 +109,7 @@ QRegion PendingRegion::applyTo(QRegion& region) const {
}
QRegion PendingRegion::applyTo(const QRect& rect) const {
// if left as the default, don't combine it with the whole rect area, leave it as is.
// if left as the default, dont combine it with the whole rect area, leave it as is.
if (this->mIntersection == Intersection::Combine) {
return this->build();
} else {
-60
View File
@@ -66,29 +66,6 @@ class PendingRegion: public QObject {
Q_PROPERTY(qint32 width MEMBER mWidth NOTIFY widthChanged);
/// Defaults to 0. Does nothing if @@item is set.
Q_PROPERTY(qint32 height MEMBER mHeight NOTIFY heightChanged);
// clang-format off
/// Corner radius for rounded rectangles. Only applies when @@shape is `Rect`. Defaults to 0.
///
/// Acts as the default for @@topLeftRadius, @@topRightRadius, @@bottomLeftRadius,
/// and @@bottomRightRadius.
Q_PROPERTY(qint32 radius READ radius WRITE setRadius NOTIFY radiusChanged);
/// Top-left corner radius. Only applies when @@shape is `Rect`.
///
/// Defaults to @@radius, and may be reset by assigning `undefined`.
Q_PROPERTY(qint32 topLeftRadius READ topLeftRadius WRITE setTopLeftRadius RESET resetTopLeftRadius NOTIFY topLeftRadiusChanged);
/// Top-right corner radius. Only applies when @@shape is `Rect`.
///
/// Defaults to @@radius, and may be reset by assigning `undefined`.
Q_PROPERTY(qint32 topRightRadius READ topRightRadius WRITE setTopRightRadius RESET resetTopRightRadius NOTIFY topRightRadiusChanged);
/// Bottom-left corner radius. Only applies when @@shape is `Rect`.
///
/// Defaults to @@radius, and may be reset by assigning `undefined`.
Q_PROPERTY(qint32 bottomLeftRadius READ bottomLeftRadius WRITE setBottomLeftRadius RESET resetBottomLeftRadius NOTIFY bottomLeftRadiusChanged);
/// Bottom-right corner radius. Only applies when @@shape is `Rect`.
///
/// Defaults to @@radius, and may be reset by assigning `undefined`.
Q_PROPERTY(qint32 bottomRightRadius READ bottomRightRadius WRITE setBottomRightRadius RESET resetBottomRightRadius NOTIFY bottomRightRadiusChanged);
// clang-format on
/// Regions to apply on top of this region.
///
@@ -114,25 +91,6 @@ public:
void setItem(QQuickItem* item);
[[nodiscard]] qint32 radius() const;
void setRadius(qint32 radius);
[[nodiscard]] qint32 topLeftRadius() const;
void setTopLeftRadius(qint32 radius);
void resetTopLeftRadius();
[[nodiscard]] qint32 topRightRadius() const;
void setTopRightRadius(qint32 radius);
void resetTopRightRadius();
[[nodiscard]] qint32 bottomLeftRadius() const;
void setBottomLeftRadius(qint32 radius);
void resetBottomLeftRadius();
[[nodiscard]] qint32 bottomRightRadius() const;
void setBottomRightRadius(qint32 radius);
void resetBottomRightRadius();
QQmlListProperty<PendingRegion> regions();
[[nodiscard]] bool empty() const;
@@ -151,11 +109,6 @@ signals:
void yChanged();
void widthChanged();
void heightChanged();
void radiusChanged();
void topLeftRadiusChanged();
void topRightRadiusChanged();
void bottomLeftRadiusChanged();
void bottomRightRadiusChanged();
void childrenChanged();
/// Triggered when the region's geometry changes.
@@ -177,25 +130,12 @@ private:
static void
regionsReplace(QQmlListProperty<PendingRegion>* prop, qsizetype i, PendingRegion* region);
enum CornerOverride : quint8 {
TopLeft = 0b1,
TopRight = 0b10,
BottomLeft = 0b100,
BottomRight = 0b1000,
};
QQuickItem* mItem = nullptr;
qint32 mX = 0;
qint32 mY = 0;
qint32 mWidth = 0;
qint32 mHeight = 0;
qint32 mRadius = 0;
qint32 mTopLeftRadius = 0;
qint32 mTopRightRadius = 0;
qint32 mBottomLeftRadius = 0;
qint32 mBottomRightRadius = 0;
quint8 mCornerOverrides = 0;
QList<PendingRegion*> mRegions;
};
+1 -1
View File
@@ -116,7 +116,7 @@ QObject* Reloadable::getChildByReloadId(QObject* parent, const QString& reloadId
auto* reloadable = qobject_cast<Reloadable*>(child);
if (reloadable != nullptr) {
if (reloadable->mReloadableId == reloadId) return reloadable;
// if not then don't check its children as that's a separate reload scope.
// if not then don't check its children as thats a seperate reload scope.
} else {
auto* reloadable = Reloadable::getChildByReloadId(child, reloadId);
if (reloadable != nullptr) return reloadable;
+1 -1
View File
@@ -21,7 +21,7 @@ class Reloadable
/// previous state.
///
/// Simply keeping a stable identifier across config versions (saves) is
/// enough to help the reloader figure out which object in the old revision corresponds to
/// enough to help the reloader figure out which object in the old revision corrosponds to
/// this object in the current revision, and facilitate smoother reloading.
///
/// Note that identifiers are scoped, and will try to do the right thing in context.
+18 -44
View File
@@ -3,7 +3,6 @@
#include <utility>
#include <qcontainerfwd.h>
#include <qcryptographichash.h>
#include <qdir.h>
#include <qfileinfo.h>
#include <qjsengine.h>
@@ -22,25 +21,6 @@
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);
@@ -89,7 +69,7 @@ void QmlScanner::scanDir(const QDir& dir) {
QString qmldir;
auto stream = QTextStream(&qmldir);
// can't derive a module name if not in shell path
// cant derive a module name if not in shell path
if (path.startsWith(this->rootPath.path())) {
auto end = path.sliced(this->rootPath.path().length());
@@ -129,13 +109,13 @@ 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;
@@ -145,7 +125,10 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
QString overrideText;
bool isOverridden = false;
auto& pragmaEngine = *QmlScanner::preprocEngine();
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});
@@ -160,7 +143,7 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
if (!singleton && line == "pragma Singleton") {
singleton = true;
} else if (line.startsWith("import")) {
// we don't care about "import qs" as we always load the root folder
// 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;
@@ -211,7 +194,7 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
mask = false;
}
if (!sourceMasked && mask) hideMask = true;
mask = sourceMasked || mask; // can't unmask if a nested if passes
mask = sourceMasked || mask; // cant unmask if a nested if passes
ifScopes.append(mask);
if (mask) isOverridden = true;
sourceMasked = mask;
@@ -236,6 +219,8 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
postError("unclosed preprocessor if block");
}
file.close();
if (isOverridden) {
this->fileIntercepts.insert(path, overrideText);
}
@@ -272,11 +257,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;
@@ -291,12 +273,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);
@@ -367,13 +351,3 @@ QPair<QString, QString> QmlScanner::jsonToQml(const QJsonValue& value, int inden
return qMakePair(QStringLiteral("var"), "null");
}
}
QJSEngine* QmlScanner::preprocEngine() {
static auto* engine = [] {
auto* engine = new QJSEngine();
engine->globalObject().setPrototype(engine->newQObject(new qs::scan::env::PreprocEnv()));
return engine;
}();
return engine;
}
-8
View File
@@ -1,10 +1,8 @@
#pragma once
#include <qbytearray.h>
#include <qcontainerfwd.h>
#include <qdir.h>
#include <qhash.h>
#include <qjsengine.h>
#include <qloggingcategory.h>
#include <qvector.h>
@@ -23,7 +21,6 @@ public:
QVector<QDir> scannedDirs;
QVector<QString> scannedFiles;
QHash<QString, QByteArray> fileHashes;
QHash<QString, QString> fileIntercepts;
struct ScanError {
@@ -34,15 +31,10 @@ public:
QVector<ScanError> scanErrors;
bool readAndHashFile(const QString& path, QByteArray& data);
[[nodiscard]] bool hasFileContentChanged(const QString& path) const;
private:
QDir rootPath;
bool scanQmlFile(const QString& path, bool& singleton, bool& internal);
bool scanQmlJson(const QString& path);
[[nodiscard]] static QPair<QString, QString> jsonToQml(const QJsonValue& value, int indent = 0);
static QJSEngine* preprocEngine();
};
-8
View File
@@ -2,8 +2,6 @@
#include <qcontainerfwd.h>
#include <qtenvironmentvariables.h>
#include <qtversion.h>
#include <qversionnumber.h>
#include "build.hpp"
@@ -22,12 +20,6 @@ bool PreprocEnv::hasVersion(int major, int minor, const QStringList& features) {
return QS_VERSION_MAJOR == major && QS_VERSION_MINOR == minor;
}
bool PreprocEnv::hasQtVersion(int major, int minor) {
auto qtVersion = QVersionNumber::fromString(qVersion());
auto requiredVersion = QVersionNumber(major, minor);
return qtVersion >= requiredVersion;
}
QString PreprocEnv::env(const QString& variable) {
return qEnvironmentVariable(variable.toStdString().c_str());
}
-1
View File
@@ -12,7 +12,6 @@ class PreprocEnv: public QObject {
public:
Q_INVOKABLE static bool
hasVersion(int major, int minor, const QStringList& features = QStringList());
Q_INVOKABLE static bool hasQtVersion(int major, int minor);
Q_INVOKABLE static QString env(const QString& variable);
Q_INVOKABLE static bool isEnvSet(const QString& variable);
+42 -135
View File
@@ -4,102 +4,39 @@
#include <qabstractitemmodel.h>
#include <qcontainerfwd.h>
#include <qjsvalue.h>
#include <qjsvalueiterator.h>
#include <qlist.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qtmetamacros.h>
#include <qtversionchecks.h>
#include <qtypes.h>
#include <qvariant.h>
namespace {
struct RecursionPair {
const QJSValue* a;
const QJSValue* b;
const RecursionPair* previous;
};
bool qjsValueStructuralEq(
const QJSValue& a,
const QJSValue& b,
const RecursionPair* activePair = nullptr
) {
if (a.strictlyEquals(b)) return true;
const auto jsValueType = QMetaType::fromType<QJSValue>();
const auto aIsNative = a.toVariant(QJSValue::RetainJSObjects).metaType() != jsValueType;
const auto bIsNative = b.toVariant(QJSValue::RetainJSObjects).metaType() != jsValueType;
if (aIsNative || bIsNative) return false;
if (!a.isObject() || !b.isObject() || a.isCallable() || b.isCallable()) return false;
if (a.isArray() != b.isArray()) return false;
for (const auto* pair = activePair; pair != nullptr; pair = pair->previous) {
if (a.strictlyEquals(*pair->a) || b.strictlyEquals(*pair->b)) return false;
}
const auto nextActivePair = RecursionPair {.a = &a, .b = &b, .previous = activePair};
auto aIter = QJSValueIterator(a);
auto bIter = QJSValueIterator(b);
while (aIter.hasNext()) {
if (!bIter.hasNext()) return false;
aIter.next();
bIter.next();
const auto aName = aIter.name();
const auto bName = bIter.name();
const auto aValue = aIter.value();
if (aName == bName) {
const auto bValue = bIter.value();
if (!qjsValueStructuralEq(aValue, bValue, &nextActivePair)) return false;
} else {
if (!b.hasOwnProperty(aName)) return false;
const auto bValue = b.property(aName);
if (!qjsValueStructuralEq(aValue, bValue, &nextActivePair)) return false;
}
}
return !bIter.hasNext();
}
} // namespace
bool ScriptModel::updateValuesUnique(const QList<QJSValue>& newValues) {
auto anyChanges = false;
this->isModifying = true;
void ScriptModel::updateValuesUnique(const QVariantList& newValues) {
this->hasActiveIterators = true;
this->mValues.reserve(newValues.size());
auto iter = this->mValues.begin();
auto newIter = newValues.begin();
auto comparisonValue = [this](const QJSValue& value) {
if (value.hasProperty(this->cmpKey)) return value.property(this->cmpKey);
return value;
};
auto valueCmp = [&, this](const QJSValue& a, const QJSValue& b) {
if (a.strictlyEquals(b)) return true;
const auto aValue = this->cmpKey.isEmpty() ? a : comparisonValue(a);
const auto bValue = this->cmpKey.isEmpty() ? b : comparisonValue(b);
if (this->mComparisonMode == ObjectComparison::Identity) {
return aValue.strictlyEquals(bValue);
} else {
return qjsValueStructuralEq(aValue, bValue);
// TODO: cache this
auto getCmpKey = [this](const QVariant& v) {
if (v.canConvert<QVariantMap>()) {
auto vMap = v.value<QVariantMap>();
if (vMap.contains(this->cmpKey)) {
return vMap.value(this->cmpKey);
}
}
return v;
};
auto eqPredicate = [&](const QJSValue& b) {
return [&](const QJSValue& a) { return valueCmp(a, b); };
auto variantCmp = [&, this](const QVariant& a, const QVariant& b) {
if (!this->cmpKey.isEmpty()) return getCmpKey(a) == getCmpKey(b);
else return a == b;
};
auto eqPredicate = [&](const QVariant& b) {
return [&](const QVariant& a) { return variantCmp(a, b); };
};
while (true) {
@@ -112,7 +49,6 @@ bool ScriptModel::updateValuesUnique(const QList<QJSValue>& newValues) {
this->beginRemoveRows(QModelIndex(), startIndex, endIndex);
this->mValues.erase(iter, this->mValues.end());
this->endRemoveRows();
anyChanges = true;
break;
} else if (iter == this->mValues.end()) {
@@ -123,10 +59,9 @@ bool ScriptModel::updateValuesUnique(const QList<QJSValue>& newValues) {
this->beginInsertRows(QModelIndex(), startIndex, endIndex);
this->mValues.append(newValues.sliced(startIndex));
this->endInsertRows();
anyChanges = true;
break;
} else if (!valueCmp(*newIter, *iter)) {
} else if (!variantCmp(*newIter, *iter)) {
auto oldIter = std::find_if(iter, this->mValues.end(), eqPredicate(*newIter));
if (oldIter != this->mValues.end()) {
@@ -146,7 +81,6 @@ bool ScriptModel::updateValuesUnique(const QList<QJSValue>& newValues) {
this->beginRemoveRows(QModelIndex(), startIndex, index - 1);
iter = this->mValues.erase(startIter, iter);
this->endRemoveRows();
anyChanges = true;
} else {
// Advance iters to capture a whole move sequence as a single operation if possible.
auto oldStartIter = oldIter;
@@ -154,7 +88,7 @@ bool ScriptModel::updateValuesUnique(const QList<QJSValue>& newValues) {
++oldIter;
++newIter;
} while (oldIter != this->mValues.end() && newIter != newValues.end()
&& valueCmp(*oldIter, *newIter));
&& variantCmp(*oldIter, *newIter));
auto index = static_cast<qint32>(std::distance(this->mValues.begin(), iter));
auto oldStartIndex =
@@ -171,7 +105,6 @@ bool ScriptModel::updateValuesUnique(const QList<QJSValue>& newValues) {
iter = this->mValues.begin() + (index + len);
this->endMoveRows();
anyChanges = true;
}
} else {
auto startNewIter = newIter;
@@ -197,56 +130,36 @@ bool ScriptModel::updateValuesUnique(const QList<QJSValue>& newValues) {
std::move_backward(iter, this->mValues.end() - len, this->mValues.end());
iter = std::copy(startNewIter, newIter, iter);
this->endInsertRows();
anyChanges = true;
}
} else if (*newIter != *iter) {
auto first = static_cast<qint32>(std::distance(this->mValues.begin(), iter));
auto index = first;
do {
this->mValues.replace(index, *newIter);
++iter;
++newIter;
++index;
} while (iter != this->mValues.end() && newIter != newValues.end() && *newIter != *iter);
this->dataChanged(
this->index(first, 0, QModelIndex()),
this->index(index - 1, 0, QModelIndex()),
{Qt::UserRole}
);
} else {
++iter;
++newIter;
}
}
for (auto i = 0; i != this->mValues.length();) {
if (this->mValues.at(i).strictlyEquals(newValues.at(i))) {
++i;
continue;
}
auto first = i;
do {
this->mValues.replace(i, newValues.at(i));
++i;
} while (i != this->mValues.length() && !this->mValues.at(i).strictlyEquals(newValues.at(i)));
this->dataChanged(
this->index(first, 0, QModelIndex()),
this->index(i - 1, 0, QModelIndex()),
{Qt::UserRole}
);
anyChanges = true;
}
this->isModifying = false;
if (this->stagedValues.has_value()) {
auto values = *this->stagedValues;
this->stagedValues.reset();
anyChanges |= this->updateValuesUnique(values);
}
return anyChanges;
this->hasActiveIterators = false;
}
void ScriptModel::setValues(const QList<QJSValue>& newValues) {
// Re-entrant modification waits for the original modification to complete to avoid mishandled duplicates.
if (this->isModifying) {
this->stagedValues = newValues;
return;
}
auto changed = this->updateValuesUnique(newValues);
if (changed) emit this->valuesChanged();
void ScriptModel::setValues(const QVariantList& newValues) {
if (newValues == this->mValues) return;
this->updateValuesUnique(newValues);
emit this->valuesChanged();
}
void ScriptModel::setObjectProp(const QString& objectProp) {
@@ -256,12 +169,6 @@ void ScriptModel::setObjectProp(const QString& objectProp) {
emit this->objectPropChanged();
}
void ScriptModel::setComparisonMode(ObjectComparison::Enum comparisonMode) {
if (comparisonMode == this->mComparisonMode) return;
this->mComparisonMode = comparisonMode;
emit this->comparisonModeChanged();
}
qint32 ScriptModel::rowCount(const QModelIndex& parent) const {
if (parent != QModelIndex()) return 0;
return static_cast<qint32>(this->mValues.length());
@@ -269,7 +176,7 @@ qint32 ScriptModel::rowCount(const QModelIndex& parent) const {
QVariant ScriptModel::data(const QModelIndex& index, qint32 role) const {
if (role != Qt::UserRole) return QVariant();
return this->mValues.at(index.row()).toVariant(QJSValue::RetainJSObjects);
return this->mValues.at(index.row());
}
QHash<int, QByteArray> ScriptModel::roleNames() const { return {{Qt::UserRole, "modelData"}}; }
+7 -42
View File
@@ -1,31 +1,10 @@
#pragma once
#include <optional>
#include <qabstractitemmodel.h>
#include <qcontainerfwd.h>
#include <qjsvalue.h>
#include <qobject.h>
#include <qobjectdefs.h>
#include <qproperty.h>
#include <qqmlintegration.h>
#include <qtmetamacros.h>
#include <qtypes.h>
///! @@ScriptModel value comparison mode.
namespace ObjectComparison { // NOLINT
Q_NAMESPACE;
QML_ELEMENT;
enum Enum : quint8 {
/// Compare values using strict JavaScript equality.
Identity = 0,
/// Compare records and arrays structurally.
Structure = 1,
};
Q_ENUM_NS(Enum);
} // namespace ObjectComparison
///! QML model reflecting a javascript expression
/// ScriptModel is a QML [Data Model] that generates model operations based on changes
@@ -61,7 +40,6 @@ Q_ENUM_NS(Enum);
/// [Data Model]: https://doc.qt.io/qt-6/qtquick-modelviewsdata-modelview.html#qml-data-models
class ScriptModel: public QAbstractListModel {
Q_OBJECT;
// clang-format off
/// The list of values to reflect in the model.
/// > [!WARNING] ScriptModel currently only works with lists of *unique* values.
/// > There must not be any duplicates in the given list, or behavior of the model is undefined.
@@ -88,8 +66,7 @@ class ScriptModel: public QAbstractListModel {
/// > values: [...DesktopEntries.applications.values].sort(...)
/// > }
/// > ```
// TODO: use qjsobject for gc/free checks?
Q_PROPERTY(QList<QJSValue> values READ values WRITE setValues NOTIFY valuesChanged);
Q_PROPERTY(QVariantList values READ values WRITE setValues NOTIFY valuesChanged);
/// The property that javascript objects passed into the model will be compared with.
///
/// For example, if `objectProp` is `"myprop"` then `{ myprop: "a", other: "y" }` and
@@ -97,30 +74,21 @@ class ScriptModel: public QAbstractListModel {
///
/// Defaults to `""`, meaning no key.
Q_PROPERTY(QString objectProp READ objectProp WRITE setObjectProp NOTIFY objectPropChanged);
/// How values should be compared. Defaults to `ObjectComparison.Structure`.
///
/// Identity based comparison is faster if usable for a given model, and often
/// achievable with @@objectProp.
Q_PROPERTY(ObjectComparison::Enum comparisonMode READ comparisonMode WRITE setComparisonMode NOTIFY comparisonModeChanged);
// clang-format on
QML_ELEMENT;
public:
[[nodiscard]] QList<QJSValue> values() const {
[[nodiscard]] QVariantList values() const {
auto values = this->mValues;
// If not detached, the QML engine will invalidate iterators in updateValuesUnique.
if (this->isModifying) values.detach();
if (this->hasActiveIterators) values.detach();
return values;
}
void setValues(const QList<QJSValue>& newValues);
void setValues(const QVariantList& newValues);
[[nodiscard]] QString objectProp() const { return this->cmpKey; }
void setObjectProp(const QString& objectProp);
[[nodiscard]] ObjectComparison::Enum comparisonMode() const { return this->mComparisonMode; }
void setComparisonMode(ObjectComparison::Enum comparisonMode);
[[nodiscard]] qint32 rowCount(const QModelIndex& parent) const override;
[[nodiscard]] QVariant data(const QModelIndex& index, qint32 role) const override;
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
@@ -128,14 +96,11 @@ public:
signals:
void valuesChanged();
void objectPropChanged();
void comparisonModeChanged();
private:
QList<QJSValue> mValues;
QVariantList mValues;
QString cmpKey;
ObjectComparison::Enum mComparisonMode = ObjectComparison::Structure;
bool isModifying = false;
std::optional<QList<QJSValue>> stagedValues;
bool hasActiveIterators = false;
bool updateValuesUnique(const QList<QJSValue>& newValues);
void updateValuesUnique(const QVariantList& newValues);
};
-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
View File
@@ -8,4 +8,3 @@ qs_test(transformwatcher transformwatcher.cpp)
qs_test(ringbuffer ringbuf.cpp)
qs_test(scriptmodel scriptmodel.cpp)
qs_test(stacklist stacklist.cpp)
qs_test(objectmodel objectmodel.cpp)
-47
View File
@@ -1,47 +0,0 @@
#include "objectmodel.hpp"
#include <qlist.h>
#include <qobject.h>
#include <qtest.h>
#include <qtestcase.h>
#include "../model.hpp"
void TestObjectModel::diffUpdateInsertRemove() {
QObject a;
QObject b;
QObject c;
QObject d;
auto model = ObjectModel<QObject>(nullptr);
model.insertObject(&a);
model.insertObject(&b);
model.insertObject(&c);
QCOMPARE(model.valueList(), (QList<QObject*> {&a, &b, &c}));
// drop b, append d — relative order of survivors unchanged
model.diffUpdate({&a, &c, &d});
QCOMPARE(model.valueList(), (QList<QObject*> {&a, &c, &d}));
}
void TestObjectModel::diffUpdateReorder() {
QObject a;
QObject b;
QObject c;
QObject d;
auto model = ObjectModel<QObject>(nullptr);
model.insertObject(&d);
model.insertObject(&a);
model.insertObject(&b);
model.insertObject(&c);
QCOMPARE(model.valueList(), (QList<QObject*> {&d, &a, &b, &c}));
// pure permutation: same elements, different order. Previously turned
// [d,a,b,c] into [a,b,c,d,a,b,c] because the old row was never removed
// before inserting at the new index.
model.diffUpdate({&a, &b, &c, &d});
QCOMPARE(model.valueList(), (QList<QObject*> {&a, &b, &c, &d}));
}
QTEST_MAIN(TestObjectModel);
-12
View File
@@ -1,12 +0,0 @@
#pragma once
#include <qobject.h>
#include <qtmetamacros.h>
class TestObjectModel: public QObject {
Q_OBJECT;
private slots:
static void diffUpdateInsertRemove();
static void diffUpdateReorder();
};
+54 -259
View File
@@ -4,12 +4,10 @@
#include <qabstractitemmodeltester.h>
#include <qcontainerfwd.h>
#include <qdebug.h>
#include <qhash.h>
#include <qjsengine.h>
#include <qjsvalue.h>
#include <qlist.h>
#include <qlogging.h>
#include <qobject.h>
#include <qsignalspy.h>
#include <qstring.h>
#include <qtest.h>
#include <qtestcase.h>
@@ -17,6 +15,8 @@
#include "../scriptmodel.hpp"
using OpList = QList<ModelOperation>;
bool ModelOperation::operator==(const ModelOperation& other) const {
return other.operation == this->operation && other.index == this->index
&& other.length == this->length && other.destIndex == this->destIndex;
@@ -44,49 +44,15 @@ QDebug& operator<<(QDebug& debug, const ModelOperation& op) {
return debug;
}
ChangeObserver::ChangeObserver(QAbstractItemModel* model) {
QObject::connect(model, &QAbstractItemModel::rowsInserted, this, &ChangeObserver::onInsert);
QObject::connect(model, &QAbstractItemModel::rowsRemoved, this, &ChangeObserver::onRemove);
QObject::connect(model, &QAbstractItemModel::rowsMoved, this, &ChangeObserver::onMove);
QObject::connect(model, &QAbstractItemModel::dataChanged, this, &ChangeObserver::onDataChanged);
}
void ChangeObserver::onInsert(const QModelIndex& parent, int first, int last) {
QCOMPARE(parent, QModelIndex());
this->mOperations.emplaceBack(ModelOperation::Insert, first, last - first + 1);
}
void ChangeObserver::onRemove(const QModelIndex& parent, int first, int last) {
QCOMPARE(parent, QModelIndex());
this->mOperations.emplaceBack(ModelOperation::Remove, first, last - first + 1);
}
void ChangeObserver::onMove(
const QModelIndex& sourceParent,
int sourceStart,
int sourceEnd,
const QModelIndex& destParent,
int destStart
) {
QCOMPARE(sourceParent, QModelIndex());
QCOMPARE(destParent, QModelIndex());
this->mOperations
.emplaceBack(ModelOperation::Move, sourceStart, sourceEnd - sourceStart + 1, destStart);
}
void ChangeObserver::onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) {
this->mChanges.emplaceBack(topLeft.row(), bottomRight.row());
}
// NOLINTNEXTLINE(misc-use-internal-linkage)
QDebug& operator<<(QDebug& debug, const QJSValueList& list) {
QString str;
QDebug& operator<<(QDebug& debug, const QVariantList& list) {
auto str = QString();
for (const auto& var: list) {
if (var.isString()) {
str += var.toString();
if (var.canConvert<QChar>()) {
str += var.value<QChar>();
} else {
qFatal() << "QJSValueList debug overridden in test";
qFatal() << "QVariantList debug overridden in test";
}
}
@@ -94,18 +60,6 @@ QDebug& operator<<(QDebug& debug, const QJSValueList& list) {
return debug;
}
namespace {
bool qjsValueListsEqual(const QJSValueList& a, const QJSValueList& b) {
if (a.length() != b.length()) return false;
for (auto i = 0; i != a.length(); i++) {
if (!a.at(i).strictlyEquals(b.at(i))) return false;
}
return true;
}
} // namespace
void TestScriptModel::unique_data() {
QTest::addColumn<QString>("oldstr");
QTest::addColumn<QString>("newstr");
@@ -136,7 +90,7 @@ void TestScriptModel::unique_data() {
QTest::addRow("move_overlapping")
<< "ABCDEFG" << "ABDEFCG" << OpList({{ModelOperation::Move, 3, 3, 2}});
// Ensure iterators aren't skipping anything at the end of operations by performing
// Ensure iterators arent skipping anything at the end of operations by performing
// multiple back to back.
QTest::addRow("insert_state_ok") << "ABCDEFG" << "ABXXEFG"
@@ -163,224 +117,65 @@ void TestScriptModel::unique() {
QFETCH(const QString, newstr);
QFETCH(const OpList, operations);
auto strToJsValueList = [](const QString& str) -> QJSValueList {
QJSValueList list;
auto strToVariantList = [](const QString& str) -> QVariantList {
QVariantList list;
for (auto c: str) {
list.emplace_back(QString(c));
list.emplace_back(c);
}
return list;
};
auto oldList = strToJsValueList(oldstr);
auto newList = strToJsValueList(newstr);
auto oldlist = strToVariantList(oldstr);
auto newlist = strToVariantList(newstr);
ScriptModel model;
auto model = ScriptModel();
auto modelTester = QAbstractItemModelTester(&model);
auto observer = ChangeObserver(&model);
model.setValues(oldList);
QVERIFY(qjsValueListsEqual(model.values(), oldList));
OpList actualOperations;
auto onInsert = [&](const QModelIndex& parent, int first, int last) {
QCOMPARE(parent, QModelIndex());
actualOperations << ModelOperation(ModelOperation::Insert, first, last - first + 1);
};
auto onRemove = [&](const QModelIndex& parent, int first, int last) {
QCOMPARE(parent, QModelIndex());
actualOperations << ModelOperation(ModelOperation::Remove, first, last - first + 1);
};
auto onMove = [&](const QModelIndex& sourceParent,
int sourceStart,
int sourceEnd,
const QModelIndex& destParent,
int destStart) {
QCOMPARE(sourceParent, QModelIndex());
QCOMPARE(destParent, QModelIndex());
actualOperations << ModelOperation(
ModelOperation::Move,
sourceStart,
sourceEnd - sourceStart + 1,
destStart
);
};
QObject::connect(&model, &QAbstractItemModel::rowsInserted, &model, onInsert);
QObject::connect(&model, &QAbstractItemModel::rowsRemoved, &model, onRemove);
QObject::connect(&model, &QAbstractItemModel::rowsMoved, &model, onMove);
model.setValues(oldlist);
QCOMPARE_EQ(model.values(), oldlist);
QCOMPARE_EQ(
observer.operations(),
OpList({{ModelOperation::Insert, 0, static_cast<qint32>(oldList.length())}})
actualOperations,
OpList({{ModelOperation::Insert, 0, static_cast<qint32>(oldlist.length())}})
);
observer.clear();
actualOperations.clear();
model.setValues(newList);
QVERIFY(qjsValueListsEqual(model.values(), newList));
QCOMPARE_EQ(observer.operations(), operations);
QCOMPARE_EQ(observer.changes(), ChangeList());
}
void TestScriptModel::structuralEquality() {
ScriptModel model;
QJSEngine engine;
auto object = engine.newObject();
object.setProperty("foo", "bar");
object.setProperty("bar", "baz");
object.setProperty("subobject", engine.newObject());
auto replacement = engine.newObject();
replacement.setProperty("subobject", engine.newObject());
replacement.setProperty("bar", "baz");
replacement.setProperty("foo", "bar");
auto oldList = QList<QJSValue> {object};
auto newList = QList<QJSValue> {replacement};
auto modelTester = QAbstractItemModelTester(&model);
auto observer = ChangeObserver(&model);
model.setValues(oldList);
QVERIFY(qjsValueListsEqual(model.values(), oldList));
QCOMPARE_EQ(
observer.operations(),
OpList({{ModelOperation::Insert, 0, static_cast<qint32>(oldList.length())}})
);
observer.clear();
model.setValues(newList);
QVERIFY(qjsValueListsEqual(model.values(), newList));
QCOMPARE_EQ(observer.operations(), OpList());
auto structurallyEquals = [](const QJSValue& a, const QJSValue& b) {
ScriptModel model;
model.setValues({a});
auto observer = ChangeObserver(&model);
model.setValues({b});
return observer.operations().isEmpty();
};
auto different = engine.newObject();
different.setProperty("foo", "bar");
different.setProperty("bar", "baz");
auto differentSubobject = engine.newObject();
differentSubobject.setProperty("value", 1);
different.setProperty("subobject", differentSubobject);
QVERIFY(!structurallyEquals(object, different));
auto cycleA = engine.newObject();
cycleA.setProperty("self", cycleA);
auto cycleB = engine.newObject();
cycleB.setProperty("self", cycleB);
QVERIFY(!structurallyEquals(cycleA, cycleB));
const auto arrayA = engine.evaluate("[1, { value: 'x' }]");
const auto arrayB = engine.evaluate("[1, { value: 'x' }]");
QVERIFY(structurallyEquals(arrayA, arrayB));
auto sparseArrayA = engine.newArray(1);
auto sparseArrayB = engine.newArray(2);
QVERIFY(!structurallyEquals(sparseArrayA, sparseArrayB));
}
void TestScriptModel::comparisonModes() {
QJSEngine engine;
auto makeValue = [&engine]() {
auto value = engine.newObject();
value.setProperty("nested", engine.newObject());
return value;
};
ScriptModel model;
QCOMPARE_EQ(model.comparisonMode(), ObjectComparison::Structure);
model.setValues({makeValue()});
model.setComparisonMode(ObjectComparison::Identity);
QCOMPARE_EQ(model.comparisonMode(), ObjectComparison::Identity);
auto observer = ChangeObserver(&model);
model.setValues({makeValue()});
QVERIFY(!observer.operations().isEmpty());
model.setComparisonMode(ObjectComparison::Structure);
observer.clear();
model.setValues({makeValue()});
QCOMPARE_EQ(observer.operations(), OpList());
}
void TestScriptModel::dataUpdated_data() {
QTest::addColumn<QString>("oldstr");
QTest::addColumn<QString>("newstr");
QTest::addColumn<OpList>("operations");
QTest::addColumn<ChangeList>("changes");
// Uppercase = same data, Lowercase = data updated
QTest::addRow("insert") << "ABC" << "AXBC" << OpList({{ModelOperation::Insert, 1, 1}})
<< ChangeList();
QTest::addRow("remove") << "AXBC" << "ABC" << OpList({{ModelOperation::Remove, 1, 1}})
<< ChangeList();
QTest::addRow("move") << "ABCD" << "ACBD" << OpList({{ModelOperation::Move, 2, 1, 1}})
<< ChangeList();
QTest::addRow("move_range") << "ABCDEF" << "ADEBCF" << OpList({{ModelOperation::Move, 3, 2, 1}})
<< ChangeList();
QTest::addRow("state_ok") << "ABCDEFG" << "ABEFXYCDG"
<< OpList({
{ModelOperation::Move, 4, 2, 2}, // ABEFCDG
{ModelOperation::Insert, 4, 2}, // ABEFXYCDG
})
<< ChangeList();
QTest::addRow("insert_all_updated") << "ABC" << "aXbc" << OpList({{ModelOperation::Insert, 1, 1}})
<< ChangeList({{0, 0}, {2, 3}});
QTest::addRow("remove_all_updated")
<< "AXBC" << "abc" << OpList({{ModelOperation::Remove, 1, 1}}) << ChangeList({{0, 2}});
QTest::addRow("move_all_updated")
<< "ABCD" << "acbd" << OpList({{ModelOperation::Move, 2, 1, 1}}) << ChangeList({{0, 3}});
QTest::addRow("move_range_all_updated")
<< "ABCDEF" << "adebcf" << OpList({{ModelOperation::Move, 3, 2, 1}}) << ChangeList({{0, 5}});
QTest::addRow("state_ok_all_updated") << "ABCDEFG" << "abefXYcdg"
<< OpList({
{ModelOperation::Move, 4, 2, 2}, // abefCDG
{ModelOperation::Insert, 4, 2}, // abefXYCDG
})
<< ChangeList({{0, 3}, {6, 8}});
QTest::addRow("update_in_place") << "ABC" << "aBc" << OpList() << ChangeList({{0, 0}, {2, 2}});
QTest::addRow("move_updated") << "ABCDEF" << "AdeBCF" << OpList({{ModelOperation::Move, 3, 2, 1}})
<< ChangeList({{1, 2}});
QTest::addRow("move_neighbours_updated")
<< "ABCD" << "aCbd" << OpList({{ModelOperation::Move, 2, 1, 1}})
<< ChangeList({{0, 0}, {2, 3}});
QTest::addRow("move_partially_updated")
<< "ABCDEF" << "aCdEbf" << OpList({{ModelOperation::Move, 2, 3, 1}})
<< ChangeList({{0, 0}, {2, 2}, {4, 5}});
}
void TestScriptModel::dataUpdated() {
QFETCH(const QString, oldstr);
QFETCH(const QString, newstr);
QFETCH(const OpList, operations);
QFETCH(const ChangeList, changes);
QJSEngine engine;
auto version = 0;
QHash<QString, QJSValue> oldValues;
auto strToJsValueList = [&](const QString& str) -> QJSValueList {
QJSValueList list;
for (auto c: str) {
auto key = QString(c.toUpper());
if (c.isUpper() && oldValues.contains(key)) {
list.emplace_back(oldValues.value(key));
continue;
}
auto value = engine.newObject();
value.setProperty("key", key);
value.setProperty("version", version);
list.emplace_back(value);
}
version++;
return list;
};
auto oldList = strToJsValueList(oldstr);
for (const auto& value: oldList) oldValues.insert(value.property("key").toString(), value);
auto newList = strToJsValueList(newstr);
ScriptModel model;
model.setObjectProp("key");
auto modelTester = QAbstractItemModelTester(&model);
model.setValues(oldList);
auto observer = ChangeObserver(&model);
model.setValues(newList);
QVERIFY(qjsValueListsEqual(model.values(), newList));
QCOMPARE_EQ(observer.operations(), operations);
QCOMPARE_EQ(observer.changes(), changes);
model.setValues(newlist);
QCOMPARE_EQ(model.values(), newlist);
QCOMPARE_EQ(actualOperations, operations);
}
QTEST_MAIN(TestScriptModel);
-38
View File
@@ -1,10 +1,7 @@
#pragma once
#include <qabstractitemmodel.h>
#include <qdebug.h>
#include <qlist.h>
#include <qobject.h>
#include <qpair.h>
#include <qtmetamacros.h>
#include <qtypes.h>
@@ -29,37 +26,6 @@ struct ModelOperation {
[[nodiscard]] bool operator==(const ModelOperation& other) const;
};
using OpList = QList<ModelOperation>;
// First and last row of each dataChanged, in emission order.
using ChangeList = QList<QPair<qint32, qint32>>;
class ChangeObserver: public QObject {
public:
explicit ChangeObserver(QAbstractItemModel* model);
[[nodiscard]] const OpList& operations() const { return this->mOperations; }
[[nodiscard]] const ChangeList& changes() const { return this->mChanges; }
void clear() {
this->mOperations.clear();
this->mChanges.clear();
}
private:
void onInsert(const QModelIndex& parent, int first, int last);
void onRemove(const QModelIndex& parent, int first, int last);
void onMove(
const QModelIndex& sourceParent,
int sourceStart,
int sourceEnd,
const QModelIndex& destParent,
int destStart
);
void onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight);
OpList mOperations;
ChangeList mChanges;
};
QDebug& operator<<(QDebug& debug, const ModelOperation& op);
class TestScriptModel: public QObject {
@@ -68,8 +34,4 @@ class TestScriptModel: public QObject {
private slots:
static void unique_data(); // NOLINT
static void unique();
static void structuralEquality();
static void comparisonModes();
static void dataUpdated_data(); // NOLINT
static void dataUpdated();
};
+1 -5
View File
@@ -177,8 +177,6 @@ void QmlToolingSupport::updateToolingFs(
auto fileInfo = QFileInfo(path);
if (!fileInfo.isFile()) continue;
if (scanner.fileIntercepts.contains(path)) continue;
auto spath = linkDir.filePath(name);
auto sFileInfo = QFileInfo(spath);
@@ -207,10 +205,8 @@ void QmlToolingSupport::updateToolingFs(
}
auto spath = linkDir.filePath(name);
QFile::remove(spath);
auto file = QFile(spath);
if (!file.open(QFile::ReadWrite | QFile::Text | QFile::NewOnly)) {
if (!file.open(QFile::ReadWrite | QFile::Text)) {
qCCritical(logTooling) << "Failed to open injected file" << spath;
continue;
}
+1 -1
View File
@@ -145,7 +145,7 @@ void Variants::updateVariants() {
{
if (this->mInstances.contains(variant)) {
continue; // we don't need to recreate this one
continue; // we dont need to recreate this one
}
auto variantMap = QVariantMap();
+1 -1
View File
@@ -43,7 +43,7 @@ class Variants: public Reloadable {
Q_OBJECT;
/// The component to create instances of.
///
/// The delegate should define a `modelData` property that will be populated with a value
/// The delegate should define a `modelData` property that will be popuplated with a value
/// from the @@model.
Q_PROPERTY(QQmlComponent* delegate MEMBER mDelegate);
/// The list of sets of properties to create instances with.
+3 -38
View File
@@ -1,15 +1,12 @@
#include "handler.hpp"
#include <algorithm>
#include <array>
#include <cerrno>
#include <csignal>
#include <cstdio>
#include <cstring>
#include <exception>
#include <cpptrace/basic.hpp>
#include <cpptrace/forward.hpp>
#include <cpptrace/utils.hpp>
#include <qdatastream.h>
#include <qfile.h>
#include <qlogging.h>
@@ -60,12 +57,6 @@ void signalHandler(
siginfo_t* /*info*/, // NOLINT (misc-include-cleaner)
void* /*context*/
) {
// NOLINTBEGIN (misc-include-cleaner)
sigset_t set;
sigfillset(&set);
sigprocmask(SIG_UNBLOCK, &set, nullptr);
// NOLINTEND
if (CrashInfo::INSTANCE.traceFd != -1) {
auto traceBuffer = std::array<cpptrace::frame_ptr, 1024>();
auto frameCount = cpptrace::safe_generate_raw_trace(traceBuffer.data(), traceBuffer.size(), 1);
@@ -73,21 +64,10 @@ void signalHandler(
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
}
write(CrashInfo::INSTANCE.traceFd, &frame, sizeof(cpptrace::safe_object_frame));
}
fail:;
}
// TODO: coredump fork and crash reporter remain as zombies, fix
auto coredumpPid = fork();
if (coredumpPid == 0) {
raise(sig);
@@ -135,6 +115,7 @@ void signalHandler(
perror("Failed to fork and launch crash reporter.\n");
_exit(-1);
} else if (pid == 0) {
// dup to remove CLOEXEC
auto dumpFdStr = std::array<char, 48>();
auto logFdStr = std::array<char, 48>();
@@ -158,21 +139,6 @@ void signalHandler(
}
}
void handleCppTerminate() {
if (auto ptr = std::current_exception()) {
try {
std::rethrow_exception(ptr);
} catch (std::exception& e) {
qFatal().nospace() << "Terminate called with C++ exception ("
<< cpptrace::demangle(typeid(e).name()).data() << "): " << e.what();
} catch (...) {
qFatal() << "Terminate called with non exception object";
}
}
qFatal() << "Terminate called without active C++ exception";
}
} // namespace
void CrashHandler::init() {
@@ -202,6 +168,7 @@ void CrashHandler::init() {
// 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);
@@ -221,8 +188,6 @@ void CrashHandler::init() {
// NOLINTEND (misc-include-cleaner)
std::set_terminate(&handleCppTerminate);
qCInfo(logCrashHandler) << "Crash handler initialized.";
}
+15 -16
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) {
@@ -79,16 +67,22 @@ CrashReporterGui::CrashReporterGui(QString reportFolder, int pid)
if (qtVersionMatches) {
mainLayout->addWidget(
new QLabel("Please open a bug report for this issue on the issue tracker.")
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=crash2.yml",
this
));
mainLayout->addWidget(new ReportLabel("Email:", "[email protected]", this));
auto* buttons = new QWidget(this);
buttons->setMinimumWidth(900);
@@ -118,5 +112,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=crash2.yml")
);
}
void CrashReporterGui::cancel() { QApplication::quit(); }
+32 -10
View File
@@ -6,6 +6,7 @@
#include <cpptrace/basic.hpp>
#include <cpptrace/formatting.hpp>
#include <qapplication.h>
#include <qconfig.h>
#include <qcoreapplication.h>
#include <qdatastream.h>
#include <qdir.h>
@@ -14,19 +15,19 @@
#include <qloggingcategory.h>
#include <qtenvironmentvariables.h>
#include <qtextstream.h>
#include <qtversion.h>
#include <qtypes.h>
#include <sys/sendfile.h>
#include <sys/types.h>
#include <unistd.h>
#include "../core/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/plugin.hpp"
#include "../core/ringbuf.hpp"
#include "build.hpp"
#include "interface.hpp"
namespace {
@@ -170,15 +171,41 @@ void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instance) {
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 << "\n===== Runtime Information =====\n";
stream << "Runtime Qt Version: " << qVersion() << '\n';
stream << "Signal: " << strsignal(crashSignal) << " (" << crashSignal << ")\n"; // NOLINT
stream << "Crashed process ID: " << crashProc << '\n';
stream << "Run ID: " << instance.instanceId << '\n';
stream << "Shell ID: " << instance.shellId << '\n';
stream << "Config Path: " << instance.configPath << '\n';
stream << "\n===== 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 {
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";
}
stream << "\n===== Stacktrace =====\n";
if (stacktrace.empty()) {
stream << "(no trace available)\n";
@@ -231,17 +258,12 @@ void qsCheckCrash(int argc, char** argv) {
);
auto app = QApplication(argc, argv);
auto desktopId =
info.instance.appId.isEmpty() ? QStringLiteral("org.quickshell") : info.instance.appId;
QApplication::setDesktopFileName(desktopId);
QApplication::setDesktopFileName("org.quickshell");
auto crashDir = QsPaths::crashDir(info.instance.instanceId);
qCInfo(logCrashReporter) << "Starting crash reporter...";
// Required platform compatibility hooks
QsEnginePlugin::preinitPluginsOnly();
recordCrashInfo(crashDir, info.instance);
auto gui = CrashReporterGui(crashDir.path(), crashProc);
+1 -1
View File
@@ -20,7 +20,7 @@ qt_add_qml_module(quickshell-dbusmenu
DEPENDENCIES QtQml
)
qs_add_module_deps_light(quickshell-dbusmenu quickshell-core)
qs_add_module_deps_light(quickshell-dbusmenu Quickshell)
install_qml_module(quickshell-dbusmenu)
-7
View File
@@ -301,13 +301,6 @@ DBusMenu::DBusMenu(const QString& service, const QString& path, QObject* parent)
&DBusMenu::onLayoutUpdated
);
QObject::connect(
this->interface,
&DBusMenuInterface::ItemsPropertiesUpdated,
this,
&DBusMenu::onItemPropertiesUpdated
);
this->properties.setInterface(this->interface);
this->properties.updateAllViaGetAll();
}
+1 -1
View File
@@ -21,7 +21,7 @@ qt_add_qml_module(quickshell-io
FileView.qml
)
qs_add_module_deps_light(quickshell-io quickshell-core)
qs_add_module_deps_light(quickshell-io Quickshell)
install_qml_module(quickshell-io)
target_link_libraries(quickshell-io PRIVATE Qt::Quick quickshell-ipc)
+1 -1
View File
@@ -51,7 +51,7 @@ void SplitParser::parseBytes(QByteArray& incoming, QByteArray& buffer) {
return;
}
// make sure we don't miss any delimiters in the buffer if the delimiter changes
// make sure we dont miss any delimiters in the buffer if the delimiter changes
if (this->mSplitMarkerChanged) {
this->mSplitMarkerChanged = false;
this->parseBytes(buffer, buffer);
-1
View File
@@ -495,7 +495,6 @@ void FileView::updateWatchedFiles() {
// If inotify events are sent to the watcher after deletion and deleteLater
// isn't used, a use after free in the QML engine will occur.
if (this->watcher) {
QObject::disconnect(this->watcher, nullptr, this, nullptr);
this->watcher->deleteLater();
this->watcher = nullptr;
}
+4 -4
View File
@@ -29,9 +29,9 @@ class FileViewError: public QObject {
public:
enum Enum : quint8 {
/// No error occurred.
/// No error occured.
Success = 0,
/// An unknown error occurred. Check the logs for details.
/// An unknown error occured. Check the logs for details.
Unknown = 1,
/// The file to read does not exist.
FileNotFound = 2,
@@ -204,7 +204,7 @@ class FileView: public QObject {
///
/// > [!NOTE] This works by creating another file with the desired content, and renaming
/// > it over the existing file if successful.
Q_PROPERTY(bool atomicWrites READ default WRITE default NOTIFY atomicWritesChanged BINDABLE bindableAtomicWrites);
Q_PROPERTY(bool atomicWrites READ default WRITE default NOTIFY blockWritesChanged BINDABLE bindableAtomicWrites);
/// If true (default), read or write errors will be printed to the quickshell logs.
/// If false, all known errors will not be printed.
QSDOC_PROPERTY_OVERRIDE(bool printErrors READ default WRITE default NOTIFY printErrorsChanged);
@@ -227,7 +227,7 @@ class FileView: public QObject {
/// Its state may be saved with @@writeAdapter().
///
/// Currently the only adapter is @@JsonAdapter.
Q_PROPERTY(qs::io::FileViewAdapter* adapter READ adapter WRITE setAdapter NOTIFY adapterChanged);
Q_PROPERTY(FileViewAdapter* adapter READ adapter WRITE setAdapter NOTIFY adapterChanged);
QSDOC_HIDE Q_PROPERTY(QString __path READ path WRITE setPath NOTIFY pathChanged);
QSDOC_HIDE Q_PROPERTY(QString __text READ text NOTIFY internalTextChanged);
+154 -67
View File
@@ -1,94 +1,181 @@
#include "ipc.hpp"
#include <cstring>
#include <utility>
#include <qcolor.h>
#include <qmetatype.h>
#include <qobjectdefs.h>
#include <qtypes.h>
#include <qvariant.h>
namespace qs::io::ipc {
bool IpcValueSlot::isSupported(const QMetaType& type) { return IpcValueSlot::typeName(type); }
const VoidIpcType VoidIpcType::INSTANCE {};
const StringIpcType StringIpcType::INSTANCE {};
const IntIpcType IntIpcType::INSTANCE {};
const BoolIpcType BoolIpcType::INSTANCE {};
const DoubleIpcType DoubleIpcType::INSTANCE {};
const ColorIpcType ColorIpcType::INSTANCE {};
const char* IpcValueSlot::typeName(const QMetaType& type) {
switch (type.id()) {
case QMetaType::Void: return "void";
case QMetaType::QString: return "string";
case QMetaType::Int: return "int";
case QMetaType::Bool: return "bool";
case QMetaType::Double: return "real";
case QMetaType::QColor: return "color";
default: return nullptr;
void* IpcType::copyStorage(const void* data) const {
auto* storage = this->createStorage();
memcpy(storage, data, this->size());
return storage;
}
const IpcType* IpcType::ipcType(const QMetaType& metaType) {
if (metaType.id() == QMetaType::Void) return &VoidIpcType::INSTANCE;
if (metaType.id() == QMetaType::QString) return &StringIpcType::INSTANCE;
if (metaType.id() == QMetaType::Int) return &IntIpcType::INSTANCE;
if (metaType.id() == QMetaType::Bool) return &BoolIpcType::INSTANCE;
if (metaType.id() == QMetaType::Double) return &DoubleIpcType::INSTANCE;
if (metaType.id() == QMetaType::QColor) return &ColorIpcType::INSTANCE;
return nullptr;
}
IpcTypeSlot::IpcTypeSlot(IpcTypeSlot&& other) noexcept { *this = std::move(other); }
IpcTypeSlot& IpcTypeSlot::operator=(IpcTypeSlot&& other) noexcept {
this->mType = other.mType;
this->storage = other.storage;
other.mType = nullptr;
other.storage = nullptr;
return *this;
}
IpcTypeSlot::~IpcTypeSlot() { this->replace(nullptr); }
const IpcType* IpcTypeSlot::type() const { return this->mType; }
void* IpcTypeSlot::get() {
if (this->storage == nullptr) {
this->storage = this->mType->createStorage();
}
return this->storage;
}
QGenericArgument IpcTypeSlot::asGenericArgument() {
if (this->mType) {
return QGenericArgument(this->mType->genericArgumentName(), this->get());
} else {
return QGenericArgument();
}
}
const char* IpcValueSlot::name() const {
return this->mValue.isValid() ? IpcValueSlot::typeName(this->mValue.metaType()) : "void";
}
bool IpcValueSlot::isVoid() const { return !this->mValue.isValid(); }
const QVariant& IpcValueSlot::value() const { return this->mValue; }
bool IpcValueSlot::setString(const QString& string) {
switch (this->mValue.typeId()) {
case QMetaType::QString: this->mValue = QVariant::fromValue(string); return true;
case QMetaType::Int: {
auto ok = false;
auto value = string.toInt(&ok);
if (ok) this->mValue = QVariant::fromValue(value);
return ok;
}
case QMetaType::Bool: {
if (string == "true" || string == "false") {
this->mValue = QVariant::fromValue(string == "true");
return true;
}
auto ok = false;
auto value = string.toInt(&ok);
if (ok) this->mValue = QVariant::fromValue(value != 0);
return ok;
}
case QMetaType::Double: {
auto ok = false;
auto value = string.toDouble(&ok);
if (ok) this->mValue = QVariant::fromValue(value);
return ok;
}
case QMetaType::QColor: {
auto value = QColor::fromString(string);
if (!value.isValid() && !string.startsWith('#')) value = QColor::fromString('#' % string);
if (value.isValid()) this->mValue = QVariant::fromValue(value);
return value.isValid();
}
default: return false;
QGenericReturnArgument IpcTypeSlot::asGenericReturnArgument() {
if (this->mType) {
return QGenericReturnArgument(this->mType->genericArgumentName(), this->get());
} else {
return QGenericReturnArgument();
}
}
void IpcValueSlot::setValue(QVariant value) { this->mValue = std::move(value); }
QString IpcValueSlot::toString() const {
switch (this->mValue.typeId()) {
case QMetaType::UnknownType: return "";
case QMetaType::QString: return this->mValue.value<QString>();
case QMetaType::Int: return QString::number(this->mValue.value<int>());
case QMetaType::Bool: return this->mValue.value<bool>() ? "true" : "false";
case QMetaType::Double: return QString::number(this->mValue.value<double>());
case QMetaType::QColor: return this->mValue.value<QColor>().name(QColor::HexArgb);
default: return "";
void IpcTypeSlot::replace(void* value) {
if (this->storage != nullptr) {
this->mType->destroyStorage(this->storage);
}
this->storage = value;
}
QGenericArgument IpcValueSlot::asGenericArgument() const {
return QGenericArgument(this->mValue.metaType().name(), this->mValue.constData());
void IpcTypeSlot::replace(const QVariant& value) {
this->replace(this->mType->copyStorage(value.constData()));
}
QGenericReturnArgument IpcValueSlot::asGenericReturnArgument() {
if (!this->mValue.isValid()) return QGenericReturnArgument();
return QGenericReturnArgument(this->mValue.metaType().name(), this->mValue.data());
const char* VoidIpcType::name() const { return "void"; }
const char* VoidIpcType::genericArgumentName() const { return "void"; }
qsizetype VoidIpcType::size() const { return 0; }
// string
const char* StringIpcType::name() const { return "string"; }
const char* StringIpcType::genericArgumentName() const { return "QString"; }
qsizetype StringIpcType::size() const { return sizeof(QString); }
void* StringIpcType::fromString(const QString& string) const { return new QString(string); }
QString StringIpcType::toString(void* slot) const { return *static_cast<QString*>(slot); }
void* StringIpcType::createStorage() const { return new QString(); }
void StringIpcType::destroyStorage(void* slot) const { delete static_cast<QString*>(slot); }
// int
const char* IntIpcType::name() const { return "int"; }
const char* IntIpcType::genericArgumentName() const { return "int"; }
qsizetype IntIpcType::size() const { return sizeof(int); }
void* IntIpcType::fromString(const QString& string) const {
auto ok = false;
auto v = string.toInt(&ok);
return ok ? new int(v) : nullptr;
}
QString IntIpcType::toString(void* slot) const { return QString::number(*static_cast<int*>(slot)); }
void* IntIpcType::createStorage() const { return new int(); }
void IntIpcType::destroyStorage(void* slot) const { delete static_cast<int*>(slot); }
// bool
const char* BoolIpcType::name() const { return "bool"; }
const char* BoolIpcType::genericArgumentName() const { return "bool"; }
qsizetype BoolIpcType::size() const { return sizeof(bool); }
void* BoolIpcType::fromString(const QString& string) const {
if (string == "true") return new bool(true);
if (string == "false") return new bool(false);
auto isInt = false;
auto iv = string.toInt(&isInt);
return isInt ? new bool(iv != 0) : nullptr;
}
QString BoolIpcType::toString(void* slot) const {
return *static_cast<bool*>(slot) ? "true" : "false";
}
void* BoolIpcType::createStorage() const { return new bool(); }
void BoolIpcType::destroyStorage(void* slot) const { delete static_cast<bool*>(slot); }
// double
const char* DoubleIpcType::name() const { return "real"; }
const char* DoubleIpcType::genericArgumentName() const { return "double"; }
qsizetype DoubleIpcType::size() const { return sizeof(double); }
void* DoubleIpcType::fromString(const QString& string) const {
auto ok = false;
auto v = string.toDouble(&ok);
return ok ? new double(v) : nullptr;
}
QString DoubleIpcType::toString(void* slot) const {
return QString::number(*static_cast<double*>(slot));
}
void* DoubleIpcType::createStorage() const { return new double(); }
void DoubleIpcType::destroyStorage(void* slot) const { delete static_cast<double*>(slot); }
// color
const char* ColorIpcType::name() const { return "color"; }
const char* ColorIpcType::genericArgumentName() const { return "QColor"; }
qsizetype ColorIpcType::size() const { return sizeof(QColor); }
void* ColorIpcType::fromString(const QString& string) const {
auto color = QColor::fromString(string);
if (!color.isValid() && !string.startsWith('#')) {
color = QColor::fromString('#' % string);
}
return color.isValid() ? new QColor(color) : nullptr;
}
QString ColorIpcType::toString(void* slot) const {
return static_cast<QColor*>(slot)->name(QColor::HexArgb);
}
void* ColorIpcType::createStorage() const { return new bool(); }
void ColorIpcType::destroyStorage(void* slot) const { delete static_cast<bool*>(slot); }
QString WireFunctionDefinition::toString() const {
QString paramString;
for (const auto& [name, type]: this->arguments) {
+110 -15
View File
@@ -1,35 +1,130 @@
#pragma once
#include <qcontainerfwd.h>
#include <qmetatype.h>
#include <qobjectdefs.h>
#include <qtclasshelpermacros.h>
#include <qtypes.h>
#include <qvariant.h>
#include "../ipc/ipc.hpp"
namespace qs::io::ipc {
class IpcValueSlot {
class IpcTypeSlot;
class IpcType {
public:
IpcValueSlot() = default;
explicit IpcValueSlot(QMetaType type): mValue(type) {}
IpcType() = default;
virtual ~IpcType() = default;
IpcType(const IpcType&) = default;
IpcType(IpcType&&) = default;
IpcType& operator=(const IpcType&) = default;
IpcType& operator=(IpcType&&) = default;
[[nodiscard]] static bool isSupported(const QMetaType& type);
[[nodiscard]] static const char* typeName(const QMetaType& type);
[[nodiscard]] const char* name() const;
[[nodiscard]] bool isVoid() const;
[[nodiscard]] const QVariant& value() const;
[[nodiscard]] virtual const char* name() const = 0;
[[nodiscard]] virtual const char* genericArgumentName() const = 0;
[[nodiscard]] virtual qsizetype size() const = 0;
[[nodiscard]] virtual void* fromString(const QString& /*string*/) const { return nullptr; }
[[nodiscard]] virtual QString toString(void* /*slot*/) const { return ""; }
[[nodiscard]] virtual void* createStorage() const { return nullptr; }
virtual void destroyStorage(void* /*slot*/) const {}
void* copyStorage(const void* data) const;
bool setString(const QString& string);
void setValue(QVariant value);
[[nodiscard]] QString toString() const;
static const IpcType* ipcType(const QMetaType& metaType);
};
[[nodiscard]] QGenericArgument asGenericArgument() const;
class IpcTypeSlot {
public:
explicit IpcTypeSlot(const IpcType* type = nullptr): mType(type) {}
~IpcTypeSlot();
Q_DISABLE_COPY(IpcTypeSlot);
IpcTypeSlot(IpcTypeSlot&& other) noexcept;
IpcTypeSlot& operator=(IpcTypeSlot&& other) noexcept;
[[nodiscard]] const IpcType* type() const;
[[nodiscard]] void* get();
[[nodiscard]] QGenericArgument asGenericArgument();
[[nodiscard]] QGenericReturnArgument asGenericReturnArgument();
void replace(void* value);
void replace(const QVariant& value);
private:
QVariant mValue;
const IpcType* mType = nullptr;
void* storage = nullptr;
};
class VoidIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
static const VoidIpcType INSTANCE;
};
class StringIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
[[nodiscard]] void* fromString(const QString& string) const override;
[[nodiscard]] QString toString(void* slot) const override;
[[nodiscard]] void* createStorage() const override;
void destroyStorage(void* slot) const override;
static const StringIpcType INSTANCE;
};
class IntIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
[[nodiscard]] void* fromString(const QString& string) const override;
[[nodiscard]] QString toString(void* slot) const override;
[[nodiscard]] void* createStorage() const override;
void destroyStorage(void* slot) const override;
static const IntIpcType INSTANCE;
};
class BoolIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
[[nodiscard]] void* fromString(const QString& string) const override;
[[nodiscard]] QString toString(void* slot) const override;
[[nodiscard]] void* createStorage() const override;
void destroyStorage(void* slot) const override;
static const BoolIpcType INSTANCE;
};
class DoubleIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
[[nodiscard]] void* fromString(const QString& string) const override;
[[nodiscard]] QString toString(void* slot) const override;
[[nodiscard]] void* createStorage() const override;
void destroyStorage(void* slot) const override;
static const DoubleIpcType INSTANCE;
};
class ColorIpcType: public IpcType {
public:
[[nodiscard]] const char* name() const override;
[[nodiscard]] const char* genericArgumentName() const override;
[[nodiscard]] qsizetype size() const override;
[[nodiscard]] void* fromString(const QString& string) const override;
[[nodiscard]] QString toString(void* slot) const override;
[[nodiscard]] void* createStorage() const override;
void destroyStorage(void* slot) const override;
static const ColorIpcType INSTANCE;
};
struct WireFunctionDefinition {
+3 -3
View File
@@ -173,7 +173,7 @@ void StringCallCommand::exec(qs::ipc::IpcServerConnection* conn) const {
func->invoke(handler, storage);
resp << Completed {
.isVoid = func->returnType.isVoid(),
.isVoid = func->returnType == &VoidIpcType::INSTANCE,
.returnValue = storage.getReturnStr(),
};
} else {
@@ -269,11 +269,11 @@ void StringPropReadCommand::exec(qs::ipc::IpcServerConnection* conn) const {
return;
}
auto slot = IpcValueSlot(prop->type);
auto slot = IpcTypeSlot(prop->type);
prop->read(handler, slot);
resp << PropertyValue {
.value = slot.toString(),
.value = slot.type()->toString(slot.get()),
};
} else {
conn->respond(StringCallResponse(NoCurrentGeneration()));
+39 -36
View File
@@ -34,8 +34,9 @@ bool IpcFunction::resolve(QString& error) {
for (auto i = 0; i < this->method.parameterCount(); i++) {
const auto& metaType = this->method.parameterMetaType(i);
const auto* type = IpcType::ipcType(metaType);
if (!IpcValueSlot::isSupported(metaType)) {
if (type == nullptr) {
error = QString("Type of argument %1 (%2: %3) cannot be used across IPC.")
.arg(i + 1)
.arg(this->method.parameterNames().value(i))
@@ -44,20 +45,23 @@ bool IpcFunction::resolve(QString& error) {
return false;
}
this->argumentTypes.emplaceBack(metaType);
this->argumentTypes.append(type);
}
auto metaType = this->method.returnMetaType();
const auto& metaType = this->method.returnMetaType();
const auto* type = IpcType::ipcType(metaType);
// void and var get mixed by qml engine in return types
if (metaType.id() == QMetaType::QVariant) metaType = QMetaType::fromType<void>();
if (type == nullptr) {
// void and var get mixed by qml engine in return types
if (metaType.id() == QMetaType::QVariant) type = &VoidIpcType::INSTANCE;
if (!IpcValueSlot::isSupported(metaType)) {
error = QString("Return type (%1) cannot be used across IPC.").arg(metaType.name());
return false;
if (type == nullptr) {
error = QString("Return type (%1) cannot be used across IPC.").arg(metaType.name());
return false;
}
}
this->returnType = IpcValueSlot(metaType);
this->returnType = type;
return true;
}
@@ -88,53 +92,52 @@ QString IpcFunction::toString() const {
QString paramString;
auto paramNames = this->method.parameterNames();
for (auto i = 0; i < this->argumentTypes.length(); i++) {
paramString += paramNames.value(i) % ": " % this->argumentTypes.value(i).name();
paramString += paramNames.value(i) % ": " % this->argumentTypes.value(i)->name();
if (i + 1 != this->argumentTypes.length()) {
paramString += ", ";
}
}
return "function " % this->method.name() % '(' % paramString % "): " % this->returnType.name();
return "function " % this->method.name() % '(' % paramString % "): " % this->returnType->name();
}
WireFunctionDefinition IpcFunction::wireDef() const {
WireFunctionDefinition wire;
wire.name = this->method.name();
wire.returnType = this->returnType.name();
wire.returnType = this->returnType->name();
auto paramNames = this->method.parameterNames();
for (auto i = 0; i < this->argumentTypes.length(); i++) {
wire.arguments += qMakePair(paramNames.value(i), this->argumentTypes.value(i).name());
wire.arguments += qMakePair(paramNames.value(i), this->argumentTypes.value(i)->name());
}
return wire;
}
bool IpcProperty::resolve(QString& error) {
const auto& metaType = this->property.metaType();
this->type = IpcType::ipcType(this->property.metaType());
if (!IpcValueSlot::isSupported(metaType)) {
error = QString("Type %1 cannot be used across IPC.").arg(metaType.name());
if (!this->type) {
error = QString("Type %1 cannot be used across IPC.").arg(this->property.metaType().name());
return false;
}
this->type = metaType;
return true;
}
void IpcProperty::read(QObject* target, IpcValueSlot& slot) const {
slot.setValue(this->property.read(target));
void IpcProperty::read(QObject* target, IpcTypeSlot& slot) const {
slot.replace(this->property.read(target));
}
QString IpcProperty::toString() const {
return QString("property ") % this->property.name() % ": " % IpcValueSlot::typeName(this->type);
return QString("property ") % this->property.name() % ": " % this->type->name();
}
WirePropertyDefinition IpcProperty::wireDef() const {
WirePropertyDefinition wire;
wire.name = this->property.name();
wire.type = IpcValueSlot::typeName(this->type);
wire.type = this->type->name();
return wire;
}
@@ -214,15 +217,21 @@ IpcCallStorage::IpcCallStorage(const IpcFunction& function): returnSlot(function
}
bool IpcCallStorage::setArgumentStr(size_t i, const QString& value) {
return this->argumentSlots.at(i).setString(value);
auto& slot = this->argumentSlots.at(i);
auto* data = slot.type()->fromString(value);
slot.replace(data);
return data != nullptr;
}
QString IpcCallStorage::getReturnStr() { return this->returnSlot.toString(); }
QString IpcCallStorage::getReturnStr() {
return this->returnSlot.type()->toString(this->returnSlot.get());
}
IpcHandler::~IpcHandler() {
if (this->registeredState.enabled) {
this->targetState.enabled = false;
this->updateRegistration();
this->updateRegistration(true);
}
}
@@ -301,34 +310,28 @@ void IpcHandler::onSignalTriggered(const QString& signal, const QString& value)
-> triggered(this->registeredState.target, signal, value);
}
void IpcHandler::updateRegistration() {
void IpcHandler::updateRegistration(bool destroying) {
if (!this->complete) return;
IpcHandlerRegistry* registry = nullptr;
if (this->registeredState.enabled) {
registry = this->registry.get();
} else {
auto* generation = EngineGeneration::findObjectGeneration(this);
auto* generation = EngineGeneration::findObjectGeneration(this);
if (!generation) {
if (!generation) {
if (!destroying) {
qmlWarning(this) << "Unable to identify engine generation, cannot register.";
return;
}
registry = IpcHandlerRegistry::forGeneration(generation);
return;
}
if (!registry) return;
auto* registry = IpcHandlerRegistry::forGeneration(generation);
if (this->registeredState.enabled) {
registry->deregisterHandler(this);
this->registry = nullptr;
qCDebug(logIpcHandler) << "Deregistered" << this << "from registry" << registry;
}
if (this->targetState.enabled && !this->targetState.target.isEmpty()) {
registry->registerHandler(this);
this->registry = registry;
qCDebug(logIpcHandler) << "Registered" << this << "to registry" << registry;
}
}
+9 -16
View File
@@ -11,7 +11,6 @@
#include <qmetaobject.h>
#include <qobject.h>
#include <qobjectdefs.h>
#include <qpointer.h>
#include <qqmlintegration.h>
#include <qqmlparserstatus.h>
#include <qtclasshelpermacros.h>
@@ -38,8 +37,8 @@ public:
[[nodiscard]] WireFunctionDefinition wireDef() const;
QMetaMethod method;
QVector<IpcValueSlot> argumentTypes;
IpcValueSlot returnType;
QVector<const IpcType*> argumentTypes;
const IpcType* returnType = nullptr;
};
class IpcCallStorage {
@@ -50,8 +49,8 @@ public:
[[nodiscard]] QString getReturnStr();
private:
std::vector<IpcValueSlot> argumentSlots;
IpcValueSlot returnSlot;
std::vector<IpcTypeSlot> argumentSlots;
IpcTypeSlot returnSlot;
friend class IpcFunction;
};
@@ -61,13 +60,13 @@ public:
explicit IpcProperty(QMetaProperty property): property(property) {}
bool resolve(QString& error);
void read(QObject* target, IpcValueSlot& slot) const;
void read(QObject* target, IpcTypeSlot& slot) const;
[[nodiscard]] QString toString() const;
[[nodiscard]] WirePropertyDefinition wireDef() const;
QMetaProperty property;
QMetaType type;
const IpcType* type = nullptr;
};
class IpcSignalListener: public QObject {
@@ -186,7 +185,7 @@ class IpcHandlerRegistry;
///
/// function getRadius(): int { return rect.radius; }
///
/// signal radiusChanged(newRadius: int);
/// signal radiusChanged(newRadius: int);
/// }
/// }
/// ```
@@ -255,7 +254,7 @@ public slots:
void onSignalTriggered(const QString& signal, const QString& value) const;
private:
void updateRegistration();
void updateRegistration(bool destroying = false);
struct RegistrationState {
explicit RegistrationState(bool enabled = false): enabled(enabled) {}
@@ -272,16 +271,10 @@ private:
QHash<QString, IpcProperty> propertyMap;
QHash<QString, IpcSignal> signalMap;
QPointer<IpcHandlerRegistry> registry;
friend class IpcHandlerRegistry;
};
class IpcHandlerRegistry
: public QObject
, public EngineGenerationExt {
Q_OBJECT;
class IpcHandlerRegistry: public EngineGenerationExt {
public:
static IpcHandlerRegistry* forGeneration(EngineGeneration* generation);
+21 -58
View File
@@ -1,13 +1,11 @@
#include "jsonadapter.hpp"
#include <qassociativeiterable.h>
#include <qcontainerfwd.h>
#include <qjsonarray.h>
#include <qjsondocument.h>
#include <qjsonobject.h>
#include <qjsonvalue.h>
#include <qjsvalue.h>
#include <qmetacontainer.h>
#include <qmetaobject.h>
#include <qnamespace.h>
#include <qobject.h>
@@ -16,7 +14,6 @@
#include <qqmlengine.h>
#include <qqmlinfo.h>
#include <qqmllist.h>
#include <qsequentialiterable.h>
#include <qstringview.h>
#include <qvariant.h>
@@ -134,22 +131,13 @@ QJsonObject JsonAdapter::serializeRec(const QObject* obj, const QMetaObject* bas
}
json.insert(prop.name(), array);
} else if (val.canConvert<QJSValue>()) {
auto variant = val.value<QJSValue>().toVariant();
auto jv = QJsonValue::fromVariant(variant);
json.insert(prop.name(), jv);
} else {
if (val.canConvert<QJSValue>()) val = val.value<QJSValue>().toVariant();
auto jsonVal = QJsonValue::fromVariant(val);
if (jsonVal.isNull() && !val.isNull() && val.isValid()) {
if (val.canConvert<QAssociativeIterable>()) {
val.convert(QMetaType::fromType<QVariantMap>());
} else if (val.canConvert<QSequentialIterable>()) {
val.convert(QMetaType::fromType<QVariantList>());
}
jsonVal = QJsonValue::fromVariant(val);
}
json.insert(prop.name(), jsonVal);
auto jv = QJsonValue::fromVariant(val);
json.insert(prop.name(), jv);
}
}
}
@@ -166,16 +154,14 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM
auto jval = json.value(prop.name());
if (prop.metaType() == QMetaType::fromType<QVariant>()) {
auto newVariant = jval.toVariant();
auto oldValue = prop.read(obj);
auto oldVariant =
oldValue.canConvert<QJSValue>() ? oldValue.value<QJSValue>().toVariant() : oldValue;
auto variant = jval.toVariant();
auto oldValue = prop.read(this).value<QJSValue>();
// Calling prop.write with a new QJSValue will cause a property update
// even if content is identical.
if (newVariant != oldVariant) {
auto jsValue = qmlEngine(this)->fromVariant<QJSValue>(newVariant);
prop.write(obj, QVariant::fromValue(jsValue));
if (jval.toVariant() != oldValue.toVariant()) {
auto jsValue = qmlEngine(this)->fromVariant<QJSValue>(jval.toVariant());
prop.write(this, QVariant::fromValue(jsValue));
}
} else if (QMetaType::canView(prop.metaType(), QMetaType::fromType<JsonObject*>())) {
// FIXME: This doesn't support creating descendants of JsonObject, as QMetaType.metaObject()
@@ -210,7 +196,7 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM
QMetaType::fromType<QQmlListProperty<JsonObject>>()
))
{
auto pval = prop.read(obj);
auto pval = prop.read(this);
if (pval.canConvert<QQmlListProperty<JsonObject>>()) {
auto lp = pval.value<QQmlListProperty<JsonObject>>();
@@ -225,15 +211,15 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM
const auto& jsonValue = array.at(i);
if (jsonValue.isObject()) {
if (isNew) {
// FIXME: should be the type inside the QQmlListProperty but how can we get that?
currentValue = static_cast<JsonObject*>(QMetaType::fromType<JsonObject>().create());
currentValue->setParent(this);
this->createdObjects.push_back(currentValue);
} else {
currentValue = lp.at(&lp, i);
if (this->oldCreatedObjects.removeOne(currentValue)) {
this->createdObjects.push_back(currentValue);
}
} else {
// FIXME: should be the type inside the QQmlListProperty but how can we get that?
currentValue = static_cast<JsonObject*>(QMetaType::fromType<JsonObject>().create());
currentValue->setParent(this);
this->createdObjects.push_back(currentValue);
}
this->deserializeRec(
@@ -261,35 +247,12 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM
}
} else {
auto variant = jval.toVariant();
auto convVariant = variant;
if (convVariant.convert(prop.metaType())) {
prop.write(obj, convVariant);
if (variant.convert(prop.metaType())) {
prop.write(obj, variant);
} else {
auto pval = prop.read(obj);
if (variant.canConvert<QSequentialIterable>() && pval.canView<QSequentialIterable>()) {
auto targetv = QVariant(pval.metaType());
auto target = targetv.view<QSequentialIterable>().metaContainer();
auto valueType = target.valueMetaType();
auto i = 0;
for (QVariant item: variant.value<QSequentialIterable>()) {
if (item.convert(valueType)) {
target.addValueAtEnd(targetv.data(), item.constData());
} else {
qmlWarning(this) << "Failed to deserialize list member " << i << " of property "
<< prop.name() << ": expected " << valueType.name() << " but got "
<< item.typeName();
}
++i;
}
prop.write(obj, targetv);
} else {
qmlWarning(this) << "Failed to deserialize property " << prop.name() << ": expected "
<< prop.metaType().name() << " but got "
<< jval.toVariant().typeName();
}
qmlWarning(this) << "Failed to deserialize property " << prop.name() << ": expected "
<< prop.metaType().name() << " but got " << jval.toVariant().typeName();
}
}
}
-2
View File
@@ -272,7 +272,6 @@ void Process::onStarted() {
}
void Process::onFinished(qint32 exitCode, QProcess::ExitStatus exitStatus) {
QObject::disconnect(this->process, nullptr, this, nullptr);
this->process->deleteLater();
this->process = nullptr;
if (this->mStdoutParser) this->mStdoutParser->streamEnded(this->stdoutBuffer);
@@ -291,7 +290,6 @@ void Process::onErrorOccurred(QProcess::ProcessError error) {
if (error == QProcess::FailedToStart) { // other cases should be covered by other events
qWarning() << "Process failed to start, likely because the binary could not be found. Command:"
<< this->mCommand;
QObject::disconnect(this->process, nullptr, this, nullptr);
this->process->deleteLater();
this->process = nullptr;
emit this->runningChanged();
+1 -14
View File
@@ -17,11 +17,7 @@
QS_LOGGING_CATEGORY(logSocket, "quickshell.io.socket", QtWarningMsg);
void Socket::setSocket(QLocalSocket* socket) {
if (this->socket != nullptr) {
QObject::disconnect(this->socket, nullptr, this, nullptr);
this->socket->deleteLater();
}
if (this->socket != nullptr) this->socket->deleteLater();
this->socket = socket;
if (socket != nullptr) {
@@ -61,7 +57,6 @@ void Socket::onSocketDisconnected() {
qCDebug(logSocket) << "Socket disconnected:" << this;
this->connected = false;
this->disconnecting = false;
QObject::disconnect(this->socket, nullptr, this, nullptr);
this->socket->deleteLater();
this->socket = nullptr;
this->buffer.clear();
@@ -73,13 +68,6 @@ void Socket::onSocketDisconnected() {
void Socket::onSocketError(QLocalSocket::LocalSocketError error) {
qCWarning(logSocket) << "Socket error for" << this << error;
emit this->error(error);
if (!this->connected) {
QObject::disconnect(this->socket, nullptr, this, nullptr);
this->socket->deleteLater();
this->socket = nullptr;
this->disconnecting = false;
}
}
bool Socket::isConnected() const { return this->connected; }
@@ -203,7 +191,6 @@ void SocketServer::disableServer() {
}
this->mSockets.clear();
QObject::disconnect(this->server, nullptr, this, nullptr);
this->server->close();
this->server->deleteLater();
this->server = nullptr;
+2 -2
View File
@@ -96,7 +96,7 @@ class SocketServer: public Reloadable {
Q_OBJECT;
/// If the socket server is currently active. Defaults to false.
///
/// Setting this to false will destroy all active connections and delete
/// Setting this to false will destory all active connections and delete
/// the socket file on disk.
///
/// If path is empty setting this property will have no effect.
@@ -105,7 +105,7 @@ class SocketServer: public Reloadable {
///
/// Setting this property while the server is active will have no effect.
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged);
/// Connection handler component. Must create a @@Socket.
/// Connection handler component. Must creeate a @@Socket.
///
/// The created socket should not set @@connected or @@path or the incoming
/// socket connection will be dropped (they will be set by the socket server.)
+4 -16
View File
@@ -3,7 +3,6 @@
#include <variant>
#include <qbuffer.h>
#include <qcoreapplication.h>
#include <qlocalserver.h>
#include <qlocalsocket.h>
#include <qlogging.h>
@@ -62,7 +61,6 @@ IpcServerConnection::IpcServerConnection(QLocalSocket* socket, IpcServer* server
void IpcServerConnection::onDisconnected() {
qCInfo(logIpc) << "IPC connection disconnected" << this;
QObject::disconnect(this->socket, nullptr, this, nullptr);
this->deleteLater();
}
@@ -106,19 +104,11 @@ IpcClient::IpcClient(const QString& path) {
bool IpcClient::isConnected() const { return this->socket.isValid(); }
void IpcClient::waitForConnected() { this->socket.waitForConnected(); }
void IpcClient::waitForDisconnected() { this->socket.waitForDisconnected(); }
void IpcClient::waitForDisconnected() {
this->waitingForDisconnect = true;
this->socket.waitForDisconnected();
}
void IpcClient::kill() { this->sendMessage(IpcCommand(IpcKillCommand())); }
void IpcClient::kill() {
this->sendMessage(IpcCommand(IpcKillCommand()));
this->waitForDisconnected();
}
void IpcClient::onError(QLocalSocket::LocalSocketError error) const {
if (this->waitingForDisconnect && error == QLocalSocket::PeerClosedError) return;
void IpcClient::onError(QLocalSocket::LocalSocketError error) {
qCCritical(logIpc) << "Socket Error" << error;
}
@@ -137,9 +127,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
+2 -5
View File
@@ -19,7 +19,7 @@
template <typename... Types>
constexpr void assertSerializable() {
// monostate being zero ensures transactional reads won't break
// monostate being zero ensures transactional reads wont break
static_assert(
std::is_same_v<std::variant_alternative_t<0, std::variant<Types...>>, std::monostate>,
"Serialization of variants without std::monostate at index 0 is disallowed."
@@ -216,10 +216,7 @@ signals:
void disconnected();
private slots:
void onError(QLocalSocket::LocalSocketError error) const;
private:
bool waitingForDisconnect = false;
static void onError(QLocalSocket::LocalSocketError error);
};
} // namespace qs::ipc
+58 -92
View File
@@ -2,7 +2,6 @@
#include <array>
#include <cerrno>
#include <cstring>
#include <functional>
#include <utility>
#include <qconfig.h>
@@ -13,6 +12,7 @@
#include <qdebug.h>
#include <qdir.h>
#include <qfileinfo.h>
#include <qguiapplication.h>
#include <qjsonarray.h>
#include <qjsondocument.h>
#include <qjsonobject.h>
@@ -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 {
@@ -120,7 +120,7 @@ int locateConfigFile(CommandState& cmd, QString& path) {
return -1;
}
} else {
qCCritical(logBare) << "Could not open manifest at path" << *cmd.config.manifest;
qCCritical(logBare) << "Could not open maifest at path" << *cmd.config.manifest;
return -1;
}
} else {
@@ -165,12 +165,7 @@ void sortInstances(QVector<InstanceLockInfo>& list, bool newestFirst) {
});
};
int selectInstance(
CommandState& cmd,
InstanceLockInfo* instance,
bool deadFallback = false,
bool logHelp = true
) {
int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallback = false) {
auto* basePath = QsPaths::instance()->baseRunDir();
if (!basePath) return -1;
@@ -179,22 +174,18 @@ int selectInstance(
if (cmd.instance.pid != -1) {
path = QDir(basePath->filePath("by-pid")).filePath(QString::number(cmd.instance.pid));
if (!QsPaths::checkLock(path, instance, deadFallback)) {
if (logHelp) qCInfo(logBare) << "No instance found for pid" << cmd.instance.pid;
qCInfo(logBare) << "No instance found for pid" << cmd.instance.pid;
return -1;
}
} else if (!cmd.instance.id->isEmpty()) {
path = basePath->filePath("by-pid");
auto [liveInstances, mismatchedInstances, deadInstances] =
auto [liveInstances, deadInstances] =
QsPaths::collectInstances(path, cmd.config.anyDisplay ? "" : getDisplayConnection());
liveInstances.removeIf([&](const InstanceLockInfo& info) {
return !info.instance.instanceId.startsWith(*cmd.instance.id);
});
mismatchedInstances.removeIf([&](const InstanceLockInfo& info) {
return !info.instance.instanceId.startsWith(*cmd.instance.id);
});
deadInstances.removeIf([&](const InstanceLockInfo& info) {
return !info.instance.instanceId.startsWith(*cmd.instance.id);
});
@@ -202,43 +193,27 @@ int selectInstance(
auto instances = liveInstances.isEmpty() && deadFallback ? deadInstances : liveInstances;
if (instances.isEmpty()) {
if (logHelp) {
if (!mismatchedInstances.isEmpty()) {
qCInfo(logBare) << "No running instances on the current display" << getDisplayConnection()
<< "start with" << *cmd.instance.id;
if (deadFallback) {
qCInfo(logBare) << "No instances start with" << *cmd.instance.id;
} else {
qCInfo(logBare) << "No running instances start with" << *cmd.instance.id;
qCInfo(logBare) << "Some instances on other displays match:";
if (!deadInstances.isEmpty()) {
qCInfo(logBare) << "Some dead instances match:";
for (auto& instance: mismatchedInstances) {
qCInfo(logBare).noquote().nospace() << " - " << instance.instance.instanceId << " ("
<< instance.instance.display << ')';
}
}
if (deadFallback) {
qCInfo(logBare) << "No instances start with" << *cmd.instance.id;
} else {
qCInfo(logBare) << "No running instances start with" << *cmd.instance.id;
if (!deadInstances.isEmpty()) {
qCInfo(logBare) << "Some dead instances match:";
for (auto& instance: deadInstances) {
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId;
}
for (auto& instance: deadInstances) {
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId;
}
}
}
return -1;
} else if (instances.length() != 1) {
if (logHelp) {
qCInfo(logBare) << "More than one instance starts with" << *cmd.instance.id;
qCInfo(logBare) << "More than one instance starts with" << *cmd.instance.id;
for (auto& instance: instances) {
qCInfo(logBare).noquote()
<< " -" << instance.instance.instanceId << (instance.pid == -1 ? "(dead)" : "");
}
for (auto& instance: instances) {
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId
<< (instance.pid == -1 ? " (dead)" : "");
}
return -1;
@@ -255,7 +230,7 @@ int selectInstance(
path = QDir(basePath->filePath("by-path")).filePath(pathId);
auto [liveInstances, mismatchedInstances, deadInstances] =
auto [liveInstances, deadInstances] =
QsPaths::collectInstances(path, cmd.config.anyDisplay ? "" : getDisplayConnection());
auto instances = liveInstances;
@@ -269,29 +244,16 @@ int selectInstance(
);
if (instances.isEmpty()) {
if (logHelp) {
if (liveInstances.isEmpty() && deadInstances.length() > 1) {
qCInfo(logBare) << "No running instances for" << configFilePath;
qCInfo(logBare) << "Dead instances:";
sortInstances(deadInstances, cmd.config.newest);
for (auto& instance: deadInstances) {
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId;
}
} else if (!mismatchedInstances.isEmpty()) {
qCInfo(logBare) << "No running instances for" << configFilePath
<< " present on the current display" << getDisplayConnection();
qCInfo(logBare) << "Some instances on other displays match:";
for (auto& instance: mismatchedInstances) {
qCInfo(logBare).noquote().nospace() << " - " << instance.instance.instanceId << " ("
<< instance.instance.display << ')';
}
} else {
qCInfo(logBare) << "No running instances for" << configFilePath;
if (liveInstances.isEmpty() && deadInstances.length() > 1) {
qCInfo(logBare) << "No running instances for" << configFilePath;
qCInfo(logBare) << "Dead instances:";
sortInstances(deadInstances, cmd.config.newest);
for (auto& instance: deadInstances) {
qCInfo(logBare).noquote() << " -" << instance.instance.instanceId;
}
} else {
qCInfo(logBare) << "No running instances for" << configFilePath;
}
return -1;
}
@@ -352,7 +314,7 @@ int listInstances(CommandState& cmd) {
path = QDir(basePath->filePath("by-path")).filePath(pathId);
}
auto [liveInstances, mismatchedInstances, deadInstances] = QsPaths::collectInstances(
auto [liveInstances, deadInstances] = QsPaths::collectInstances(
path,
cmd.config.anyDisplay || cmd.instance.all ? "" : getDisplayConnection()
);
@@ -466,7 +428,7 @@ int ipcCommand(CommandState& cmd) {
});
}
int launchFromCommand(CommandState& cmd) {
int launchFromCommand(CommandState& cmd, QCoreApplication* coreApplication) {
QString configPath;
auto r = locateConfigFile(cmd, configPath);
@@ -474,7 +436,7 @@ int launchFromCommand(CommandState& cmd) {
{
InstanceLockInfo info;
if (cmd.misc.noDuplicate && selectInstance(cmd, &info, false, false) == 0) {
if (cmd.misc.noDuplicate && selectInstance(cmd, &info) == 0) {
qCInfo(logBare) << "An instance of this configuration is already running.";
return 0;
}
@@ -486,13 +448,14 @@ int launchFromCommand(CommandState& cmd) {
.debugPort = cmd.debug.port,
.waitForDebug = cmd.debug.wait,
},
cmd.exec.argv
cmd.exec.argv,
coreApplication
);
}
} // namespace
int runCommand(int argc, char** argv) {
int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
auto state = CommandState();
if (auto ret = parseCommand(argc, argv, state); ret != 65535) return ret;
@@ -555,24 +518,30 @@ int runCommand(int argc, char** argv) {
);
}
std::function<int(CommandState&)> continuation;
if (state.misc.printVersion) {
if (state.log.verbosity == 0) {
qCInfo(logBare).noquote() << "Quickshell" << qs::debuginfo::qsVersion();
} else {
qCInfo(logBare).noquote() << qs::debuginfo::combinedInfo();
qCInfo(logBare).noquote().nospace() << "quickshell " << QS_VERSION << ", revision "
<< GIT_REVISION << ", distributed by: " << DISTRIBUTOR;
if (state.log.verbosity > 1) {
qCInfo(logBare).noquote() << "\nBuildtime Qt Version:" << QT_VERSION_STR;
qCInfo(logBare).noquote() << "Runtime Qt Version:" << qVersion();
qCInfo(logBare).noquote() << "Compiler:" << COMPILER;
qCInfo(logBare).noquote() << "Compile Flags:" << COMPILE_FLAGS;
}
return 0;
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) {
continuation = readLogFile;
return readLogFile(state);
} else if (*state.subcommand.list) {
continuation = listInstances;
return listInstances(state);
} else if (*state.subcommand.kill) {
continuation = killInstances;
return killInstances(state);
} else if (*state.subcommand.msg || *state.ipc.ipc) {
continuation = ipcCommand;
return ipcCommand(state);
} else {
if (strcmp(qVersion(), QT_VERSION_STR) != 0) {
qWarning() << "\033[31mQuickshell was built against Qt" << QT_VERSION_STR
@@ -581,26 +550,23 @@ int runCommand(int argc, char** argv) {
"the quickshell package must be rebuilt.\n";
}
return launchFromCommand(state);
return launchFromCommand(state, coreApplication);
}
int qArgc = 1;
auto coreApp = QCoreApplication(qArgc, argv);
return continuation(state);
return 0;
}
QString getDisplayConnection() {
// Doesn't actually have to match what qt picks, but will 99% of the time.
// Running quickshell under wayland in x11 mode doesn't really make any sense.
auto platform = qEnvironmentVariable("QT_QPA_PLATFORM");
auto wlDisplay = qEnvironmentVariable("WAYLAND_DISPLAY");
auto xDisplay = qEnvironmentVariable("DISPLAY");
if (!wlDisplay.isEmpty()) {
return "wayland/" + wlDisplay;
} else if (!xDisplay.isEmpty()) {
return "x11/" + xDisplay;
if (platform == "wayland" || (platform.isEmpty() && !wlDisplay.isEmpty())) {
return "wayland," + wlDisplay;
} else if (platform == "xcb" || (platform.isEmpty() && !xDisplay.isEmpty())) {
return "x11," + xDisplay;
} else {
return "unk";
return "unk," + QGuiApplication::platformName();
}
}
+20 -85
View File
@@ -22,7 +22,6 @@
#include "../core/logging.hpp"
#include "../core/paths.hpp"
#include "../core/plugin.hpp"
#include "../core/qmlglobal.hpp"
#include "../core/rootwrapper.hpp"
#include "../ipc/ipc.hpp"
#include "build.hpp"
@@ -58,7 +57,7 @@ QString base36Encode(T number) {
} // namespace
int launch(const LaunchArgs& args, char** argv) {
int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplication) {
auto pathId = QCryptographicHash::hash(args.configPath.toUtf8(), QCryptographicHash::Md5).toHex();
auto shellId = QString(pathId);
@@ -77,9 +76,6 @@ int launch(const LaunchArgs& args, char** argv) {
bool useSystemStyle = false;
QString iconTheme = qEnvironmentVariable("QS_ICON_THEME");
QHash<QString, QString> envOverrides;
QHash<QString, QString> defaultEnv;
QString appId = qEnvironmentVariable("QS_APP_ID");
bool dropExpensiveFonts = false;
QString dataDir;
QString stateDir;
QString cacheDir;
@@ -91,31 +87,23 @@ int launch(const LaunchArgs& args, char** argv) {
if (line.startsWith("//@ pragma ")) {
auto pragma = line.sliced(11).trimmed();
auto isEnv = pragma.startsWith("Env ");
auto isDefaultEnv = pragma.startsWith("DefaultEnv ");
if (isEnv || isDefaultEnv) {
auto content = pragma.sliced(isDefaultEnv ? 11 : 4);
auto splitIdx = content.indexOf('=');
if (pragma == "UseQApplication") pragmas.useQApplication = true;
else if (pragma == "NativeTextRendering") pragmas.nativeTextRendering = true;
else if (pragma == "IgnoreSystemSettings") pragmas.desktopSettingsAware = false;
else if (pragma == "RespectSystemStyle") pragmas.useSystemStyle = true;
else if (pragma.startsWith("IconTheme ")) pragmas.iconTheme = pragma.sliced(10);
else if (pragma.startsWith("Env ")) {
auto envPragma = pragma.sliced(4);
auto splitIdx = envPragma.indexOf('=');
if (splitIdx == -1) {
qCritical() << "Env pragma" << pragma << "not in the form 'VAR = VALUE'";
return -1;
}
auto var = content.sliced(0, splitIdx).trimmed();
auto val = content.sliced(splitIdx + 1).trimmed();
if (isDefaultEnv) pragmas.defaultEnv.insert(var, val);
else pragmas.envOverrides.insert(var, val);
} else if (pragma == "UseQApplication") pragmas.useQApplication = true;
else if (pragma == "NativeTextRendering") pragmas.nativeTextRendering = true;
else if (pragma == "IgnoreSystemSettings") pragmas.desktopSettingsAware = false;
else if (pragma == "RespectSystemStyle") pragmas.useSystemStyle = true;
else if (pragma == "DropExpensiveFonts") pragmas.dropExpensiveFonts = true;
else if (pragma.startsWith("IconTheme ")) pragmas.iconTheme = pragma.sliced(10);
else if (pragma.startsWith("AppId ")) {
pragmas.appId = pragma.sliced(6).trimmed();
auto var = envPragma.sliced(0, splitIdx).trimmed();
auto val = envPragma.sliced(splitIdx + 1).trimmed();
pragmas.envOverrides.insert(var, val);
} else if (pragma.startsWith("ShellId ")) {
shellId = pragma.sliced(8).trimmed();
} else if (pragma.startsWith("DataDir ")) {
@@ -125,7 +113,8 @@ int launch(const LaunchArgs& args, char** argv) {
} else if (pragma.startsWith("CacheDir ")) {
pragmas.cacheDir = pragma.sliced(9).trimmed();
} else {
qWarning() << "Unrecognized pragma" << pragma;
qCritical() << "Unrecognized pragma" << pragma;
return -1;
}
} else if (line.startsWith("import")) break;
}
@@ -139,24 +128,19 @@ int launch(const LaunchArgs& args, char** argv) {
qInfo() << "Shell ID:" << shellId << "Path ID" << pathId;
auto launchTime = qs::Common::LAUNCH_TIME.toSecsSinceEpoch();
auto appId = pragmas.appId.isEmpty() ? QStringLiteral("org.quickshell") : pragmas.appId;
InstanceInfo::CURRENT = InstanceInfo {
.instanceId = base36Encode(getpid()) + base36Encode(launchTime),
.configPath = args.configPath,
.shellId = shellId,
.appId = appId,
.launchTime = qs::Common::LAUNCH_TIME,
.pid = getpid(),
.display = getDisplayConnection(),
};
#if CRASH_HANDLER
if (qEnvironmentVariableIsSet("QS_DISABLE_CRASH_HANDLER")) {
qInfo() << "Crash handling disabled.";
} else {
crash::CrashHandler::init();
crash::CrashHandler::init();
{
auto* log = LogManager::instance();
crash::CrashHandler::setRelaunchInfo({
.instance = InstanceInfo::CURRENT,
@@ -172,6 +156,7 @@ int launch(const LaunchArgs& args, char** argv) {
QsPaths::init(shellId, pathId, pragmas.dataDir, pragmas.stateDir, pragmas.cacheDir);
QsPaths::instance()->linkRunDir();
QsPaths::instance()->linkPathDir();
LogManager::initFs();
Common::INITIAL_ENVIRONMENT = QProcessEnvironment::systemEnvironment();
@@ -180,56 +165,10 @@ int launch(const LaunchArgs& args, char** argv) {
qputenv("QT_QUICK_CONTROLS_STYLE", "Fusion");
}
for (auto [var, val]: pragmas.defaultEnv.asKeyValueRange()) {
if (!qEnvironmentVariableIsSet(var.toUtf8())) qputenv(var.toUtf8(), val.toUtf8());
}
for (auto [var, val]: pragmas.envOverrides.asKeyValueRange()) {
qputenv(var.toUtf8(), val.toUtf8());
}
pragmas.dropExpensiveFonts |= qEnvironmentVariableIntValue("QS_DROP_EXPENSIVE_FONTS") == 1;
if (pragmas.dropExpensiveFonts) {
if (auto* runDir = QsPaths::instance()->instanceRunDir()) {
auto baseConfigPath = qEnvironmentVariable("FONTCONFIG_FILE");
if (baseConfigPath.isEmpty()) baseConfigPath = "/etc/fonts/fonts.conf";
auto filterPath = runDir->filePath("fonts-override.conf");
auto filterFile = QFile(filterPath);
if (filterFile.open(QFile::WriteOnly | QFile::Truncate | QFile::Text)) {
auto filterTemplate = QStringLiteral(R"(<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
<fontconfig>
<include ignore_missing="no">%1</include>
<selectfont>
<rejectfont>
<pattern>
<patelt name="fontwrapper">
<string>woff</string>
</patelt>
</pattern>
<pattern>
<patelt name="fontwrapper">
<string>woff2</string>
</patelt>
</pattern>
</rejectfont>
</selectfont>
</fontconfig>
)");
QTextStream(&filterFile) << filterTemplate.arg(baseConfigPath);
filterFile.close();
qputenv("FONTCONFIG_FILE", filterPath.toUtf8());
} else {
qCritical() << "Could not write fontconfig filter to" << filterPath;
}
} else {
qCritical() << "Could not create fontconfig filter: instance run directory unavailable";
}
}
// The qml engine currently refuses to cache non file (qsintercept) paths.
// if (auto* cacheDir = QsPaths::instance()->cacheDir()) {
@@ -277,9 +216,10 @@ int launch(const LaunchArgs& args, char** argv) {
QIcon::setFallbackSearchPaths(fallbackPaths);
}
QGuiApplication::setDesktopFileName(appId);
QGuiApplication::setDesktopSettingsAware(pragmas.desktopSettingsAware);
delete coreApplication;
QGuiApplication* app = nullptr;
auto qArgC = 0;
@@ -289,8 +229,7 @@ int launch(const LaunchArgs& args, char** argv) {
app = new QGuiApplication(qArgC, argv);
}
LogManager::initThreadLogging();
LogManager::initFs();
QGuiApplication::setDesktopFileName("org.quickshell");
if (args.debugPort != -1) {
QQmlDebuggingEnabler::enableDebugging(true);
@@ -299,10 +238,6 @@ int launch(const LaunchArgs& args, char** argv) {
QQmlDebuggingEnabler::startTcpDebugServer(args.debugPort, wait);
}
// This needs to run early to get the first connection to QGuiApplication::screenAdded() in Qs.
// If we don't do that, attempts to get a QuickshellScreenInfo from a QScreen will fail in screenAdded bindings.
QuickshellTracked::init();
QsEnginePlugin::initPlugins();
// Base window transparency appears to be additive.
+2 -2
View File
@@ -107,10 +107,10 @@ struct LaunchArgs {
void exitDaemon(int code);
int parseCommand(int argc, char** argv, CommandState& state);
int runCommand(int argc, char** argv);
int runCommand(int argc, char** argv, QCoreApplication* coreApplication);
QString getDisplayConnection();
int launch(const LaunchArgs& args, char** argv);
int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplication);
} // namespace qs::launch
+27 -39
View File
@@ -24,31 +24,23 @@ namespace qs::launch {
namespace {
void checkCrashRelaunch(char** argv) {
void checkCrashRelaunch(char** argv, QCoreApplication* coreApplication) {
#if CRASH_HANDLER
auto lastInfoFdStr = qEnvironmentVariable("__QUICKSHELL_CRASH_INFO_FD");
auto dumpPid = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_PID").toInt();
if (!lastInfoFdStr.isEmpty()) {
auto lastInfoFd = lastInfoFdStr.toInt();
RelaunchInfo info;
{
QFile file;
if (!file.open(lastInfoFd, QFile::ReadOnly, QFile::AutoCloseHandle)) {
qFatal() << "Failed to open crash info fd. Cannot restart.";
}
file.seek(0);
auto ds = QDataStream(&file);
ds >> info;
QFile file;
if (!file.open(lastInfoFd, QFile::ReadOnly, QFile::AutoCloseHandle)) {
qFatal() << "Failed to open crash info fd. Cannot restart.";
}
qunsetenv("__QUICKSHELL_CRASH_INFO_FD");
qunsetenv("__QUICKSHELL_CRASH_DUMP_PID");
qunsetenv("__QUICKSHELL_CRASH_SIGNAL");
file.seek(0);
auto ds = QDataStream(&file);
RelaunchInfo info;
ds >> info;
LogManager::init(
!info.noColor,
@@ -58,7 +50,8 @@ void checkCrashRelaunch(char** argv) {
info.logRules
);
qCritical().nospace() << "Quickshell has crashed under pid " << dumpPid
qCritical().nospace() << "Quickshell has crashed under pid "
<< qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_PID").toInt()
<< " (Coredumps will be available under that pid.)";
qCritical() << "Further crash information is stored under"
@@ -71,7 +64,7 @@ void checkCrashRelaunch(char** argv) {
} else {
qCritical() << "Quickshell has been restarted.";
launch({.configPath = info.instance.configPath}, argv);
launch({.configPath = info.instance.configPath}, argv, coreApplication);
}
}
#endif
@@ -91,29 +84,21 @@ void exitDaemon(int code) {
close(DAEMON_PIPE);
auto fd = open("/dev/null", O_RDWR);
if (fd == -1) {
qCritical().nospace() << "Failed to open /dev/null for daemon stdio" << errno << ": "
<< qt_error_string();
return;
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
if (open("/dev/null", O_RDONLY) != STDIN_FILENO) { // NOLINT
qFatal() << "Failed to open /dev/null on stdin";
}
if (dup2(fd, STDIN_FILENO) != STDIN_FILENO) { // NOLINT
qCritical().nospace() << "Failed to set daemon stdin to /dev/null" << errno << ": "
<< qt_error_string();
if (open("/dev/null", O_WRONLY) != STDOUT_FILENO) { // NOLINT
qFatal() << "Failed to open /dev/null on stdout";
}
if (dup2(fd, STDOUT_FILENO) != STDOUT_FILENO) { // NOLINT
qCritical().nospace() << "Failed to set daemon stdout to /dev/null" << errno << ": "
<< qt_error_string();
if (open("/dev/null", O_WRONLY) != STDERR_FILENO) { // NOLINT
qFatal() << "Failed to open /dev/null on stderr";
}
if (dup2(fd, STDERR_FILENO) != STDERR_FILENO) { // NOLINT
qCritical().nospace() << "Failed to set daemon stderr to /dev/null" << errno << ": "
<< qt_error_string();
}
close(fd);
}
int main(int argc, char** argv) {
@@ -123,8 +108,11 @@ int main(int argc, char** argv) {
qsCheckCrash(argc, argv);
#endif
checkCrashRelaunch(argv);
auto code = runCommand(argc, argv);
auto qArgC = 1;
auto* coreApplication = new QCoreApplication(qArgC, argv);
checkCrashRelaunch(argv, coreApplication);
auto code = runCommand(argc, argv, coreApplication);
exitDaemon(code);
return code;
+1 -4
View File
@@ -3,10 +3,7 @@ add_subdirectory(nm)
qt_add_library(quickshell-network STATIC
network.cpp
device.cpp
wired.cpp
wifi.cpp
enums.cpp
qml.cpp
)
target_include_directories(quickshell-network PRIVATE
@@ -19,7 +16,7 @@ qt_add_qml_module(quickshell-network
DEPENDENCIES QtQml
)
qs_add_module_deps_light(quickshell-network quickshell-core)
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)
+43 -13
View File
@@ -8,8 +8,6 @@
#include <qtmetamacros.h>
#include "../core/logcat.hpp"
#include "enums.hpp"
#include "network.hpp"
namespace qs::network {
@@ -17,9 +15,49 @@ 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 == ConnectionState::Connected;
return this->bState == DeviceConnectionState::Connected;
});
};
@@ -28,17 +66,12 @@ void NetworkDevice::setAutoconnect(bool autoconnect) {
emit this->requestSetAutoconnect(autoconnect);
}
void NetworkDevice::setNmManaged(bool managed) {
if (this->bNmManaged == managed) return;
emit this->requestSetNmManaged(managed);
}
void NetworkDevice::disconnect() {
if (this->bState == ConnectionState::Disconnected) {
if (this->bState == DeviceConnectionState::Disconnected) {
qCCritical(logNetworkDevice) << "Device" << this << "is already disconnected";
return;
}
if (this->bState == ConnectionState::Disconnecting) {
if (this->bState == DeviceConnectionState::Disconnecting) {
qCCritical(logNetworkDevice) << "Device" << this << "is already disconnecting";
return;
}
@@ -46,7 +79,4 @@ void NetworkDevice::disconnect() {
this->requestDisconnect();
}
void NetworkDevice::networkAdded(Network* net) { this->mNetworks.insertObject(net); }
void NetworkDevice::networkRemoved(Network* net) { this->mNetworks.removeObject(net); }
} // namespace qs::network
+80 -46
View File
@@ -6,44 +6,88 @@
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../core/doc.hpp"
#include "../core/model.hpp"
#include "enums.hpp"
#include "network.hpp"
namespace qs::network {
///! Connection state of a NetworkDevice.
class DeviceConnectionState: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
Unknown = 0,
Connecting = 1,
Connected = 2,
Disconnecting = 3,
Disconnected = 4,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(DeviceConnectionState::Enum state);
};
///! Type of network device.
class DeviceType: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
None = 0,
Wifi = 1,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(DeviceType::Enum type);
};
///! NetworkManager-specific device state.
/// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceState.
class NMDeviceState: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
Unknown = 0,
Unmanaged = 10,
Unavailable = 20,
Disconnected = 30,
Prepare = 40,
Config = 50,
NeedAuth = 60,
IPConfig = 70,
IPCheck = 80,
Secondaries = 90,
Activated = 100,
Deactivating = 110,
Failed = 120,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(NMDeviceState::Enum state);
};
///! A network device.
/// The @@type property may be used to determine if this device is a @@WifiDevice or @@WiredDevice.
/// 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.
///
/// When the device type is `Wifi`, the device object is a @@WifiDevice.
/// When the device type is `Wired`, the device object is a @@WiredDevice.
/// connection and scanning.
Q_PROPERTY(qs::network::DeviceType::Enum type READ type CONSTANT);
Q_PROPERTY(DeviceType::Enum type READ type CONSTANT);
/// The name of the device's control interface.
Q_PROPERTY(QString name READ name NOTIFY nameChanged BINDABLE bindableName);
/// A list of available or connected networks for this device.
///
/// When the device type is 'Wifi', this model will only contain @@WifiNetwork.
QSDOC_TYPE_OVERRIDE(ObjectModel<Network>*);
Q_PROPERTY(UntypedObjectModel* networks READ networks CONSTANT);
/// The hardware address of the device in the XX:XX:XX:XX:XX:XX format.
Q_PROPERTY(QString address READ default NOTIFY addressChanged BINDABLE bindableAddress);
/// True if the device is connected.
Q_PROPERTY(bool connected READ default NOTIFY connectedChanged BINDABLE bindableConnected);
/// Connection state of the device.
Q_PROPERTY(qs::network::ConnectionState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState);
/// True if the device is managed by NetworkManager.
///
/// > [!WARNING] Only valid for the NetworkManager backend.
Q_PROPERTY(bool nmManaged READ nmManaged WRITE setNmManaged NOTIFY nmManagedChanged)
/// True if the device is allowed to autoconnect to a network.
Q_PROPERTY(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
@@ -53,45 +97,35 @@ public:
/// Disconnects the device and prevents it from automatically activating further connections.
Q_INVOKABLE void disconnect();
virtual void networkAdded(Network* net);
virtual void networkRemoved(Network* net);
[[nodiscard]] ObjectModel<Network>* networks() { return &this->mNetworks; }
[[nodiscard]] DeviceType::Enum type() const { return this->mType; }
QBindable<QString> bindableName() { return &this->bName; }
[[nodiscard]] QString name() const { return this->bName; }
QBindable<QString> bindableAddress() { return &this->bAddress; }
QBindable<bool> bindableConnected() { return &this->bConnected; }
QBindable<ConnectionState::Enum> bindableState() { return &this->bState; }
QBindable<bool> bindableNmManaged() { return &this->bNmManaged; }
[[nodiscard]] bool nmManaged() { return this->bNmManaged; }
void setNmManaged(bool managed);
QBindable<bool> bindableAutoconnect() { return &this->bAutoconnect; }
[[nodiscard]] bool autoconnect() { return this->bAutoconnect; }
[[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:
QSDOC_HIDE void requestDisconnect();
QSDOC_HIDE void requestSetAutoconnect(bool autoconnect);
QSDOC_HIDE void requestSetNmManaged(bool managed);
void requestDisconnect();
void requestSetAutoconnect(bool autoconnect);
void nameChanged();
void addressChanged();
void connectedChanged();
void stateChanged();
void nmManagedChanged();
void nmStateChanged();
void autoconnectChanged();
protected:
ObjectModel<Network> mNetworks {this};
private:
DeviceType::Enum mType;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, QString, bName, &NetworkDevice::nameChanged);
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, QString, bAddress, &NetworkDevice::addressChanged);
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, bool, bConnected, &NetworkDevice::connectedChanged);
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, ConnectionState::Enum, bState, &NetworkDevice::stateChanged);
Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, bool, bNmManaged, &NetworkDevice::nmManagedChanged);
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
};
-87
View File
@@ -1,87 +0,0 @@
#include "enums.hpp"
#include <qstring.h>
namespace qs::network {
QString NetworkConnectivity::toString(NetworkConnectivity::Enum conn) {
switch (conn) {
case Unknown: return QStringLiteral("Unknown");
case None: return QStringLiteral("Not connected to a network");
case Portal: return QStringLiteral("Connection intercepted by a captive portal");
case Limited: return QStringLiteral("Partial internet connectivity");
case Full: return QStringLiteral("Full internet connectivity");
default: return QStringLiteral("Unknown");
}
}
QString NetworkBackendType::toString(NetworkBackendType::Enum type) {
switch (type) {
case NetworkBackendType::None: return "None";
case NetworkBackendType::NetworkManager: return "NetworkManager";
default: return "Unknown";
}
}
QString ConnectionState::toString(ConnectionState::Enum state) {
switch (state) {
case Unknown: return QStringLiteral("Unknown");
case Connecting: return QStringLiteral("Connecting");
case Connected: return QStringLiteral("Connected");
case Disconnecting: return QStringLiteral("Disconnecting");
case Disconnected: return QStringLiteral("Disconnected");
default: return QStringLiteral("Unknown");
}
}
QString ConnectionFailReason::toString(ConnectionFailReason::Enum reason) {
switch (reason) {
case Unknown: return QStringLiteral("Unknown");
case NoSecrets: return QStringLiteral("Secrets were required but not provided");
case WifiClientDisconnected: return QStringLiteral("Wi-Fi supplicant disconnected");
case WifiClientFailed: return QStringLiteral("Wi-Fi supplicant failed");
case WifiAuthTimeout: return QStringLiteral("Wi-Fi connection took too long to authenticate");
case WifiNetworkLost: return QStringLiteral("Wi-Fi network could not be found");
default: return QStringLiteral("Unknown");
}
}
QString DeviceType::toString(DeviceType::Enum type) {
switch (type) {
case None: return QStringLiteral("None");
case Wifi: return QStringLiteral("Wifi");
case Wired: return QStringLiteral("Wired");
default: return QStringLiteral("Unknown");
}
}
QString WifiSecurityType::toString(WifiSecurityType::Enum type) {
switch (type) {
case Unknown: return QStringLiteral("Unknown");
case Wpa3SuiteB192: return QStringLiteral("WPA3 Suite B 192-bit");
case Sae: return QStringLiteral("WPA3");
case Wpa2Eap: return QStringLiteral("WPA2 Enterprise");
case Wpa2Psk: return QStringLiteral("WPA2");
case WpaEap: return QStringLiteral("WPA Enterprise");
case WpaPsk: return QStringLiteral("WPA");
case StaticWep: return QStringLiteral("WEP");
case DynamicWep: return QStringLiteral("Dynamic WEP");
case Leap: return QStringLiteral("LEAP");
case Owe: return QStringLiteral("OWE");
case Open: return QStringLiteral("Open");
default: return QStringLiteral("Unknown");
}
}
QString WifiDeviceMode::toString(WifiDeviceMode::Enum mode) {
switch (mode) {
case Unknown: return QStringLiteral("Unknown");
case AdHoc: return QStringLiteral("Ad-Hoc");
case Station: return QStringLiteral("Station");
case AccessPoint: return QStringLiteral("Access Point");
case Mesh: return QStringLiteral("Mesh");
default: return QStringLiteral("Unknown");
};
}
} // namespace qs::network
-155
View File
@@ -1,155 +0,0 @@
#pragma once
#include <qobject.h>
#include <qqmlintegration.h>
#include <qtypes.h>
namespace qs::network {
///! The degree to which the host can reach the internet.
class NetworkConnectivity: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
/// Network connectivity is unknown. This means the connectivity checks are disabled or have not run yet.
Unknown = 0,
/// The host is not connected to any network.
None = 1,
/// The internet connection is hijacked by a captive portal gateway.
/// This indicates the shell should open a sandboxed web browser window for the purpose of authenticating to a gateway.
Portal = 2,
/// The host is connected to a network but does not appear to be able to reach the full internet.
Limited = 3,
/// The host is connected to a network and appears to be able to reach the full internet.
Full = 4,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(qs::network::NetworkConnectivity::Enum conn);
};
///! The backend supplying the Network service.
class NetworkBackendType: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
None = 0,
NetworkManager = 1,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(qs::network::NetworkBackendType::Enum type);
};
///! The connection state of a device or network.
class ConnectionState: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
Unknown = 0,
Connecting = 1,
Connected = 2,
Disconnecting = 3,
Disconnected = 4,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(qs::network::ConnectionState::Enum state);
};
///! The reason a connection failed.
class ConnectionFailReason: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
/// The connection failed for an unknown reason.
Unknown = 0,
/// Secrets were required, but not provided.
NoSecrets = 1,
/// The Wi-Fi supplicant disconnected.
WifiClientDisconnected = 2,
/// The Wi-Fi supplicant failed.
WifiClientFailed = 3,
/// The Wi-Fi connection took too long to authenticate.
WifiAuthTimeout = 4,
/// The Wi-Fi network could not be found.
WifiNetworkLost = 5,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(qs::network::ConnectionFailReason::Enum reason);
};
///! Type of a @@NetworkDevice.
class DeviceType: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
None = 0,
Wifi = 1,
Wired = 2,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(qs::network::DeviceType::Enum type);
};
///! The security type of a @@WifiNetwork.
class WifiSecurityType: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
Wpa3SuiteB192 = 0,
Sae = 1,
Wpa2Eap = 2,
Wpa2Psk = 3,
WpaEap = 4,
WpaPsk = 5,
StaticWep = 6,
DynamicWep = 7,
Leap = 8,
Owe = 9,
Open = 10,
Unknown = 11,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(qs::network::WifiSecurityType::Enum type);
};
///! The 802.11 mode of a @@WifiDevice.
class WifiDeviceMode: public QObject {
Q_OBJECT;
QML_ELEMENT;
QML_SINGLETON;
public:
enum Enum : quint8 {
/// The device is part of an Ad-Hoc network without a central access point.
AdHoc = 0,
/// The device is a station that can connect to networks.
Station = 1,
/// The device is a local hotspot/access point.
AccessPoint = 2,
/// The device is an 802.11s mesh point.
Mesh = 3,
/// The device mode is unknown.
Unknown = 4,
};
Q_ENUM(Enum);
Q_INVOKABLE static QString toString(qs::network::WifiDeviceMode::Enum mode);
};
} // namespace qs::network
-4
View File
@@ -1,13 +1,9 @@
name = "Quickshell.Networking"
description = "Network API"
headers = [
"qml.hpp",
"network.hpp",
"device.hpp",
"wifi.hpp",
"wired.hpp",
"enums.hpp",
"nm/settings.hpp",
]
-----
This module exposes Network management APIs provided by a supported network backend.
+43 -48
View File
@@ -1,15 +1,15 @@
#include "network.hpp"
#include <utility>
#include <qdebug.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qobject.h>
#include <qstring.h>
#include <qtmetamacros.h>
#include "../core/logcat.hpp"
#include "enums.hpp"
#include "nm/settings.hpp"
#include "device.hpp"
#include "nm/backend.hpp"
namespace qs::network {
@@ -17,54 +17,49 @@ namespace {
QS_LOGGING_CATEGORY(logNetwork, "quickshell.network", QtWarningMsg);
} // namespace
Network::Network(QString name, NetworkDevice* device, QObject* parent)
: QObject(parent)
, bName(std::move(name))
, mDevice(device) {
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 == ConnectionState::Connecting || state == ConnectionState::Disconnecting;
return state == NetworkState::Connecting || state == NetworkState::Disconnecting;
});
};
void Network::connect() {
if (this->bConnected) {
qCCritical(logNetwork) << this << "is already connected.";
return;
}
this->requestConnect();
}
void Network::connectWithSettings(NMSettings* settings) {
if (this->bConnected) {
qCCritical(logNetwork) << this << "is already connected.";
return;
}
if (this->bNmSettings.value().indexOf(settings) == -1) return;
this->requestConnectWithSettings(settings);
}
void Network::disconnect() {
if (!this->bConnected) {
qCCritical(logNetwork) << this << "is not currently connected";
return;
}
this->requestDisconnect();
}
void Network::forget() { this->requestForget(); }
void Network::settingsAdded(NMSettings* settings) {
auto list = this->bNmSettings.value();
if (list.contains(settings)) return;
list.append(settings);
this->bNmSettings = list;
}
void Network::settingsRemoved(NMSettings* settings) {
auto list = this->bNmSettings.value();
list.removeOne(settings);
this->bNmSettings = list;
}
} // namespace qs::network
Loaded 100 of 283 files, more files were not shown because too many files have changed in this diff. Show more