mirror of
https://github.com/quickshell-mirror/quickshell.git
synced 2026-10-29 03:05:05 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da4ea5ff95 |
No files matched your search
@@ -20,7 +20,6 @@ Checks: >
|
|||||||
-cppcoreguidelines-avoid-do-while,
|
-cppcoreguidelines-avoid-do-while,
|
||||||
-cppcoreguidelines-pro-type-reinterpret-cast,
|
-cppcoreguidelines-pro-type-reinterpret-cast,
|
||||||
-cppcoreguidelines-pro-type-vararg,
|
-cppcoreguidelines-pro-type-vararg,
|
||||||
-cppcoreguidelines-pro-type-union-access,
|
|
||||||
-cppcoreguidelines-use-enum-class,
|
-cppcoreguidelines-use-enum-class,
|
||||||
google-global-names-in-headers,
|
google-global-names-in-headers,
|
||||||
google-readability-casting,
|
google-readability-casting,
|
||||||
|
|||||||
@@ -15,7 +15,15 @@ Please make this descriptive enough to identify your specific package, for examp
|
|||||||
- `Nixpkgs`
|
- `Nixpkgs`
|
||||||
- `Fedora COPR (errornointernet/quickshell)`
|
- `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
|
### QML Module dir
|
||||||
Currently all QML modules are statically linked to quickshell, but this is where
|
Currently all QML modules are statically linked to quickshell, but this is where
|
||||||
@@ -33,7 +41,6 @@ Quickshell has a set of base dependencies you will always need, names vary by di
|
|||||||
- `cmake`
|
- `cmake`
|
||||||
- `qt6base`
|
- `qt6base`
|
||||||
- `qt6declarative`
|
- `qt6declarative`
|
||||||
- `libdrm`
|
|
||||||
- `qtshadertools` (build-time)
|
- `qtshadertools` (build-time)
|
||||||
- `spirv-tools` (build-time)
|
- `spirv-tools` (build-time)
|
||||||
- `pkg-config` (build-time)
|
- `pkg-config` (build-time)
|
||||||
@@ -67,14 +74,7 @@ To disable: `-DCRASH_HANDLER=OFF`
|
|||||||
Dependencies: `cpptrace`
|
Dependencies: `cpptrace`
|
||||||
|
|
||||||
Note: `-DVENDOR_CPPTRACE=ON` can be set to vendor cpptrace using FetchContent.
|
Note: `-DVENDOR_CPPTRACE=ON` can be set to vendor cpptrace using FetchContent.
|
||||||
|
This still requires `libunwind` to build.
|
||||||
When using FetchContent, `libunwind` is required, and `libdwarf` can be provided by the
|
|
||||||
package manager or fetched with FetchContent.
|
|
||||||
|
|
||||||
*Please ensure binaries have usable symbols.* We do not necessarily need full debuginfo, but
|
|
||||||
leaving symbols in the binary is extremely helpful. You can check if symbols are useful
|
|
||||||
by sending a SIGSEGV to the process and ensuring symbols for the quickshell binary are present
|
|
||||||
in the trace.
|
|
||||||
|
|
||||||
### Jemalloc
|
### Jemalloc
|
||||||
We recommend leaving Jemalloc enabled as it will mask memory fragmentation caused
|
We recommend leaving Jemalloc enabled as it will mask memory fragmentation caused
|
||||||
@@ -147,6 +147,7 @@ Enables streaming video from monitors and toplevel windows through various proto
|
|||||||
To disable: `-DSCREENCOPY=OFF`
|
To disable: `-DSCREENCOPY=OFF`
|
||||||
|
|
||||||
Dependencies:
|
Dependencies:
|
||||||
|
- `libdrm`
|
||||||
- `libgbm`
|
- `libgbm`
|
||||||
- `vulkan-headers` (build-time)
|
- `vulkan-headers` (build-time)
|
||||||
|
|
||||||
@@ -242,7 +243,7 @@ Only `ninja` builds are tested, but makefiles may work.
|
|||||||
|
|
||||||
#### Configuring the build
|
#### Configuring the build
|
||||||
```sh
|
```sh
|
||||||
$ cmake -GNinja -B build -DCMAKE_BUILD_TYPE=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
|
Note that features you do not supply dependencies for MUST be disabled with their associated flags
|
||||||
|
|||||||
+1
-3
@@ -9,9 +9,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|||||||
|
|
||||||
set(QS_BUILD_OPTIONS "")
|
set(QS_BUILD_OPTIONS "")
|
||||||
|
|
||||||
# should be changed for forks
|
|
||||||
set(CRASHREPORT_URL "https://github.com/outfoxxed/quickshell/issues/new?template=crash2.yml" CACHE STRING "Bugreport URL")
|
|
||||||
|
|
||||||
function(boption VAR NAME DEFAULT)
|
function(boption VAR NAME DEFAULT)
|
||||||
cmake_parse_arguments(PARSE_ARGV 3 arg "" "REQUIRES" "")
|
cmake_parse_arguments(PARSE_ARGV 3 arg "" "REQUIRES" "")
|
||||||
|
|
||||||
@@ -43,6 +40,7 @@ string(APPEND QS_BUILD_OPTIONS " Distributor: ${DISTRIBUTOR}")
|
|||||||
|
|
||||||
message(STATUS "Quickshell configuration")
|
message(STATUS "Quickshell configuration")
|
||||||
message(STATUS " Distributor: ${DISTRIBUTOR}")
|
message(STATUS " Distributor: ${DISTRIBUTOR}")
|
||||||
|
boption(DISTRIBUTOR_DEBUGINFO_AVAILABLE "Distributor provided debuginfo" NO)
|
||||||
boption(NO_PCH "Disable precompild headers (dev)" OFF)
|
boption(NO_PCH "Disable precompild headers (dev)" OFF)
|
||||||
boption(BUILD_TESTING "Build tests (dev)" OFF)
|
boption(BUILD_TESTING "Build tests (dev)" OFF)
|
||||||
boption(ASAN "ASAN (dev)" OFF) # note: better output with gcc than clang
|
boption(ASAN "ASAN (dev)" OFF) # note: better output with gcc than clang
|
||||||
|
|||||||
+221
-26
@@ -1,40 +1,235 @@
|
|||||||
# Contributing
|
# Contributing / Development
|
||||||
|
Instructions for development setup and upstreaming patches.
|
||||||
|
|
||||||
Thank you for taking the time to contribute.
|
If you just want to build or package quickshell see [BUILD.md](BUILD.md).
|
||||||
To ensure nobody's time is wasted, please follow the rules below.
|
|
||||||
|
|
||||||
## Acceptable Code Contributions
|
## Development
|
||||||
|
|
||||||
- All changes submitted MUST be **fully understood by the submitter**. If you do not know why or how
|
Install the dependencies listed in [BUILD.md](BUILD.md).
|
||||||
your change works, do not submit it to be merged. You must be able to explain your reasoning
|
You probably want all of them even if you don't use all of them
|
||||||
for every change.
|
to ensure tests work correctly and avoid passing a bunch of configure
|
||||||
|
flags when you need to wipe the build directory.
|
||||||
|
|
||||||
- Changes MUST be submitted by a human who will be responsible for them. Changes submitted without
|
Quickshell also uses `just` for common development command aliases.
|
||||||
a human in the loop such as automated tooling and AI Agents are **strictly disallowed**. Accounts
|
|
||||||
responsible for such contribution attempts **will be banned**.
|
|
||||||
|
|
||||||
- Changes MUST respect Quickshell's license and the license of any source works. Changes including
|
The dependencies are also available as a nix shell or nix flake which we recommend
|
||||||
code from any other works must disclose the source of the code, explain why it was used, and
|
using with nix-direnv.
|
||||||
ensure the license is compatible.
|
|
||||||
|
|
||||||
- Changes must follow the guidelines outlined in [HACKING.md](HACKING.md) for style and substance.
|
Common aliases:
|
||||||
|
- `just configure [<debug|release> [extra cmake args]]` (note that you must specify debug/release to specify extra args)
|
||||||
|
- `just build` - runs the build, configuring if not configured already.
|
||||||
|
- `just run [args]` - runs quickshell with the given arguments
|
||||||
|
- `just clean` - clean up build artifacts. `just clean build` is somewhat common.
|
||||||
|
|
||||||
- Changes must stand on their own as a unit. Do not make multiple unrelated changes in one PR.
|
### Formatting
|
||||||
Changes depending on prior merges should be marked as a draft.
|
All contributions should be formatted similarly to what already exists.
|
||||||
|
Group related functionality together.
|
||||||
|
|
||||||
## Acceptable Non-code Contributions
|
Run the formatter using `just fmt`.
|
||||||
|
If the results look stupid, fix the clang-format file if possible,
|
||||||
|
or disable clang-format in the affected area
|
||||||
|
using `// clang-format off` and `// clang-format on`.
|
||||||
|
|
||||||
- Bug and crash reports. You must follow the instructions in the issue templates and provide the
|
#### Style preferences not caught by clang-format
|
||||||
information requested.
|
These are flexible. You can ignore them if it looks or works better to
|
||||||
|
for one reason or another.
|
||||||
|
|
||||||
- Feature requests can be made via Issues. Please check to ensure nobody else has requested the same feature.
|
Use `auto` if the type of a variable can be deduced automatically, instead of
|
||||||
|
redeclaring the returned value's type. Additionally, auto should be used when a
|
||||||
|
constructor takes arguments.
|
||||||
|
|
||||||
- Do not make insubstantial or pointless changes.
|
```cpp
|
||||||
|
auto x = <expr>; // ok
|
||||||
|
auto x = QString::number(3); // ok
|
||||||
|
QString x; // ok
|
||||||
|
QString x = "foo"; // ok
|
||||||
|
auto x = QString("foo"); // ok
|
||||||
|
|
||||||
- Changes to project rules / policy / governance will not be entertained, except from significant
|
auto x = QString(); // avoid
|
||||||
long-term contributors. These changes should not be addressed through contribution channels.
|
QString x(); // avoid
|
||||||
|
QString x("foo"); // avoid
|
||||||
|
```
|
||||||
|
|
||||||
## Merge timelines
|
Put newlines around logical units of code, and after closing braces. If the
|
||||||
|
most reasonable logical unit of code takes only a single line, it should be
|
||||||
|
merged into the next single line logical unit if applicable.
|
||||||
|
```cpp
|
||||||
|
// multiple units
|
||||||
|
auto x = <expr>; // unit 1
|
||||||
|
auto y = <expr>; // unit 2
|
||||||
|
|
||||||
We handle work for the most part on a push basis. If your PR has been ignored for a while
|
auto x = <expr>; // unit 1
|
||||||
and is still relevant please bump it.
|
emit this->y(); // unit 2
|
||||||
|
|
||||||
|
auto x1 = <expr>; // unit 1
|
||||||
|
auto x2 = <expr>; // unit 1
|
||||||
|
auto x3 = <expr>; // unit 1
|
||||||
|
|
||||||
|
auto y1 = <expr>; // unit 2
|
||||||
|
auto y2 = <expr>; // unit 2
|
||||||
|
auto y3 = <expr>; // unit 2
|
||||||
|
|
||||||
|
// one unit
|
||||||
|
auto x = <expr>;
|
||||||
|
if (x...) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// if more than one variable needs to be used then add a newline
|
||||||
|
auto x = <expr>;
|
||||||
|
auto y = <expr>;
|
||||||
|
|
||||||
|
if (x && y) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Class formatting:
|
||||||
|
```cpp
|
||||||
|
//! Doc comment summary
|
||||||
|
/// Doc comment body
|
||||||
|
class Foo: public QObject {
|
||||||
|
// The Q_OBJECT macro comes first. Macros are ; terminated.
|
||||||
|
Q_OBJECT;
|
||||||
|
QML_ELEMENT;
|
||||||
|
QML_CLASSINFO(...);
|
||||||
|
// Properties must stay on a single line or the doc generator won't be able to pick them up
|
||||||
|
Q_PROPERTY(...);
|
||||||
|
/// Doc comment
|
||||||
|
Q_PROPERTY(...);
|
||||||
|
/// Doc comment
|
||||||
|
Q_PROPERTY(...);
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Classes should have explicit constructors if they aren't intended to
|
||||||
|
// implicitly cast. The constructor can be inline in the header if it has no body.
|
||||||
|
explicit Foo(QObject* parent = nullptr): QObject(parent) {}
|
||||||
|
|
||||||
|
// Instance functions if applicable.
|
||||||
|
static Foo* instance();
|
||||||
|
|
||||||
|
// Member functions unrelated to properties come next
|
||||||
|
void function();
|
||||||
|
void function();
|
||||||
|
void function();
|
||||||
|
|
||||||
|
// Then Q_INVOKABLEs
|
||||||
|
Q_INVOKABLE function();
|
||||||
|
/// Doc comment
|
||||||
|
Q_INVOKABLE function();
|
||||||
|
/// Doc comment
|
||||||
|
Q_INVOKABLE function();
|
||||||
|
|
||||||
|
// Then property related functions, in the order (bindable, getter, setter).
|
||||||
|
// Related functions may be included here as well. Function bodies may be inline
|
||||||
|
// if they are a single expression. There should be a newline between each
|
||||||
|
// property's methods.
|
||||||
|
[[nodiscard]] QBindable<T> bindableFoo() { return &this->bFoo; }
|
||||||
|
[[nodiscard]] T foo() const { return this->foo; }
|
||||||
|
void setFoo();
|
||||||
|
|
||||||
|
[[nodiscard]] T bar() const { return this->foo; }
|
||||||
|
void setBar();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
// Signals that are not property change related go first.
|
||||||
|
// Property change signals go in property definition order.
|
||||||
|
void asd();
|
||||||
|
void asd2();
|
||||||
|
void fooChanged();
|
||||||
|
void barChanged();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
// generally Q_INVOKABLEs are preferred to public slots.
|
||||||
|
void slot();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
// ...
|
||||||
|
|
||||||
|
private:
|
||||||
|
// statics, then functions, then fields
|
||||||
|
static const foo BAR;
|
||||||
|
static void foo();
|
||||||
|
|
||||||
|
void foo();
|
||||||
|
void bar();
|
||||||
|
|
||||||
|
// property related members are prefixed with `m`.
|
||||||
|
QString mFoo;
|
||||||
|
QString bar;
|
||||||
|
|
||||||
|
// Bindables go last and should be prefixed with `b`.
|
||||||
|
Q_OBJECT_BINDABLE_PROPERTY(Foo, QString, bFoo, &Foo::fooChanged);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linter
|
||||||
|
All contributions should pass the linter.
|
||||||
|
|
||||||
|
Note that running the linter requires disabling precompiled
|
||||||
|
headers and including the test codepaths:
|
||||||
|
```sh
|
||||||
|
$ just configure debug -DNO_PCH=ON -DBUILD_TESTING=ON
|
||||||
|
$ just lint-changed
|
||||||
|
```
|
||||||
|
|
||||||
|
If the linter is complaining about something that you think it should not,
|
||||||
|
please disable the lint in your MR and explain your reasoning if it isn't obvious.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
If you feel like the feature you are working on is very complex or likely to break,
|
||||||
|
please write some tests. We will ask you to directly if you send in an MR for an
|
||||||
|
overly complex or breakable feature.
|
||||||
|
|
||||||
|
At least all tests that passed before your changes should still be passing
|
||||||
|
by the time your contribution is ready.
|
||||||
|
|
||||||
|
You can run the tests using `just test` but you must enable them first
|
||||||
|
using `-DBUILD_TESTING=ON`.
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
Most of quickshell's documentation is automatically generated from the source code.
|
||||||
|
You should annotate `Q_PROPERTY`s and `Q_INVOKABLE`s with doc comments. Note that the parser
|
||||||
|
cannot handle random line breaks and will usually require you to disable clang-format if the
|
||||||
|
lines are too long.
|
||||||
|
|
||||||
|
Before submitting an MR, if adding new features please make sure the documentation is generated
|
||||||
|
reasonably using the `quickshell-docs` repo. We recommend checking it out at `/docs` in this repo.
|
||||||
|
|
||||||
|
Doc comments take the form `///` or `///!` (summary) and work with markdown.
|
||||||
|
You can reference other types using the `@@[Module.][Type.][member]` shorthand
|
||||||
|
where all parts are optional. If module or type are not specified they will
|
||||||
|
be inferred as the current module. Member can be a `property`, `function()` or `signal(s)`.
|
||||||
|
Look at existing code for how it works.
|
||||||
|
|
||||||
|
Quickshell modules additionally have a `module.md` file which contains a summary, description,
|
||||||
|
and list of headers to scan for documentation.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
Please structure your commit messages as `scope[!]: commit` where
|
||||||
|
the scope is something like `core` or `service/mpris`. (pick what has been
|
||||||
|
used historically or what makes sense if new). Add `!` for changes that break
|
||||||
|
existing APIs or functionality.
|
||||||
|
|
||||||
|
Commit descriptions should contain a summary of the changes if they are not
|
||||||
|
sufficiently addressed in the commit message.
|
||||||
|
|
||||||
|
Please squash/rebase additions or edits to previous changes and follow the
|
||||||
|
commit style to keep the history easily searchable at a glance.
|
||||||
|
Depending on the change, it is often reasonable to squash it into just
|
||||||
|
a single commit. (If you do not follow this we will squash your changes
|
||||||
|
for you.)
|
||||||
|
|
||||||
|
### Sending patches
|
||||||
|
You may contribute by submitting a pull request on github, asking for
|
||||||
|
an account on our git server, or emailing patches / git bundles
|
||||||
|
directly to `[email protected]`.
|
||||||
|
|
||||||
|
### Getting help
|
||||||
|
If you're getting stuck, you can come talk to us in the
|
||||||
|
[quickshell-development matrix room](https://matrix.to/#/#quickshell-development:outfoxxed.me)
|
||||||
|
for help on implementation, conventions, etc.
|
||||||
|
Feel free to ask for advice early in your implementation if you are
|
||||||
|
unsure.
|
||||||
-226
@@ -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.
|
|
||||||
@@ -13,7 +13,7 @@ lint-changed:
|
|||||||
git diff --name-only HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
|
git diff --name-only HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
|
||||||
|
|
||||||
lint-staged:
|
lint-staged:
|
||||||
git diff --staged --name-only --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='':
|
configure target='debug' *FLAGS='':
|
||||||
cmake -GNinja -B {{builddir}} \
|
cmake -GNinja -B {{builddir}} \
|
||||||
|
|||||||
@@ -7,9 +7,7 @@ This repo is hosted at:
|
|||||||
- https://github.com/quickshell-mirror/quickshell
|
- https://github.com/quickshell-mirror/quickshell
|
||||||
|
|
||||||
# Contributing / Development
|
# Contributing / Development
|
||||||
- [HACKING.md](HACKING.md) - Development instructions and policy.
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
||||||
- [CONTRIBUTING.md](CONTRIBUTING.md) - Contribution policy.
|
|
||||||
- [BUILD.md](BUILD.md) - Packaging and build instructions.
|
|
||||||
|
|
||||||
#### License
|
#### License
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ set shell id.
|
|||||||
- Added Quickshell version checking and version gated preprocessing.
|
- 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 a way to detect if an icon is from the system icon theme or not.
|
||||||
- Added vulkan support to screencopy.
|
- Added vulkan support to screencopy.
|
||||||
- Added generic WindowManager interface implementing ext-workspace.
|
|
||||||
|
|
||||||
## Other Changes
|
## Other Changes
|
||||||
|
|
||||||
@@ -34,10 +33,6 @@ set shell id.
|
|||||||
- IPC operations filter available instances to the current display connection by default.
|
- IPC operations filter available instances to the current display connection by default.
|
||||||
- PwNodeLinkTracker ignores sound level monitoring programs.
|
- PwNodeLinkTracker ignores sound level monitoring programs.
|
||||||
- Replaced breakpad with cpptrace.
|
- Replaced breakpad with cpptrace.
|
||||||
- Reloads are prevented if no file content has changed.
|
|
||||||
- Added `QS_DISABLE_FILE_WATCHER` environment variable to disable file watching.
|
|
||||||
- Added `QS_DISABLE_CRASH_HANDLER` environment variable to disable crash handling.
|
|
||||||
- Added `QS_CRASHREPORT_URL` environment variable to allow overriding the crash reporter link.
|
|
||||||
|
|
||||||
## Bug Fixes
|
## Bug Fixes
|
||||||
|
|
||||||
@@ -55,14 +50,9 @@ set shell id.
|
|||||||
- Fixed ClippingRectangle related crashes.
|
- Fixed ClippingRectangle related crashes.
|
||||||
- Fixed crashes when monitors are unplugged.
|
- Fixed crashes when monitors are unplugged.
|
||||||
- Fixed crashes when default pipewire devices are lost.
|
- Fixed crashes when default pipewire devices are lost.
|
||||||
- Fixed ToplevelManager not clearing activeToplevel on deactivation.
|
|
||||||
- Desktop action order is now preserved.
|
|
||||||
- Fixed partial socket reads in greetd and hyprland on slow machines.
|
|
||||||
|
|
||||||
## Packaging Changes
|
## Packaging Changes
|
||||||
|
|
||||||
- `glib` and `polkit` have been added as dependencies when compiling with polkit agent support.
|
- `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).
|
- `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.
|
- `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.
|
|
||||||
+12
-10
@@ -19,7 +19,6 @@
|
|||||||
wayland-protocols,
|
wayland-protocols,
|
||||||
wayland-scanner,
|
wayland-scanner,
|
||||||
xorg,
|
xorg,
|
||||||
libxcb ? xorg.libxcb,
|
|
||||||
libdrm,
|
libdrm,
|
||||||
libgbm ? null,
|
libgbm ? null,
|
||||||
vulkan-headers,
|
vulkan-headers,
|
||||||
@@ -76,21 +75,24 @@
|
|||||||
buildInputs = [
|
buildInputs = [
|
||||||
qt6.qtbase
|
qt6.qtbase
|
||||||
qt6.qtdeclarative
|
qt6.qtdeclarative
|
||||||
libdrm
|
|
||||||
cli11
|
cli11
|
||||||
]
|
]
|
||||||
++ lib.optional withQtSvg qt6.qtsvg
|
++ lib.optional withQtSvg qt6.qtsvg
|
||||||
++ lib.optional withCrashHandler (cpptrace.overrideAttrs (prev: {
|
++ lib.optionals withCrashHandler [
|
||||||
cmakeFlags = prev.cmakeFlags ++ [
|
(cpptrace.overrideAttrs (prev: {
|
||||||
"-DCPPTRACE_UNWIND_WITH_LIBUNWIND=TRUE"
|
cmakeFlags = prev.cmakeFlags ++ [
|
||||||
];
|
"-DCPPTRACE_UNWIND_WITH_LIBUNWIND=TRUE"
|
||||||
buildInputs = prev.buildInputs ++ [ libunwind ];
|
];
|
||||||
}))
|
buildInputs = prev.buildInputs ++ [ libunwind ];
|
||||||
|
}))
|
||||||
|
libunwind
|
||||||
|
libdwarf
|
||||||
|
]
|
||||||
++ lib.optional withJemalloc jemalloc
|
++ lib.optional withJemalloc jemalloc
|
||||||
++ lib.optional (withWayland && lib.strings.compareVersions qt6.qtbase.version "6.10.0" == -1) qt6.qtwayland
|
++ lib.optional (withWayland && lib.strings.compareVersions qt6.qtbase.version "6.10.0" == -1) qt6.qtwayland
|
||||||
++ lib.optionals withWayland [ wayland wayland-protocols ]
|
++ lib.optionals withWayland [ wayland wayland-protocols ]
|
||||||
++ lib.optionals (withWayland && libgbm != null) [ libgbm vulkan-headers ]
|
++ lib.optionals (withWayland && libgbm != null) [ libdrm libgbm vulkan-headers ]
|
||||||
++ lib.optional withX11 libxcb
|
++ lib.optional withX11 xorg.libxcb
|
||||||
++ lib.optional withPam pam
|
++ lib.optional withPam pam
|
||||||
++ lib.optional withPipewire pipewire
|
++ lib.optional withPipewire pipewire
|
||||||
++ lib.optionals withPolkit [ polkit glib ];
|
++ lib.optionals withPolkit [ polkit glib ];
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ add_subdirectory(window)
|
|||||||
add_subdirectory(io)
|
add_subdirectory(io)
|
||||||
add_subdirectory(widgets)
|
add_subdirectory(widgets)
|
||||||
add_subdirectory(ui)
|
add_subdirectory(ui)
|
||||||
add_subdirectory(windowmanager)
|
|
||||||
|
|
||||||
if (CRASH_HANDLER)
|
if (CRASH_HANDLER)
|
||||||
add_subdirectory(crash)
|
add_subdirectory(crash)
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ else()
|
|||||||
set(CRASH_HANDLER_DEF 0)
|
set(CRASH_HANDLER_DEF 0)
|
||||||
endif()
|
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)
|
configure_file(build.hpp.in build.hpp @ONLY ESCAPE_QUOTES)
|
||||||
|
|
||||||
target_include_directories(quickshell-build INTERFACE ${CMAKE_CURRENT_BINARY_DIR})
|
target_include_directories(quickshell-build INTERFACE ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
@@ -8,10 +8,10 @@
|
|||||||
#define QS_UNRELEASED_FEATURES "@UNRELEASED_FEATURES@"
|
#define QS_UNRELEASED_FEATURES "@UNRELEASED_FEATURES@"
|
||||||
#define GIT_REVISION "@GIT_REVISION@"
|
#define GIT_REVISION "@GIT_REVISION@"
|
||||||
#define DISTRIBUTOR "@DISTRIBUTOR@"
|
#define DISTRIBUTOR "@DISTRIBUTOR@"
|
||||||
|
#define DISTRIBUTOR_DEBUGINFO_AVAILABLE @DEBUGINFO_AVAILABLE@
|
||||||
#define CRASH_HANDLER @CRASH_HANDLER_DEF@
|
#define CRASH_HANDLER @CRASH_HANDLER_DEF@
|
||||||
#define BUILD_TYPE "@CMAKE_BUILD_TYPE@"
|
#define BUILD_TYPE "@CMAKE_BUILD_TYPE@"
|
||||||
#define COMPILER "@CMAKE_CXX_COMPILER_ID@ (@CMAKE_CXX_COMPILER_VERSION@)"
|
#define COMPILER "@CMAKE_CXX_COMPILER_ID@ (@CMAKE_CXX_COMPILER_VERSION@)"
|
||||||
#define COMPILE_FLAGS "@CMAKE_CXX_FLAGS@"
|
#define COMPILE_FLAGS "@CMAKE_CXX_FLAGS@"
|
||||||
#define BUILD_CONFIGURATION "@QS_BUILD_OPTIONS@"
|
#define BUILD_CONFIGURATION "@QS_BUILD_OPTIONS@"
|
||||||
#define CRASHREPORT_URL "@CRASHREPORT_URL@"
|
|
||||||
// NOLINTEND
|
// NOLINTEND
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
pkg_check_modules(libdrm REQUIRED IMPORTED_TARGET libdrm)
|
|
||||||
qt_add_library(quickshell-core STATIC
|
qt_add_library(quickshell-core STATIC
|
||||||
plugin.cpp
|
plugin.cpp
|
||||||
shell.cpp
|
shell.cpp
|
||||||
@@ -41,8 +40,6 @@ qt_add_library(quickshell-core STATIC
|
|||||||
scriptmodel.cpp
|
scriptmodel.cpp
|
||||||
colorquantizer.cpp
|
colorquantizer.cpp
|
||||||
toolsupport.cpp
|
toolsupport.cpp
|
||||||
streamreader.cpp
|
|
||||||
debuginfo.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_add_qml_module(quickshell-core
|
qt_add_qml_module(quickshell-core
|
||||||
@@ -55,7 +52,7 @@ qt_add_qml_module(quickshell-core
|
|||||||
|
|
||||||
install_qml_module(quickshell-core)
|
install_qml_module(quickshell-core)
|
||||||
|
|
||||||
target_link_libraries(quickshell-core PRIVATE Qt::Quick Qt::QuickPrivate Qt::Widgets quickshell-build PkgConfig::libdrm)
|
target_link_libraries(quickshell-core PRIVATE Qt::Quick Qt::QuickPrivate Qt::Widgets quickshell-build)
|
||||||
|
|
||||||
qs_module_pch(quickshell-core SET large)
|
qs_module_pch(quickshell-core SET large)
|
||||||
|
|
||||||
|
|||||||
@@ -1,175 +0,0 @@
|
|||||||
#include "debuginfo.hpp"
|
|
||||||
#include <array>
|
|
||||||
#include <cstring>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <qconfig.h>
|
|
||||||
#include <qcontainerfwd.h>
|
|
||||||
#include <qdebug.h>
|
|
||||||
#include <qfile.h>
|
|
||||||
#include <qfloat16.h>
|
|
||||||
#include <qhashfunctions.h>
|
|
||||||
#include <qscopeguard.h>
|
|
||||||
#include <qtversion.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <xf86drm.h>
|
|
||||||
|
|
||||||
#include "build.hpp"
|
|
||||||
|
|
||||||
extern char** environ; // NOLINT
|
|
||||||
|
|
||||||
namespace qs::debuginfo {
|
|
||||||
|
|
||||||
QString qsVersion() {
|
|
||||||
return QS_VERSION " (revision " GIT_REVISION ", distributed by " DISTRIBUTOR ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
QString qtVersion() { return qVersion() % QStringLiteral(" (built against " QT_VERSION_STR ")"); }
|
|
||||||
|
|
||||||
QString gpuInfo() {
|
|
||||||
auto deviceCount = drmGetDevices2(0, nullptr, 0);
|
|
||||||
if (deviceCount < 0) return "Failed to get DRM device count: " % QString::number(deviceCount);
|
|
||||||
auto* devices = new drmDevicePtr[deviceCount];
|
|
||||||
auto devicesArrayGuard = qScopeGuard([&] { delete[] devices; });
|
|
||||||
auto r = drmGetDevices2(0, devices, deviceCount);
|
|
||||||
if (deviceCount < 0) return "Failed to get DRM devices: " % QString::number(r);
|
|
||||||
auto devicesGuard = qScopeGuard([&] {
|
|
||||||
for (auto i = 0; i != deviceCount; ++i) drmFreeDevice(&devices[i]); // NOLINT
|
|
||||||
});
|
|
||||||
|
|
||||||
QString info;
|
|
||||||
auto stream = QTextStream(&info);
|
|
||||||
|
|
||||||
for (auto i = 0; i != deviceCount; ++i) {
|
|
||||||
auto* device = devices[i]; // NOLINT
|
|
||||||
|
|
||||||
int deviceNodeType = -1;
|
|
||||||
if (device->available_nodes & (1 << DRM_NODE_RENDER)) deviceNodeType = DRM_NODE_RENDER;
|
|
||||||
else if (device->available_nodes & (1 << DRM_NODE_PRIMARY)) deviceNodeType = DRM_NODE_PRIMARY;
|
|
||||||
|
|
||||||
if (deviceNodeType == -1) continue;
|
|
||||||
|
|
||||||
auto* deviceNode = device->nodes[DRM_NODE_RENDER]; // NOLINT
|
|
||||||
|
|
||||||
auto driver = [&]() -> QString {
|
|
||||||
auto fd = open(deviceNode, O_RDWR | O_CLOEXEC);
|
|
||||||
if (fd == -1) return "<failed to open device node>";
|
|
||||||
auto fdGuard = qScopeGuard([&] { close(fd); });
|
|
||||||
auto* ver = drmGetVersion(fd);
|
|
||||||
if (!ver) return "<drmGetVersion failed>";
|
|
||||||
auto verGuard = qScopeGuard([&] { drmFreeVersion(ver); });
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
return QString(ver->name)
|
|
||||||
% ' ' % QString::number(ver->version_major)
|
|
||||||
% '.' % QString::number(ver->version_minor)
|
|
||||||
% '.' % QString::number(ver->version_patchlevel)
|
|
||||||
% " (" % ver->desc % ')';
|
|
||||||
// clang-format on
|
|
||||||
}();
|
|
||||||
|
|
||||||
QString product = "unknown";
|
|
||||||
QString address = "unknown";
|
|
||||||
|
|
||||||
auto hex = [](int num, int pad) { return QString::number(num, 16).rightJustified(pad, '0'); };
|
|
||||||
|
|
||||||
switch (device->bustype) {
|
|
||||||
case DRM_BUS_PCI: {
|
|
||||||
auto* b = device->businfo.pci;
|
|
||||||
auto* d = device->deviceinfo.pci;
|
|
||||||
address = "PCI " % hex(b->bus, 2) % ':' % hex(b->dev, 2) % '.' % hex(b->func, 1);
|
|
||||||
product = hex(d->vendor_id, 4) % ':' % hex(d->device_id, 4);
|
|
||||||
} break;
|
|
||||||
case DRM_BUS_USB: {
|
|
||||||
auto* b = device->businfo.usb;
|
|
||||||
auto* d = device->deviceinfo.usb;
|
|
||||||
address = "USB " % QString::number(b->bus) % ':' % QString::number(b->dev);
|
|
||||||
product = hex(d->vendor, 4) % ':' % hex(d->product, 4);
|
|
||||||
} break;
|
|
||||||
default: break;
|
|
||||||
}
|
|
||||||
|
|
||||||
stream << "GPU " << deviceNode << "\n Driver: " << driver << "\n Model: " << product
|
|
||||||
<< "\n Address: " << address << '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString systemInfo() {
|
|
||||||
QString info;
|
|
||||||
auto stream = QTextStream(&info);
|
|
||||||
|
|
||||||
stream << gpuInfo() << '\n';
|
|
||||||
|
|
||||||
stream << "/etc/os-release:";
|
|
||||||
auto osReleaseFile = QFile("/etc/os-release");
|
|
||||||
if (osReleaseFile.open(QFile::ReadOnly)) {
|
|
||||||
stream << '\n' << osReleaseFile.readAll() << '\n';
|
|
||||||
osReleaseFile.close();
|
|
||||||
} else {
|
|
||||||
stream << "FAILED TO OPEN\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
stream << "/etc/lsb-release:";
|
|
||||||
auto lsbReleaseFile = QFile("/etc/lsb-release");
|
|
||||||
if (lsbReleaseFile.open(QFile::ReadOnly)) {
|
|
||||||
stream << '\n' << lsbReleaseFile.readAll();
|
|
||||||
lsbReleaseFile.close();
|
|
||||||
} else {
|
|
||||||
stream << "FAILED TO OPEN\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString envInfo() {
|
|
||||||
QString info;
|
|
||||||
auto stream = QTextStream(&info);
|
|
||||||
|
|
||||||
for (auto** envp = environ; *envp != nullptr; ++envp) { // NOLINT
|
|
||||||
auto prefixes = std::array<std::string_view, 5> {
|
|
||||||
"QS_",
|
|
||||||
"QT_",
|
|
||||||
"QML_",
|
|
||||||
"QML2_",
|
|
||||||
"QSG_",
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const auto& prefix: prefixes) {
|
|
||||||
if (strncmp(prefix.data(), *envp, prefix.length()) == 0) goto print;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
|
|
||||||
print:
|
|
||||||
stream << *envp << '\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString combinedInfo() {
|
|
||||||
QString info;
|
|
||||||
auto stream = QTextStream(&info);
|
|
||||||
|
|
||||||
stream << "===== Version Information =====\n";
|
|
||||||
stream << "Quickshell: " << qsVersion() << '\n';
|
|
||||||
stream << "Qt: " << qtVersion() << '\n';
|
|
||||||
|
|
||||||
stream << "\n===== Build Information =====\n";
|
|
||||||
stream << "Build Type: " << BUILD_TYPE << '\n';
|
|
||||||
stream << "Compiler: " << COMPILER << '\n';
|
|
||||||
stream << "Compile Flags: " << COMPILE_FLAGS << '\n';
|
|
||||||
stream << "Configuration:\n" << BUILD_CONFIGURATION << '\n';
|
|
||||||
|
|
||||||
stream << "\n===== System Information =====\n";
|
|
||||||
stream << systemInfo();
|
|
||||||
|
|
||||||
stream << "\n===== Environment (trimmed) =====\n";
|
|
||||||
stream << envInfo();
|
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace qs::debuginfo
|
|
||||||
@@ -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
|
|
||||||
@@ -107,10 +107,7 @@ ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString&
|
|||||||
auto groupName = QString();
|
auto groupName = QString();
|
||||||
auto entries = QHash<QString, QPair<Locale, QString>>();
|
auto entries = QHash<QString, QPair<Locale, QString>>();
|
||||||
|
|
||||||
auto actionOrder = QStringList();
|
auto finishCategory = [&data, &groupName, &entries]() {
|
||||||
auto pendingActions = QHash<QString, DesktopActionData>();
|
|
||||||
|
|
||||||
auto finishCategory = [&data, &groupName, &entries, &actionOrder, &pendingActions]() {
|
|
||||||
if (groupName == "Desktop Entry") {
|
if (groupName == "Desktop Entry") {
|
||||||
if (entries.value("Type").second != "Application") return;
|
if (entries.value("Type").second != "Application") return;
|
||||||
|
|
||||||
@@ -132,10 +129,9 @@ ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString&
|
|||||||
else if (key == "Terminal") data.terminal = value == "true";
|
else if (key == "Terminal") data.terminal = value == "true";
|
||||||
else if (key == "Categories") data.categories = value.split(u';', Qt::SkipEmptyParts);
|
else if (key == "Categories") data.categories = value.split(u';', Qt::SkipEmptyParts);
|
||||||
else if (key == "Keywords") data.keywords = value.split(u';', Qt::SkipEmptyParts);
|
else if (key == "Keywords") data.keywords = value.split(u';', Qt::SkipEmptyParts);
|
||||||
else if (key == "Actions") actionOrder = value.split(u';', Qt::SkipEmptyParts);
|
|
||||||
}
|
}
|
||||||
} else if (groupName.startsWith("Desktop Action ")) {
|
} else if (groupName.startsWith("Desktop Action ")) {
|
||||||
auto actionName = groupName.sliced(15);
|
auto actionName = groupName.sliced(16);
|
||||||
DesktopActionData action;
|
DesktopActionData action;
|
||||||
action.id = actionName;
|
action.id = actionName;
|
||||||
|
|
||||||
@@ -151,7 +147,7 @@ ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString&
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pendingActions.insert(actionName, action);
|
data.actions.insert(actionName, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.clear();
|
entries.clear();
|
||||||
@@ -197,13 +193,6 @@ ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString&
|
|||||||
}
|
}
|
||||||
|
|
||||||
finishCategory();
|
finishCategory();
|
||||||
|
|
||||||
for (const auto& actionId: actionOrder) {
|
|
||||||
if (pendingActions.contains(actionId)) {
|
|
||||||
data.actions.append(pendingActions.value(actionId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,18 +216,17 @@ void DesktopEntry::updateState(const ParsedDesktopEntryData& newState) {
|
|||||||
this->updateActions(newState.actions);
|
this->updateActions(newState.actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DesktopEntry::updateActions(const QVector<DesktopActionData>& newActions) {
|
void DesktopEntry::updateActions(const QHash<QString, DesktopActionData>& newActions) {
|
||||||
auto old = this->mActions;
|
auto old = this->mActions;
|
||||||
this->mActions.clear();
|
|
||||||
|
|
||||||
for (const auto& d: newActions) {
|
for (const auto& [key, d]: newActions.asKeyValueRange()) {
|
||||||
DesktopAction* act = nullptr;
|
DesktopAction* act = nullptr;
|
||||||
auto found = std::ranges::find(old, d.id, &DesktopAction::mId);
|
if (auto found = old.find(key); found != old.end()) {
|
||||||
if (found != old.end()) {
|
act = found.value();
|
||||||
act = *found;
|
|
||||||
old.erase(found);
|
old.erase(found);
|
||||||
} else {
|
} else {
|
||||||
act = new DesktopAction(d.id, this);
|
act = new DesktopAction(d.id, this);
|
||||||
|
this->mActions.insert(key, act);
|
||||||
}
|
}
|
||||||
|
|
||||||
Qt::beginPropertyUpdateGroup();
|
Qt::beginPropertyUpdateGroup();
|
||||||
@@ -249,7 +237,6 @@ void DesktopEntry::updateActions(const QVector<DesktopActionData>& newActions) {
|
|||||||
Qt::endPropertyUpdateGroup();
|
Qt::endPropertyUpdateGroup();
|
||||||
|
|
||||||
act->mEntries = d.entries;
|
act->mEntries = d.entries;
|
||||||
this->mActions.append(act);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto* leftover: old) {
|
for (auto* leftover: old) {
|
||||||
@@ -263,7 +250,7 @@ void DesktopEntry::execute() const {
|
|||||||
|
|
||||||
bool DesktopEntry::isValid() const { return !this->bName.value().isEmpty(); }
|
bool DesktopEntry::isValid() const { return !this->bName.value().isEmpty(); }
|
||||||
|
|
||||||
QVector<DesktopAction*> DesktopEntry::actions() const { return this->mActions; }
|
QVector<DesktopAction*> DesktopEntry::actions() const { return this->mActions.values(); }
|
||||||
|
|
||||||
QVector<QString> DesktopEntry::parseExecString(const QString& execString) {
|
QVector<QString> DesktopEntry::parseExecString(const QString& execString) {
|
||||||
QVector<QString> arguments;
|
QVector<QString> arguments;
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ struct ParsedDesktopEntryData {
|
|||||||
QVector<QString> categories;
|
QVector<QString> categories;
|
||||||
QVector<QString> keywords;
|
QVector<QString> keywords;
|
||||||
QHash<QString, QString> entries;
|
QHash<QString, QString> entries;
|
||||||
QVector<DesktopActionData> actions;
|
QHash<QString, DesktopActionData> actions;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A desktop entry. See @@DesktopEntries for details.
|
/// A desktop entry. See @@DesktopEntries for details.
|
||||||
@@ -164,10 +164,10 @@ public:
|
|||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateActions(const QVector<DesktopActionData>& newActions);
|
void updateActions(const QHash<QString, DesktopActionData>& newActions);
|
||||||
|
|
||||||
ParsedDesktopEntryData state;
|
ParsedDesktopEntryData state;
|
||||||
QVector<DesktopAction*> mActions;
|
QHash<QString, DesktopAction*> mActions;
|
||||||
|
|
||||||
friend class DesktopAction;
|
friend class DesktopAction;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -209,8 +209,6 @@ bool EngineGeneration::setExtraWatchedFiles(const QVector<QString>& files) {
|
|||||||
for (const auto& file: files) {
|
for (const auto& file: files) {
|
||||||
if (!this->scanner.scannedFiles.contains(file)) {
|
if (!this->scanner.scannedFiles.contains(file)) {
|
||||||
this->extraWatchedFiles.append(file);
|
this->extraWatchedFiles.append(file);
|
||||||
QByteArray data;
|
|
||||||
this->scanner.readAndHashFile(file, data);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,11 +229,6 @@ void EngineGeneration::onFileChanged(const QString& name) {
|
|||||||
auto fileInfo = QFileInfo(name);
|
auto fileInfo = QFileInfo(name);
|
||||||
if (fileInfo.isFile() && fileInfo.size() == 0) return;
|
if (fileInfo.isFile() && fileInfo.size() == 0) return;
|
||||||
|
|
||||||
if (!this->scanner.hasFileContentChanged(name)) {
|
|
||||||
qCDebug(logQmlScanner) << "Ignoring file change with unchanged content:" << name;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
emit this->filesChanged();
|
emit this->filesChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,11 +237,6 @@ void EngineGeneration::onDirectoryChanged() {
|
|||||||
// try to find any files that were just deleted from a replace operation
|
// try to find any files that were just deleted from a replace operation
|
||||||
for (auto& file: this->deletedWatchedFiles) {
|
for (auto& file: this->deletedWatchedFiles) {
|
||||||
if (QFileInfo(file).exists()) {
|
if (QFileInfo(file).exists()) {
|
||||||
if (!this->scanner.hasFileContentChanged(file)) {
|
|
||||||
qCDebug(logQmlScanner) << "Ignoring restored file with unchanged content:" << file;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
emit this->filesChanged();
|
emit this->filesChanged();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,9 +60,7 @@ void QuickshellSettings::setWorkingDirectory(QString workingDirectory) { // NOLI
|
|||||||
emit this->workingDirectoryChanged();
|
emit this->workingDirectoryChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QuickshellSettings::watchFiles() const {
|
bool QuickshellSettings::watchFiles() const { return this->mWatchFiles; }
|
||||||
return this->mWatchFiles && qEnvironmentVariableIsEmpty("QS_DISABLE_FILE_WATCHER");
|
|
||||||
}
|
|
||||||
|
|
||||||
void QuickshellSettings::setWatchFiles(bool watchFiles) {
|
void QuickshellSettings::setWatchFiles(bool watchFiles) {
|
||||||
if (watchFiles == this->mWatchFiles) return;
|
if (watchFiles == this->mWatchFiles) return;
|
||||||
|
|||||||
+11
-30
@@ -3,7 +3,6 @@
|
|||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
#include <qcryptographichash.h>
|
|
||||||
#include <qdir.h>
|
#include <qdir.h>
|
||||||
#include <qfileinfo.h>
|
#include <qfileinfo.h>
|
||||||
#include <qjsengine.h>
|
#include <qjsengine.h>
|
||||||
@@ -22,25 +21,6 @@
|
|||||||
|
|
||||||
QS_LOGGING_CATEGORY(logQmlScanner, "quickshell.qmlscanner", QtWarningMsg);
|
QS_LOGGING_CATEGORY(logQmlScanner, "quickshell.qmlscanner", QtWarningMsg);
|
||||||
|
|
||||||
bool QmlScanner::readAndHashFile(const QString& path, QByteArray& data) {
|
|
||||||
auto file = QFile(path);
|
|
||||||
if (!file.open(QFile::ReadOnly)) return false;
|
|
||||||
data = file.readAll();
|
|
||||||
this->fileHashes.insert(path, QCryptographicHash::hash(data, QCryptographicHash::Md5));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool QmlScanner::hasFileContentChanged(const QString& path) const {
|
|
||||||
auto it = this->fileHashes.constFind(path);
|
|
||||||
if (it == this->fileHashes.constEnd()) return true;
|
|
||||||
|
|
||||||
auto file = QFile(path);
|
|
||||||
if (!file.open(QFile::ReadOnly)) return true;
|
|
||||||
|
|
||||||
auto newHash = QCryptographicHash::hash(file.readAll(), QCryptographicHash::Md5);
|
|
||||||
return newHash != it.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
void QmlScanner::scanDir(const QDir& dir) {
|
void QmlScanner::scanDir(const QDir& dir) {
|
||||||
if (this->scannedDirs.contains(dir)) return;
|
if (this->scannedDirs.contains(dir)) return;
|
||||||
this->scannedDirs.push_back(dir);
|
this->scannedDirs.push_back(dir);
|
||||||
@@ -129,13 +109,13 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
|
|||||||
|
|
||||||
qCDebug(logQmlScanner) << "Scanning qml file" << path;
|
qCDebug(logQmlScanner) << "Scanning qml file" << path;
|
||||||
|
|
||||||
QByteArray fileData;
|
auto file = QFile(path);
|
||||||
if (!this->readAndHashFile(path, fileData)) {
|
if (!file.open(QFile::ReadOnly | QFile::Text)) {
|
||||||
qCWarning(logQmlScanner) << "Failed to open file" << path;
|
qCWarning(logQmlScanner) << "Failed to open file" << path;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto stream = QTextStream(&fileData);
|
auto stream = QTextStream(&file);
|
||||||
auto imports = QVector<QString>();
|
auto imports = QVector<QString>();
|
||||||
|
|
||||||
bool inHeader = true;
|
bool inHeader = true;
|
||||||
@@ -239,6 +219,8 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
|
|||||||
postError("unclosed preprocessor if block");
|
postError("unclosed preprocessor if block");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
|
||||||
if (isOverridden) {
|
if (isOverridden) {
|
||||||
this->fileIntercepts.insert(path, overrideText);
|
this->fileIntercepts.insert(path, overrideText);
|
||||||
}
|
}
|
||||||
@@ -275,11 +257,8 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (import.endsWith(".js")) {
|
if (import.endsWith(".js")) this->scannedFiles.push_back(cpath);
|
||||||
this->scannedFiles.push_back(cpath);
|
else this->scanDir(cpath);
|
||||||
QByteArray jsData;
|
|
||||||
this->readAndHashFile(cpath, jsData);
|
|
||||||
} else this->scanDir(cpath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -294,12 +273,14 @@ void QmlScanner::scanQmlRoot(const QString& path) {
|
|||||||
bool QmlScanner::scanQmlJson(const QString& path) {
|
bool QmlScanner::scanQmlJson(const QString& path) {
|
||||||
qCDebug(logQmlScanner) << "Scanning qml.json file" << path;
|
qCDebug(logQmlScanner) << "Scanning qml.json file" << path;
|
||||||
|
|
||||||
QByteArray data;
|
auto file = QFile(path);
|
||||||
if (!this->readAndHashFile(path, data)) {
|
if (!file.open(QFile::ReadOnly | QFile::Text)) {
|
||||||
qCWarning(logQmlScanner) << "Failed to open file" << path;
|
qCWarning(logQmlScanner) << "Failed to open file" << path;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto data = file.readAll();
|
||||||
|
|
||||||
// Importing this makes CI builds fail for some reason.
|
// Importing this makes CI builds fail for some reason.
|
||||||
QJsonParseError error; // NOLINT (misc-include-cleaner)
|
QJsonParseError error; // NOLINT (misc-include-cleaner)
|
||||||
auto json = QJsonDocument::fromJson(data, &error);
|
auto json = QJsonDocument::fromJson(data, &error);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <qbytearray.h>
|
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
#include <qdir.h>
|
#include <qdir.h>
|
||||||
#include <qhash.h>
|
#include <qhash.h>
|
||||||
@@ -22,7 +21,6 @@ public:
|
|||||||
|
|
||||||
QVector<QDir> scannedDirs;
|
QVector<QDir> scannedDirs;
|
||||||
QVector<QString> scannedFiles;
|
QVector<QString> scannedFiles;
|
||||||
QHash<QString, QByteArray> fileHashes;
|
|
||||||
QHash<QString, QString> fileIntercepts;
|
QHash<QString, QString> fileIntercepts;
|
||||||
|
|
||||||
struct ScanError {
|
struct ScanError {
|
||||||
@@ -33,9 +31,6 @@ public:
|
|||||||
|
|
||||||
QVector<ScanError> scanErrors;
|
QVector<ScanError> scanErrors;
|
||||||
|
|
||||||
bool readAndHashFile(const QString& path, QByteArray& data);
|
|
||||||
[[nodiscard]] bool hasFileContentChanged(const QString& path) const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QDir rootPath;
|
QDir rootPath;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
};
|
|
||||||
+2
-17
@@ -1,7 +1,6 @@
|
|||||||
#include "handler.hpp"
|
#include "handler.hpp"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cerrno>
|
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -65,27 +64,12 @@ void signalHandler(
|
|||||||
for (size_t i = 0; i < static_cast<size_t>(frameCount); i++) {
|
for (size_t i = 0; i < static_cast<size_t>(frameCount); i++) {
|
||||||
auto frame = cpptrace::safe_object_frame();
|
auto frame = cpptrace::safe_object_frame();
|
||||||
cpptrace::get_safe_object_frame(traceBuffer[i], &frame);
|
cpptrace::get_safe_object_frame(traceBuffer[i], &frame);
|
||||||
|
write(CrashInfo::INSTANCE.traceFd, &frame, sizeof(cpptrace::safe_object_frame));
|
||||||
auto* wptr = reinterpret_cast<char*>(&frame);
|
|
||||||
auto* end = wptr + sizeof(cpptrace::safe_object_frame); // NOLINT
|
|
||||||
while (wptr != end) {
|
|
||||||
auto r = write(CrashInfo::INSTANCE.traceFd, &frame, sizeof(cpptrace::safe_object_frame));
|
|
||||||
if (r < 0 && errno == EINTR) continue;
|
|
||||||
if (r <= 0) goto fail;
|
|
||||||
wptr += r; // NOLINT
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fail:;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto coredumpPid = fork();
|
auto coredumpPid = fork();
|
||||||
if (coredumpPid == 0) {
|
if (coredumpPid == 0) {
|
||||||
// NOLINTBEGIN (misc-include-cleaner)
|
|
||||||
sigset_t set;
|
|
||||||
sigfillset(&set);
|
|
||||||
sigprocmask(SIG_UNBLOCK, &set, nullptr);
|
|
||||||
// NOLINTEND
|
|
||||||
raise(sig);
|
raise(sig);
|
||||||
_exit(-1);
|
_exit(-1);
|
||||||
}
|
}
|
||||||
@@ -184,6 +168,7 @@ void CrashHandler::init() {
|
|||||||
// Set up alternate signal stack for stack overflow handling
|
// Set up alternate signal stack for stack overflow handling
|
||||||
auto ss = stack_t();
|
auto ss = stack_t();
|
||||||
ss.ss_sp = new char[SIGSTKSZ];
|
ss.ss_sp = new char[SIGSTKSZ];
|
||||||
|
;
|
||||||
ss.ss_size = SIGSTKSZ;
|
ss.ss_size = SIGSTKSZ;
|
||||||
ss.ss_flags = 0;
|
ss.ss_flags = 0;
|
||||||
sigaltstack(&ss, nullptr);
|
sigaltstack(&ss, nullptr);
|
||||||
|
|||||||
+15
-16
@@ -5,7 +5,6 @@
|
|||||||
#include <qapplication.h>
|
#include <qapplication.h>
|
||||||
#include <qboxlayout.h>
|
#include <qboxlayout.h>
|
||||||
#include <qconfig.h>
|
#include <qconfig.h>
|
||||||
#include <qcontainerfwd.h>
|
|
||||||
#include <qdesktopservices.h>
|
#include <qdesktopservices.h>
|
||||||
#include <qfont.h>
|
#include <qfont.h>
|
||||||
#include <qfontinfo.h>
|
#include <qfontinfo.h>
|
||||||
@@ -13,22 +12,11 @@
|
|||||||
#include <qnamespace.h>
|
#include <qnamespace.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qpushbutton.h>
|
#include <qpushbutton.h>
|
||||||
#include <qtenvironmentvariables.h>
|
|
||||||
#include <qtversion.h>
|
#include <qtversion.h>
|
||||||
#include <qwidget.h>
|
#include <qwidget.h>
|
||||||
|
|
||||||
#include "build.hpp"
|
#include "build.hpp"
|
||||||
|
|
||||||
namespace {
|
|
||||||
QString crashreportUrl() {
|
|
||||||
if (auto url = qEnvironmentVariable("QS_CRASHREPORT_URL"); !url.isEmpty()) {
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
return CRASHREPORT_URL;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
class ReportLabel: public QWidget {
|
class ReportLabel: public QWidget {
|
||||||
public:
|
public:
|
||||||
ReportLabel(const QString& label, const QString& content, QWidget* parent): QWidget(parent) {
|
ReportLabel(const QString& label, const QString& content, QWidget* parent): QWidget(parent) {
|
||||||
@@ -79,16 +67,22 @@ CrashReporterGui::CrashReporterGui(QString reportFolder, int pid)
|
|||||||
|
|
||||||
if (qtVersionMatches) {
|
if (qtVersionMatches) {
|
||||||
mainLayout->addWidget(
|
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 {
|
} else {
|
||||||
mainLayout->addWidget(new QLabel(
|
mainLayout->addWidget(new QLabel(
|
||||||
"Please rebuild Quickshell against the current Qt version.\n"
|
"Please rebuild Quickshell against the current Qt version.\n"
|
||||||
"If this does not solve the problem, please open a bug report 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);
|
auto* buttons = new QWidget(this);
|
||||||
buttons->setMinimumWidth(900);
|
buttons->setMinimumWidth(900);
|
||||||
@@ -118,5 +112,10 @@ void CrashReporterGui::openFolder() {
|
|||||||
QDesktopServices::openUrl(QUrl::fromLocalFile(this->reportFolder));
|
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(); }
|
void CrashReporterGui::cancel() { QApplication::quit(); }
|
||||||
+31
-3
@@ -6,6 +6,7 @@
|
|||||||
#include <cpptrace/basic.hpp>
|
#include <cpptrace/basic.hpp>
|
||||||
#include <cpptrace/formatting.hpp>
|
#include <cpptrace/formatting.hpp>
|
||||||
#include <qapplication.h>
|
#include <qapplication.h>
|
||||||
|
#include <qconfig.h>
|
||||||
#include <qcoreapplication.h>
|
#include <qcoreapplication.h>
|
||||||
#include <qdatastream.h>
|
#include <qdatastream.h>
|
||||||
#include <qdir.h>
|
#include <qdir.h>
|
||||||
@@ -14,18 +15,19 @@
|
|||||||
#include <qloggingcategory.h>
|
#include <qloggingcategory.h>
|
||||||
#include <qtenvironmentvariables.h>
|
#include <qtenvironmentvariables.h>
|
||||||
#include <qtextstream.h>
|
#include <qtextstream.h>
|
||||||
|
#include <qtversion.h>
|
||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
#include <sys/sendfile.h>
|
#include <sys/sendfile.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "../core/debuginfo.hpp"
|
|
||||||
#include "../core/instanceinfo.hpp"
|
#include "../core/instanceinfo.hpp"
|
||||||
#include "../core/logcat.hpp"
|
#include "../core/logcat.hpp"
|
||||||
#include "../core/logging.hpp"
|
#include "../core/logging.hpp"
|
||||||
#include "../core/logging_p.hpp"
|
#include "../core/logging_p.hpp"
|
||||||
#include "../core/paths.hpp"
|
#include "../core/paths.hpp"
|
||||||
#include "../core/ringbuf.hpp"
|
#include "../core/ringbuf.hpp"
|
||||||
|
#include "build.hpp"
|
||||||
#include "interface.hpp"
|
#include "interface.hpp"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -169,15 +171,41 @@ void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instance) {
|
|||||||
qCCritical(logCrashReporter) << "Failed to open crash info file for writing.";
|
qCCritical(logCrashReporter) << "Failed to open crash info file for writing.";
|
||||||
} else {
|
} else {
|
||||||
auto stream = QTextStream(&extraInfoFile);
|
auto stream = QTextStream(&extraInfoFile);
|
||||||
stream << 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 << "Signal: " << strsignal(crashSignal) << " (" << crashSignal << ")\n"; // NOLINT
|
||||||
stream << "Crashed process ID: " << crashProc << '\n';
|
stream << "Crashed process ID: " << crashProc << '\n';
|
||||||
stream << "Run ID: " << instance.instanceId << '\n';
|
stream << "Run ID: " << instance.instanceId << '\n';
|
||||||
stream << "Shell ID: " << instance.shellId << '\n';
|
stream << "Shell ID: " << instance.shellId << '\n';
|
||||||
stream << "Config Path: " << instance.configPath << '\n';
|
stream << "Config Path: " << instance.configPath << '\n';
|
||||||
|
|
||||||
|
stream << "\n===== 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";
|
stream << "\n===== Stacktrace =====\n";
|
||||||
if (stacktrace.empty()) {
|
if (stacktrace.empty()) {
|
||||||
stream << "(no trace available)\n";
|
stream << "(no trace available)\n";
|
||||||
|
|||||||
+1
-4
@@ -3,7 +3,6 @@
|
|||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
#include <qbuffer.h>
|
#include <qbuffer.h>
|
||||||
#include <qcoreapplication.h>
|
|
||||||
#include <qlocalserver.h>
|
#include <qlocalserver.h>
|
||||||
#include <qlocalsocket.h>
|
#include <qlocalsocket.h>
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
@@ -128,9 +127,7 @@ int IpcClient::connect(const QString& id, const std::function<void(IpcClient& cl
|
|||||||
|
|
||||||
void IpcKillCommand::exec(IpcServerConnection* /*unused*/) {
|
void IpcKillCommand::exec(IpcServerConnection* /*unused*/) {
|
||||||
qInfo() << "Exiting due to IPC request.";
|
qInfo() << "Exiting due to IPC request.";
|
||||||
auto* generation = EngineGeneration::currentGeneration();
|
EngineGeneration::currentGeneration()->quit();
|
||||||
if (generation) generation->quit();
|
|
||||||
else QCoreApplication::exit(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace qs::ipc
|
} // namespace qs::ipc
|
||||||
+15
-5
@@ -25,12 +25,12 @@
|
|||||||
#include <qtversion.h>
|
#include <qtversion.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "../core/debuginfo.hpp"
|
|
||||||
#include "../core/instanceinfo.hpp"
|
#include "../core/instanceinfo.hpp"
|
||||||
#include "../core/logging.hpp"
|
#include "../core/logging.hpp"
|
||||||
#include "../core/paths.hpp"
|
#include "../core/paths.hpp"
|
||||||
#include "../io/ipccomm.hpp"
|
#include "../io/ipccomm.hpp"
|
||||||
#include "../ipc/ipc.hpp"
|
#include "../ipc/ipc.hpp"
|
||||||
|
#include "build.hpp"
|
||||||
#include "launch_p.hpp"
|
#include "launch_p.hpp"
|
||||||
|
|
||||||
namespace qs::launch {
|
namespace qs::launch {
|
||||||
@@ -519,10 +519,20 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state.misc.printVersion) {
|
if (state.misc.printVersion) {
|
||||||
if (state.log.verbosity == 0) {
|
qCInfo(logBare).noquote().nospace() << "quickshell " << QS_VERSION << ", revision "
|
||||||
qCInfo(logBare).noquote() << "Quickshell" << qs::debuginfo::qsVersion();
|
<< GIT_REVISION << ", distributed by: " << DISTRIBUTOR;
|
||||||
} else {
|
|
||||||
qCInfo(logBare).noquote() << qs::debuginfo::combinedInfo();
|
if (state.log.verbosity > 1) {
|
||||||
|
qCInfo(logBare).noquote() << "\nBuildtime Qt Version:" << QT_VERSION_STR;
|
||||||
|
qCInfo(logBare).noquote() << "Runtime Qt Version:" << qVersion();
|
||||||
|
qCInfo(logBare).noquote() << "Compiler:" << COMPILER;
|
||||||
|
qCInfo(logBare).noquote() << "Compile Flags:" << COMPILE_FLAGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.log.verbosity > 0) {
|
||||||
|
qCInfo(logBare).noquote() << "\nBuild Type:" << BUILD_TYPE;
|
||||||
|
qCInfo(logBare).noquote() << "Build configuration:";
|
||||||
|
qCInfo(logBare).noquote().nospace() << BUILD_CONFIGURATION;
|
||||||
}
|
}
|
||||||
} else if (*state.subcommand.log) {
|
} else if (*state.subcommand.log) {
|
||||||
return readLogFile(state);
|
return readLogFile(state);
|
||||||
|
|||||||
@@ -138,11 +138,9 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
|
|||||||
};
|
};
|
||||||
|
|
||||||
#if CRASH_HANDLER
|
#if CRASH_HANDLER
|
||||||
if (qEnvironmentVariableIsSet("QS_DISABLE_CRASH_HANDLER")) {
|
crash::CrashHandler::init();
|
||||||
qInfo() << "Crash handling disabled.";
|
|
||||||
} else {
|
|
||||||
crash::CrashHandler::init();
|
|
||||||
|
|
||||||
|
{
|
||||||
auto* log = LogManager::instance();
|
auto* log = LogManager::instance();
|
||||||
crash::CrashHandler::setRelaunchInfo({
|
crash::CrashHandler::setRelaunchInfo({
|
||||||
.instance = InstanceInfo::CURRENT,
|
.instance = InstanceInfo::CURRENT,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ qt_add_qml_module(quickshell-service-greetd
|
|||||||
install_qml_module(quickshell-service-greetd)
|
install_qml_module(quickshell-service-greetd)
|
||||||
|
|
||||||
# can't be Qt::Qml because generation.hpp pulls in gui types
|
# can't be Qt::Qml because generation.hpp pulls in gui types
|
||||||
target_link_libraries(quickshell-service-greetd PRIVATE Qt::Quick quickshell-core)
|
target_link_libraries(quickshell-service-greetd PRIVATE Qt::Quick)
|
||||||
|
|
||||||
qs_module_pch(quickshell-service-greetd)
|
qs_module_pch(quickshell-service-greetd)
|
||||||
|
|
||||||
|
|||||||
@@ -145,7 +145,6 @@ void GreetdConnection::setInactive() {
|
|||||||
QString GreetdConnection::user() const { return this->mUser; }
|
QString GreetdConnection::user() const { return this->mUser; }
|
||||||
|
|
||||||
void GreetdConnection::onSocketConnected() {
|
void GreetdConnection::onSocketConnected() {
|
||||||
this->reader.setDevice(&this->socket);
|
|
||||||
qCDebug(logGreetd) << "Connected to greetd socket.";
|
qCDebug(logGreetd) << "Connected to greetd socket.";
|
||||||
|
|
||||||
if (this->mTargetActive) {
|
if (this->mTargetActive) {
|
||||||
@@ -161,84 +160,82 @@ void GreetdConnection::onSocketError(QLocalSocket::LocalSocketError error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GreetdConnection::onSocketReady() {
|
void GreetdConnection::onSocketReady() {
|
||||||
while (true) {
|
qint32 length = 0;
|
||||||
this->reader.startTransaction();
|
|
||||||
auto length = this->reader.readI32();
|
|
||||||
auto text = this->reader.readBytes(length);
|
|
||||||
if (!this->reader.commitTransaction()) return;
|
|
||||||
|
|
||||||
auto json = QJsonDocument::fromJson(text).object();
|
this->socket.read(reinterpret_cast<char*>(&length), sizeof(qint32));
|
||||||
auto type = json.value("type").toString();
|
|
||||||
|
|
||||||
qCDebug(logGreetd).noquote() << "Received greetd response:" << text;
|
auto text = this->socket.read(length);
|
||||||
|
auto json = QJsonDocument::fromJson(text).object();
|
||||||
|
auto type = json.value("type").toString();
|
||||||
|
|
||||||
if (type == "success") {
|
qCDebug(logGreetd).noquote() << "Received greetd response:" << text;
|
||||||
switch (this->mState) {
|
|
||||||
case GreetdState::Authenticating:
|
|
||||||
qCDebug(logGreetd) << "Authentication complete.";
|
|
||||||
this->mState = GreetdState::ReadyToLaunch;
|
|
||||||
emit this->stateChanged();
|
|
||||||
emit this->readyToLaunch();
|
|
||||||
break;
|
|
||||||
case GreetdState::Launching:
|
|
||||||
qCDebug(logGreetd) << "Target session set successfully.";
|
|
||||||
this->mState = GreetdState::Launched;
|
|
||||||
emit this->stateChanged();
|
|
||||||
emit this->launched();
|
|
||||||
|
|
||||||
if (this->mExitAfterLaunch) {
|
if (type == "success") {
|
||||||
qCDebug(logGreetd) << "Quitting.";
|
switch (this->mState) {
|
||||||
EngineGeneration::currentGeneration()->quit();
|
case GreetdState::Authenticating:
|
||||||
}
|
qCDebug(logGreetd) << "Authentication complete.";
|
||||||
|
this->mState = GreetdState::ReadyToLaunch;
|
||||||
|
emit this->stateChanged();
|
||||||
|
emit this->readyToLaunch();
|
||||||
|
break;
|
||||||
|
case GreetdState::Launching:
|
||||||
|
qCDebug(logGreetd) << "Target session set successfully.";
|
||||||
|
this->mState = GreetdState::Launched;
|
||||||
|
emit this->stateChanged();
|
||||||
|
emit this->launched();
|
||||||
|
|
||||||
break;
|
if (this->mExitAfterLaunch) {
|
||||||
default: goto unexpected;
|
qCDebug(logGreetd) << "Quitting.";
|
||||||
}
|
EngineGeneration::currentGeneration()->quit();
|
||||||
} else if (type == "error") {
|
|
||||||
auto errorType = json.value("error_type").toString();
|
|
||||||
auto desc = json.value("description").toString();
|
|
||||||
|
|
||||||
// Special case this error in case a session was already running.
|
|
||||||
// This cancels and restarts the session.
|
|
||||||
if (errorType == "error" && desc == "a session is already being configured") {
|
|
||||||
qCDebug(
|
|
||||||
logGreetd
|
|
||||||
) << "A session was already in progress, cancelling it and starting a new one.";
|
|
||||||
this->setActive(false);
|
|
||||||
this->setActive(true);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorType == "auth_error") {
|
break;
|
||||||
emit this->authFailure(desc);
|
default: goto unexpected;
|
||||||
this->setActive(false);
|
}
|
||||||
} else if (errorType == "error") {
|
} else if (type == "error") {
|
||||||
qCWarning(logGreetd) << "Greetd error occurred" << desc;
|
auto errorType = json.value("error_type").toString();
|
||||||
emit this->error(desc);
|
auto desc = json.value("description").toString();
|
||||||
} else goto unexpected;
|
|
||||||
|
|
||||||
// errors terminate the session
|
// Special case this error in case a session was already running.
|
||||||
this->setInactive();
|
// This cancels and restarts the session.
|
||||||
} else if (type == "auth_message") {
|
if (errorType == "error" && desc == "a session is already being configured") {
|
||||||
auto message = json.value("auth_message").toString();
|
qCDebug(
|
||||||
auto type = json.value("auth_message_type").toString();
|
logGreetd
|
||||||
auto error = type == "error";
|
) << "A session was already in progress, cancelling it and starting a new one.";
|
||||||
auto responseRequired = type == "visible" || type == "secret";
|
this->setActive(false);
|
||||||
auto echoResponse = type != "secret";
|
this->setActive(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this->mResponseRequired = responseRequired;
|
if (errorType == "auth_error") {
|
||||||
emit this->authMessage(message, error, responseRequired, echoResponse);
|
emit this->authFailure(desc);
|
||||||
|
this->setActive(false);
|
||||||
if (!responseRequired) {
|
} else if (errorType == "error") {
|
||||||
this->sendRequest({{"type", "post_auth_message_response"}});
|
qCWarning(logGreetd) << "Greetd error occurred" << desc;
|
||||||
}
|
emit this->error(desc);
|
||||||
} else goto unexpected;
|
} else goto unexpected;
|
||||||
|
|
||||||
continue;
|
// errors terminate the session
|
||||||
unexpected:
|
this->setInactive();
|
||||||
qCCritical(logGreetd) << "Received unexpected greetd response" << text;
|
} else if (type == "auth_message") {
|
||||||
this->setActive(false);
|
auto message = json.value("auth_message").toString();
|
||||||
}
|
auto type = json.value("auth_message_type").toString();
|
||||||
|
auto error = type == "error";
|
||||||
|
auto responseRequired = type == "visible" || type == "secret";
|
||||||
|
auto echoResponse = type != "secret";
|
||||||
|
|
||||||
|
this->mResponseRequired = responseRequired;
|
||||||
|
emit this->authMessage(message, error, responseRequired, echoResponse);
|
||||||
|
|
||||||
|
if (!responseRequired) {
|
||||||
|
this->sendRequest({{"type", "post_auth_message_response"}});
|
||||||
|
}
|
||||||
|
} else goto unexpected;
|
||||||
|
|
||||||
|
return;
|
||||||
|
unexpected:
|
||||||
|
qCCritical(logGreetd) << "Received unexpected greetd response" << text;
|
||||||
|
this->setActive(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GreetdConnection::sendRequest(const QJsonObject& json) {
|
void GreetdConnection::sendRequest(const QJsonObject& json) {
|
||||||
|
|||||||
@@ -8,8 +8,6 @@
|
|||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
|
|
||||||
#include "../../core/streamreader.hpp"
|
|
||||||
|
|
||||||
///! State of the Greetd connection.
|
///! State of the Greetd connection.
|
||||||
/// See @@Greetd.state.
|
/// See @@Greetd.state.
|
||||||
class GreetdState: public QObject {
|
class GreetdState: public QObject {
|
||||||
@@ -76,5 +74,4 @@ private:
|
|||||||
bool mResponseRequired = false;
|
bool mResponseRequired = false;
|
||||||
QString mUser;
|
QString mUser;
|
||||||
QLocalSocket socket;
|
QLocalSocket socket;
|
||||||
StreamReader reader;
|
|
||||||
};
|
};
|
||||||
@@ -143,17 +143,12 @@ void PwCore::onSync(void* data, quint32 id, qint32 seq) {
|
|||||||
void PwCore::onError(void* data, quint32 id, qint32 /*seq*/, qint32 res, const char* message) {
|
void PwCore::onError(void* data, quint32 id, qint32 /*seq*/, qint32 res, const char* message) {
|
||||||
auto* self = static_cast<PwCore*>(data);
|
auto* self = static_cast<PwCore*>(data);
|
||||||
|
|
||||||
// Pipewire's documentation describes the error event as being fatal, however it isn't.
|
if (message != nullptr) {
|
||||||
// We're not sure what causes these ENOENTs on device removal, presumably something in
|
qCWarning(logLoop) << "Fatal pipewire error on object" << id << "with code" << res << message;
|
||||||
// the teardown sequence, but they're harmless. Attempting to handle them as a fatal
|
} else {
|
||||||
// error causes unnecessary triggers for shells.
|
qCWarning(logLoop) << "Fatal pipewire error on object" << id << "with code" << res;
|
||||||
if (res == -ENOENT) {
|
|
||||||
qCDebug(logLoop) << "Pipewire ENOENT on object" << id << "with code" << res << message;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qCWarning(logLoop) << "Pipewire error on object" << id << "with code" << res << message;
|
|
||||||
|
|
||||||
emit self->fatalError();
|
emit self->fatalError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -123,8 +123,6 @@ list(APPEND WAYLAND_MODULES Quickshell.Wayland._IdleNotify)
|
|||||||
add_subdirectory(shortcuts_inhibit)
|
add_subdirectory(shortcuts_inhibit)
|
||||||
list(APPEND WAYLAND_MODULES Quickshell.Wayland._ShortcutsInhibitor)
|
list(APPEND WAYLAND_MODULES Quickshell.Wayland._ShortcutsInhibitor)
|
||||||
|
|
||||||
add_subdirectory(windowmanager)
|
|
||||||
|
|
||||||
# widgets for qmenu
|
# widgets for qmenu
|
||||||
target_link_libraries(quickshell-wayland PRIVATE
|
target_link_libraries(quickshell-wayland PRIVATE
|
||||||
Qt::Quick Qt::Widgets Qt::WaylandClient Qt::WaylandClientPrivate
|
Qt::Quick Qt::Widgets Qt::WaylandClient Qt::WaylandClientPrivate
|
||||||
|
|||||||
@@ -28,7 +28,6 @@
|
|||||||
#include <qscopedpointer.h>
|
#include <qscopedpointer.h>
|
||||||
#include <qsgrendererinterface.h>
|
#include <qsgrendererinterface.h>
|
||||||
#include <qsgtexture_platform.h>
|
#include <qsgtexture_platform.h>
|
||||||
#include <qtypes.h>
|
|
||||||
#include <qvulkanfunctions.h>
|
#include <qvulkanfunctions.h>
|
||||||
#include <qvulkaninstance.h>
|
#include <qvulkaninstance.h>
|
||||||
#include <qwayland-linux-dmabuf-v1.h>
|
#include <qwayland-linux-dmabuf-v1.h>
|
||||||
@@ -36,6 +35,7 @@
|
|||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <qtypes.h>
|
||||||
#include <vulkan/vulkan_core.h>
|
#include <vulkan/vulkan_core.h>
|
||||||
#include <wayland-client-protocol.h>
|
#include <wayland-client-protocol.h>
|
||||||
#include <wayland-linux-dmabuf-v1-client-protocol.h>
|
#include <wayland-linux-dmabuf-v1-client-protocol.h>
|
||||||
@@ -80,8 +80,10 @@ bool drmFormatHasAlpha(uint32_t drmFormat) {
|
|||||||
case DRM_FORMAT_ABGR8888:
|
case DRM_FORMAT_ABGR8888:
|
||||||
case DRM_FORMAT_ARGB2101010:
|
case DRM_FORMAT_ARGB2101010:
|
||||||
case DRM_FORMAT_ABGR2101010:
|
case DRM_FORMAT_ABGR2101010:
|
||||||
case DRM_FORMAT_ABGR16161616F: return true;
|
case DRM_FORMAT_ABGR16161616F:
|
||||||
default: return false;
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -816,8 +818,7 @@ WlBufferQSGTexture* WlDmaBuffer::createQsgTextureVulkan(QQuickWindow* window) co
|
|||||||
|
|
||||||
// dup() is required because vkAllocateMemory with VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT
|
// dup() is required because vkAllocateMemory with VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT
|
||||||
// takes ownership of the fd on succcess. Without dup, WlDmaBuffer would double-close.
|
// takes ownership of the fd on succcess. Without dup, WlDmaBuffer would double-close.
|
||||||
const int dupFd =
|
const int dupFd = dup(this->planes[0].fd); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
|
||||||
dup(this->planes[0].fd); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
|
|
||||||
if (dupFd < 0) {
|
if (dupFd < 0) {
|
||||||
qCWarning(logDmabuf) << "Failed to dup() fd for DMA-BUF import";
|
qCWarning(logDmabuf) << "Failed to dup() fd for DMA-BUF import";
|
||||||
goto cleanup_fail; // NOLINT
|
goto cleanup_fail; // NOLINT
|
||||||
@@ -908,12 +909,12 @@ WlBufferQSGTexture* WlDmaBuffer::createQsgTextureVulkan(QQuickWindow* window) co
|
|||||||
// find the graphics queue family index for the ownrship transfer.
|
// find the graphics queue family index for the ownrship transfer.
|
||||||
uint32_t graphicsQueueFamily = 0;
|
uint32_t graphicsQueueFamily = 0;
|
||||||
uint32_t queueFamilyCount = 0;
|
uint32_t queueFamilyCount = 0;
|
||||||
instFuncs->vkGetPhysicalDeviceQueueFamilyProperties(physDevice, &queueFamilyCount, nullptr);
|
instFuncs->vkGetPhysicalDeviceQueueFamilyProperties(
|
||||||
|
physDevice, &queueFamilyCount, nullptr
|
||||||
|
);
|
||||||
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
|
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
|
||||||
instFuncs->vkGetPhysicalDeviceQueueFamilyProperties(
|
instFuncs->vkGetPhysicalDeviceQueueFamilyProperties(
|
||||||
physDevice,
|
physDevice, &queueFamilyCount, queueFamilies.data()
|
||||||
&queueFamilyCount,
|
|
||||||
queueFamilies.data()
|
|
||||||
);
|
);
|
||||||
for (uint32_t i = 0; i < queueFamilyCount; ++i) {
|
for (uint32_t i = 0; i < queueFamilyCount; ++i) {
|
||||||
if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
|
if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
|
||||||
@@ -988,7 +989,13 @@ WlBufferQSGTexture* WlDmaBuffer::createQsgTextureVulkan(QQuickWindow* window) co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* tex = new WlDmaBufferVulkanQSGTexture(devFuncs, device, image, memory, qsgTexture);
|
auto* tex = new WlDmaBufferVulkanQSGTexture(
|
||||||
|
devFuncs,
|
||||||
|
device,
|
||||||
|
image,
|
||||||
|
memory,
|
||||||
|
qsgTexture
|
||||||
|
);
|
||||||
qCDebug(logDmabuf) << "Created WlDmaBufferVulkanQSGTexture" << tex << "from" << this;
|
qCDebug(logDmabuf) << "Created WlDmaBufferVulkanQSGTexture" << tex << "from" << this;
|
||||||
return tex;
|
return tex;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ qs_add_module_deps_light(quickshell-hyprland-ipc Quickshell)
|
|||||||
|
|
||||||
install_qml_module(quickshell-hyprland-ipc)
|
install_qml_module(quickshell-hyprland-ipc)
|
||||||
|
|
||||||
target_link_libraries(quickshell-hyprland-ipc PRIVATE Qt::Quick quickshell-core)
|
target_link_libraries(quickshell-hyprland-ipc PRIVATE Qt::Quick)
|
||||||
|
|
||||||
if (WAYLAND_TOPLEVEL_MANAGEMENT)
|
if (WAYLAND_TOPLEVEL_MANAGEMENT)
|
||||||
target_sources(quickshell-hyprland-ipc PRIVATE
|
target_sources(quickshell-hyprland-ipc PRIVATE
|
||||||
|
|||||||
@@ -93,7 +93,6 @@ void HyprlandIpc::eventSocketError(QLocalSocket::LocalSocketError error) const {
|
|||||||
|
|
||||||
void HyprlandIpc::eventSocketStateChanged(QLocalSocket::LocalSocketState state) {
|
void HyprlandIpc::eventSocketStateChanged(QLocalSocket::LocalSocketState state) {
|
||||||
if (state == QLocalSocket::ConnectedState) {
|
if (state == QLocalSocket::ConnectedState) {
|
||||||
this->eventReader.setDevice(&this->eventSocket);
|
|
||||||
qCInfo(logHyprlandIpc) << "Hyprland event socket connected.";
|
qCInfo(logHyprlandIpc) << "Hyprland event socket connected.";
|
||||||
emit this->connected();
|
emit this->connected();
|
||||||
} else if (state == QLocalSocket::UnconnectedState && this->valid) {
|
} else if (state == QLocalSocket::UnconnectedState && this->valid) {
|
||||||
@@ -105,11 +104,11 @@ void HyprlandIpc::eventSocketStateChanged(QLocalSocket::LocalSocketState state)
|
|||||||
|
|
||||||
void HyprlandIpc::eventSocketReady() {
|
void HyprlandIpc::eventSocketReady() {
|
||||||
while (true) {
|
while (true) {
|
||||||
this->eventReader.startTransaction();
|
auto rawEvent = this->eventSocket.readLine();
|
||||||
auto rawEvent = this->eventReader.readUntil('\n');
|
if (rawEvent.isEmpty()) break;
|
||||||
if (!this->eventReader.commitTransaction()) return;
|
|
||||||
|
|
||||||
rawEvent.chop(1); // remove trailing \n
|
// remove trailing \n
|
||||||
|
rawEvent.truncate(rawEvent.length() - 1);
|
||||||
auto splitIdx = rawEvent.indexOf(">>");
|
auto splitIdx = rawEvent.indexOf(">>");
|
||||||
auto event = QByteArrayView(rawEvent.data(), splitIdx);
|
auto event = QByteArrayView(rawEvent.data(), splitIdx);
|
||||||
auto data = QByteArrayView(
|
auto data = QByteArrayView(
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
|
|
||||||
#include "../../../core/model.hpp"
|
#include "../../../core/model.hpp"
|
||||||
#include "../../../core/qmlscreen.hpp"
|
#include "../../../core/qmlscreen.hpp"
|
||||||
#include "../../../core/streamreader.hpp"
|
|
||||||
#include "../../../wayland/toplevel_management/handle.hpp"
|
#include "../../../wayland/toplevel_management/handle.hpp"
|
||||||
|
|
||||||
namespace qs::hyprland::ipc {
|
namespace qs::hyprland::ipc {
|
||||||
@@ -140,7 +139,6 @@ private:
|
|||||||
static bool compareWorkspaces(HyprlandWorkspace* a, HyprlandWorkspace* b);
|
static bool compareWorkspaces(HyprlandWorkspace* a, HyprlandWorkspace* b);
|
||||||
|
|
||||||
QLocalSocket eventSocket;
|
QLocalSocket eventSocket;
|
||||||
StreamReader eventReader;
|
|
||||||
QString mRequestSocketPath;
|
QString mRequestSocketPath;
|
||||||
QString mEventSocketPath;
|
QString mEventSocketPath;
|
||||||
bool valid = false;
|
bool valid = false;
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
#include <qqmlcomponent.h>
|
#include <qqmlcomponent.h>
|
||||||
#include <qqmlengine.h>
|
#include <qqmlengine.h>
|
||||||
#include <qqmllist.h>
|
#include <qqmllist.h>
|
||||||
#include <qquickgraphicsconfiguration.h>
|
|
||||||
#include <qquickitem.h>
|
#include <qquickitem.h>
|
||||||
#include <qquickwindow.h>
|
#include <qquickwindow.h>
|
||||||
#include <qscreen.h>
|
#include <qscreen.h>
|
||||||
@@ -217,15 +216,6 @@ void WlSessionLockSurface::onReload(QObject* oldInstance) {
|
|||||||
|
|
||||||
if (this->window == nullptr) {
|
if (this->window == nullptr) {
|
||||||
this->window = new QQuickWindow();
|
this->window = new QQuickWindow();
|
||||||
|
|
||||||
// needed for vulkan dmabuf import, qt ignores these if not applicable
|
|
||||||
auto graphicsConfig = this->window->graphicsConfiguration();
|
|
||||||
graphicsConfig.setDeviceExtensions({
|
|
||||||
"VK_KHR_external_memory_fd",
|
|
||||||
"VK_EXT_external_memory_dma_buf",
|
|
||||||
"VK_EXT_image_drm_format_modifier",
|
|
||||||
});
|
|
||||||
this->window->setGraphicsConfiguration(graphicsConfig);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this->mContentItem->setParentItem(this->window->contentItem());
|
this->mContentItem->setParentItem(this->window->contentItem());
|
||||||
|
|||||||
@@ -161,11 +161,7 @@ void ToplevelManager::onToplevelReady(impl::ToplevelHandle* handle) {
|
|||||||
|
|
||||||
void ToplevelManager::onToplevelActiveChanged() {
|
void ToplevelManager::onToplevelActiveChanged() {
|
||||||
auto* toplevel = qobject_cast<Toplevel*>(this->sender());
|
auto* toplevel = qobject_cast<Toplevel*>(this->sender());
|
||||||
if (toplevel->activated()) {
|
if (toplevel->activated()) this->setActiveToplevel(toplevel);
|
||||||
this->setActiveToplevel(toplevel);
|
|
||||||
} else if (toplevel == this->mActiveToplevel) {
|
|
||||||
this->setActiveToplevel(nullptr);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ToplevelManager::onToplevelClosed() {
|
void ToplevelManager::onToplevelClosed() {
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
qt_add_library(quickshell-wayland-windowsystem STATIC
|
|
||||||
windowmanager.cpp
|
|
||||||
windowset.cpp
|
|
||||||
ext_workspace.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
add_library(quickshell-wayland-windowsystem-init OBJECT init.cpp)
|
|
||||||
target_link_libraries(quickshell-wayland-windowsystem-init PRIVATE Qt::Quick)
|
|
||||||
|
|
||||||
wl_proto(wlp-ext-workspace ext-workspace-v1 "${WAYLAND_PROTOCOLS}/staging/ext-workspace")
|
|
||||||
|
|
||||||
target_link_libraries(quickshell-wayland-windowsystem PRIVATE
|
|
||||||
Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client
|
|
||||||
wlp-ext-workspace
|
|
||||||
)
|
|
||||||
|
|
||||||
qs_pch(quickshell-wayland-windowsystem SET large)
|
|
||||||
|
|
||||||
target_link_libraries(quickshell PRIVATE quickshell-wayland-windowsystem quickshell-wayland-windowsystem-init)
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
#include "ext_workspace.hpp"
|
|
||||||
|
|
||||||
#include <qcontainerfwd.h>
|
|
||||||
#include <qlogging.h>
|
|
||||||
#include <qloggingcategory.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
#include <qtypes.h>
|
|
||||||
#include <qwayland-ext-workspace-v1.h>
|
|
||||||
#include <qwaylandclientextension.h>
|
|
||||||
#include <wayland-ext-workspace-v1-client-protocol.h>
|
|
||||||
#include <wayland-util.h>
|
|
||||||
|
|
||||||
#include "../../core/logcat.hpp"
|
|
||||||
|
|
||||||
namespace qs::wayland::workspace {
|
|
||||||
|
|
||||||
QS_LOGGING_CATEGORY(logWorkspace, "quickshell.wm.wayland.workspace", QtWarningMsg);
|
|
||||||
|
|
||||||
WorkspaceManager::WorkspaceManager(): QWaylandClientExtensionTemplate(1) { this->initialize(); }
|
|
||||||
|
|
||||||
WorkspaceManager* WorkspaceManager::instance() {
|
|
||||||
static auto* instance = new WorkspaceManager();
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WorkspaceManager::ext_workspace_manager_v1_workspace_group(
|
|
||||||
::ext_workspace_group_handle_v1* handle
|
|
||||||
) {
|
|
||||||
auto* group = new WorkspaceGroup(handle);
|
|
||||||
qCDebug(logWorkspace) << "Created group" << group;
|
|
||||||
this->mGroups.insert(handle, group);
|
|
||||||
emit this->groupCreated(group);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WorkspaceManager::ext_workspace_manager_v1_workspace(::ext_workspace_handle_v1* handle) {
|
|
||||||
auto* workspace = new Workspace(handle);
|
|
||||||
qCDebug(logWorkspace) << "Created workspace" << workspace;
|
|
||||||
this->mWorkspaces.insert(handle, workspace);
|
|
||||||
emit this->workspaceCreated(workspace);
|
|
||||||
};
|
|
||||||
|
|
||||||
void WorkspaceManager::destroyWorkspace(Workspace* workspace) {
|
|
||||||
this->mWorkspaces.remove(workspace->object());
|
|
||||||
this->destroyedWorkspaces.append(workspace);
|
|
||||||
emit this->workspaceDestroyed(workspace);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WorkspaceManager::destroyGroup(WorkspaceGroup* group) {
|
|
||||||
this->mGroups.remove(group->object());
|
|
||||||
this->destroyedGroups.append(group);
|
|
||||||
emit this->groupDestroyed(group);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WorkspaceManager::ext_workspace_manager_v1_done() {
|
|
||||||
qCDebug(logWorkspace) << "Workspace changes done";
|
|
||||||
emit this->serverCommit();
|
|
||||||
|
|
||||||
for (auto* workspace: this->destroyedWorkspaces) delete workspace;
|
|
||||||
for (auto* group: this->destroyedGroups) delete group;
|
|
||||||
this->destroyedWorkspaces.clear();
|
|
||||||
this->destroyedGroups.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WorkspaceManager::ext_workspace_manager_v1_finished() {
|
|
||||||
qCWarning(logWorkspace) << "ext_workspace_manager_v1.finished() was received";
|
|
||||||
}
|
|
||||||
|
|
||||||
Workspace::~Workspace() {
|
|
||||||
if (this->isInitialized()) this->destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Workspace::ext_workspace_handle_v1_id(const QString& id) {
|
|
||||||
qCDebug(logWorkspace) << "Updated id for workspace" << this << "to" << id;
|
|
||||||
this->id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Workspace::ext_workspace_handle_v1_name(const QString& name) {
|
|
||||||
qCDebug(logWorkspace) << "Updated name for workspace" << this << "to" << name;
|
|
||||||
this->name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Workspace::ext_workspace_handle_v1_coordinates(wl_array* coordinates) {
|
|
||||||
this->coordinates.clear();
|
|
||||||
|
|
||||||
auto* data = static_cast<qint32*>(coordinates->data);
|
|
||||||
auto size = static_cast<qsizetype>(coordinates->size / sizeof(qint32));
|
|
||||||
|
|
||||||
for (auto i = 0; i != size; ++i) {
|
|
||||||
this->coordinates.append(data[i]); // NOLINT
|
|
||||||
}
|
|
||||||
|
|
||||||
qCDebug(logWorkspace) << "Updated coordinates for workspace" << this << "to" << this->coordinates;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Workspace::ext_workspace_handle_v1_state(quint32 state) {
|
|
||||||
this->active = state & ext_workspace_handle_v1::state_active;
|
|
||||||
this->urgent = state & ext_workspace_handle_v1::state_urgent;
|
|
||||||
this->hidden = state & ext_workspace_handle_v1::state_hidden;
|
|
||||||
|
|
||||||
qCDebug(logWorkspace).nospace() << "Updated state for workspace " << this
|
|
||||||
<< " to [active: " << this->active << ", urgent: " << this->urgent
|
|
||||||
<< ", hidden: " << this->hidden << ']';
|
|
||||||
}
|
|
||||||
|
|
||||||
void Workspace::ext_workspace_handle_v1_capabilities(quint32 capabilities) {
|
|
||||||
this->canActivate = capabilities & ext_workspace_handle_v1::workspace_capabilities_activate;
|
|
||||||
this->canDeactivate = capabilities & ext_workspace_handle_v1::workspace_capabilities_deactivate;
|
|
||||||
this->canRemove = capabilities & ext_workspace_handle_v1::workspace_capabilities_remove;
|
|
||||||
this->canAssign = capabilities & ext_workspace_handle_v1::workspace_capabilities_assign;
|
|
||||||
|
|
||||||
qCDebug(logWorkspace).nospace() << "Updated capabilities for workspace " << this
|
|
||||||
<< " to [activate: " << this->canActivate
|
|
||||||
<< ", deactivate: " << this->canDeactivate
|
|
||||||
<< ", remove: " << this->canRemove
|
|
||||||
<< ", assign: " << this->canAssign << ']';
|
|
||||||
}
|
|
||||||
|
|
||||||
void Workspace::ext_workspace_handle_v1_removed() {
|
|
||||||
qCDebug(logWorkspace) << "Destroyed workspace" << this;
|
|
||||||
WorkspaceManager::instance()->destroyWorkspace(this);
|
|
||||||
this->destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Workspace::enterGroup(WorkspaceGroup* group) { this->group = group; }
|
|
||||||
|
|
||||||
void Workspace::leaveGroup(WorkspaceGroup* group) {
|
|
||||||
if (this->group == group) this->group = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
WorkspaceGroup::~WorkspaceGroup() {
|
|
||||||
if (this->isInitialized()) this->destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WorkspaceGroup::ext_workspace_group_handle_v1_capabilities(quint32 capabilities) {
|
|
||||||
this->canCreateWorkspace =
|
|
||||||
capabilities & ext_workspace_group_handle_v1::group_capabilities_create_workspace;
|
|
||||||
|
|
||||||
qCDebug(logWorkspace).nospace() << "Updated capabilities for group " << this
|
|
||||||
<< " to [create_workspace: " << this->canCreateWorkspace << ']';
|
|
||||||
}
|
|
||||||
|
|
||||||
void WorkspaceGroup::ext_workspace_group_handle_v1_output_enter(::wl_output* output) {
|
|
||||||
qCDebug(logWorkspace) << "Output" << output << "added to group" << this;
|
|
||||||
this->screens.addOutput(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WorkspaceGroup::ext_workspace_group_handle_v1_output_leave(::wl_output* output) {
|
|
||||||
qCDebug(logWorkspace) << "Output" << output << "removed from group" << this;
|
|
||||||
this->screens.removeOutput(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WorkspaceGroup::ext_workspace_group_handle_v1_workspace_enter(
|
|
||||||
::ext_workspace_handle_v1* handle
|
|
||||||
) {
|
|
||||||
auto* workspace = WorkspaceManager::instance()->mWorkspaces.value(handle);
|
|
||||||
qCDebug(logWorkspace) << "Workspace" << workspace << "added to group" << this;
|
|
||||||
|
|
||||||
if (workspace) workspace->enterGroup(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WorkspaceGroup::ext_workspace_group_handle_v1_workspace_leave(
|
|
||||||
::ext_workspace_handle_v1* handle
|
|
||||||
) {
|
|
||||||
auto* workspace = WorkspaceManager::instance()->mWorkspaces.value(handle);
|
|
||||||
qCDebug(logWorkspace) << "Workspace" << workspace << "removed from group" << this;
|
|
||||||
|
|
||||||
if (workspace) workspace->leaveGroup(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WorkspaceGroup::ext_workspace_group_handle_v1_removed() {
|
|
||||||
qCDebug(logWorkspace) << "Destroyed group" << this;
|
|
||||||
WorkspaceManager::instance()->destroyGroup(this);
|
|
||||||
this->destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace qs::wayland::workspace
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <qcontainerfwd.h>
|
|
||||||
#include <qlist.h>
|
|
||||||
#include <qloggingcategory.h>
|
|
||||||
#include <qscreen.h>
|
|
||||||
#include <qtclasshelpermacros.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
#include <qtypes.h>
|
|
||||||
#include <qwayland-ext-workspace-v1.h>
|
|
||||||
#include <qwaylandclientextension.h>
|
|
||||||
#include <wayland-ext-workspace-v1-client-protocol.h>
|
|
||||||
|
|
||||||
#include "../../core/logcat.hpp"
|
|
||||||
#include "../output_tracking.hpp"
|
|
||||||
|
|
||||||
namespace qs::wayland::workspace {
|
|
||||||
|
|
||||||
QS_DECLARE_LOGGING_CATEGORY(logWorkspace);
|
|
||||||
|
|
||||||
class WorkspaceGroup;
|
|
||||||
class Workspace;
|
|
||||||
|
|
||||||
class WorkspaceManager
|
|
||||||
: public QWaylandClientExtensionTemplate<WorkspaceManager>
|
|
||||||
, public QtWayland::ext_workspace_manager_v1 {
|
|
||||||
Q_OBJECT;
|
|
||||||
|
|
||||||
public:
|
|
||||||
static WorkspaceManager* instance();
|
|
||||||
|
|
||||||
[[nodiscard]] QList<Workspace*> workspaces() { return this->mWorkspaces.values(); }
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void serverCommit();
|
|
||||||
void workspaceCreated(Workspace* workspace);
|
|
||||||
void workspaceDestroyed(Workspace* workspace);
|
|
||||||
void groupCreated(WorkspaceGroup* group);
|
|
||||||
void groupDestroyed(WorkspaceGroup* group);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void ext_workspace_manager_v1_workspace_group(::ext_workspace_group_handle_v1* handle) override;
|
|
||||||
void ext_workspace_manager_v1_workspace(::ext_workspace_handle_v1* handle) override;
|
|
||||||
void ext_workspace_manager_v1_done() override;
|
|
||||||
void ext_workspace_manager_v1_finished() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
WorkspaceManager();
|
|
||||||
|
|
||||||
void destroyGroup(WorkspaceGroup* group);
|
|
||||||
void destroyWorkspace(Workspace* workspace);
|
|
||||||
|
|
||||||
QHash<::ext_workspace_handle_v1*, Workspace*> mWorkspaces;
|
|
||||||
QHash<::ext_workspace_group_handle_v1*, WorkspaceGroup*> mGroups;
|
|
||||||
QList<WorkspaceGroup*> destroyedGroups;
|
|
||||||
QList<Workspace*> destroyedWorkspaces;
|
|
||||||
|
|
||||||
friend class Workspace;
|
|
||||||
friend class WorkspaceGroup;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Workspace: public QtWayland::ext_workspace_handle_v1 {
|
|
||||||
public:
|
|
||||||
Workspace(::ext_workspace_handle_v1* handle): QtWayland::ext_workspace_handle_v1(handle) {}
|
|
||||||
~Workspace() override;
|
|
||||||
Q_DISABLE_COPY_MOVE(Workspace);
|
|
||||||
|
|
||||||
QString id;
|
|
||||||
QString name;
|
|
||||||
QList<qint32> coordinates;
|
|
||||||
WorkspaceGroup* group = nullptr;
|
|
||||||
|
|
||||||
bool active : 1 = false;
|
|
||||||
bool urgent : 1 = false;
|
|
||||||
bool hidden : 1 = false;
|
|
||||||
|
|
||||||
bool canActivate : 1 = false;
|
|
||||||
bool canDeactivate : 1 = false;
|
|
||||||
bool canRemove : 1 = false;
|
|
||||||
bool canAssign : 1 = false;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void ext_workspace_handle_v1_id(const QString& id) override;
|
|
||||||
void ext_workspace_handle_v1_name(const QString& name) override;
|
|
||||||
void ext_workspace_handle_v1_coordinates(wl_array* coordinates) override;
|
|
||||||
void ext_workspace_handle_v1_state(quint32 state) override;
|
|
||||||
void ext_workspace_handle_v1_capabilities(quint32 capabilities) override;
|
|
||||||
void ext_workspace_handle_v1_removed() override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
void enterGroup(WorkspaceGroup* group);
|
|
||||||
void leaveGroup(WorkspaceGroup* group);
|
|
||||||
|
|
||||||
friend class WorkspaceGroup;
|
|
||||||
};
|
|
||||||
|
|
||||||
class WorkspaceGroup: public QtWayland::ext_workspace_group_handle_v1 {
|
|
||||||
public:
|
|
||||||
WorkspaceGroup(::ext_workspace_group_handle_v1* handle)
|
|
||||||
: QtWayland::ext_workspace_group_handle_v1(handle) {}
|
|
||||||
|
|
||||||
~WorkspaceGroup() override;
|
|
||||||
Q_DISABLE_COPY_MOVE(WorkspaceGroup);
|
|
||||||
|
|
||||||
WlOutputTracker screens;
|
|
||||||
bool canCreateWorkspace : 1 = false;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void ext_workspace_group_handle_v1_capabilities(quint32 capabilities) override;
|
|
||||||
void ext_workspace_group_handle_v1_output_enter(::wl_output* output) override;
|
|
||||||
void ext_workspace_group_handle_v1_output_leave(::wl_output* output) override;
|
|
||||||
void ext_workspace_group_handle_v1_workspace_enter(::ext_workspace_handle_v1* handle) override;
|
|
||||||
void ext_workspace_group_handle_v1_workspace_leave(::ext_workspace_handle_v1* handle) override;
|
|
||||||
void ext_workspace_group_handle_v1_removed() override;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace qs::wayland::workspace
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
#include <qcontainerfwd.h>
|
|
||||||
#include <qguiapplication.h>
|
|
||||||
#include <qlist.h>
|
|
||||||
|
|
||||||
#include "../../core/plugin.hpp"
|
|
||||||
|
|
||||||
namespace qs::wm::wayland {
|
|
||||||
void installWmProvider();
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
class WaylandWmPlugin: public QsEnginePlugin {
|
|
||||||
QList<QString> dependencies() override { return {"window"}; }
|
|
||||||
|
|
||||||
bool applies() override { return QGuiApplication::platformName() == "wayland"; }
|
|
||||||
|
|
||||||
void init() override { qs::wm::wayland::installWmProvider(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
QS_REGISTER_PLUGIN(WaylandWmPlugin);
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
#include "windowmanager.hpp"
|
|
||||||
|
|
||||||
#include "../../windowmanager/windowmanager.hpp"
|
|
||||||
#include "windowset.hpp"
|
|
||||||
|
|
||||||
namespace qs::wm::wayland {
|
|
||||||
|
|
||||||
WaylandWindowManager* WaylandWindowManager::instance() {
|
|
||||||
static auto* instance = []() {
|
|
||||||
auto* wm = new WaylandWindowManager();
|
|
||||||
WindowsetManager::instance();
|
|
||||||
return wm;
|
|
||||||
}();
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
void installWmProvider() { // NOLINT (misc-use-internal-linkage)
|
|
||||||
qs::wm::WindowManager::setProvider([]() { return WaylandWindowManager::instance(); });
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace qs::wm::wayland
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
|
|
||||||
#include "../../windowmanager/windowmanager.hpp"
|
|
||||||
#include "windowset.hpp"
|
|
||||||
|
|
||||||
namespace qs::wm::wayland {
|
|
||||||
|
|
||||||
class WaylandWindowManager: public WindowManager {
|
|
||||||
Q_OBJECT;
|
|
||||||
|
|
||||||
public:
|
|
||||||
static WaylandWindowManager* instance();
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace qs::wm::wayland
|
|
||||||
@@ -1,252 +0,0 @@
|
|||||||
#include "windowset.hpp"
|
|
||||||
|
|
||||||
#include <qlist.h>
|
|
||||||
#include <qlogging.h>
|
|
||||||
#include <qloggingcategory.h>
|
|
||||||
#include <qnamespace.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qobjectdefs.h>
|
|
||||||
#include <qproperty.h>
|
|
||||||
|
|
||||||
#include "../../windowmanager/windowmanager.hpp"
|
|
||||||
#include "../../windowmanager/windowset.hpp"
|
|
||||||
#include "../../windowmanager/screenprojection.hpp"
|
|
||||||
#include "ext_workspace.hpp"
|
|
||||||
|
|
||||||
namespace qs::wm::wayland {
|
|
||||||
|
|
||||||
WindowsetManager::WindowsetManager() {
|
|
||||||
auto* impl = impl::WorkspaceManager::instance();
|
|
||||||
|
|
||||||
QObject::connect(
|
|
||||||
impl,
|
|
||||||
&impl::WorkspaceManager::serverCommit,
|
|
||||||
this,
|
|
||||||
&WindowsetManager::onServerCommit
|
|
||||||
);
|
|
||||||
|
|
||||||
QObject::connect(
|
|
||||||
impl,
|
|
||||||
&impl::WorkspaceManager::workspaceCreated,
|
|
||||||
this,
|
|
||||||
&WindowsetManager::onWindowsetCreated
|
|
||||||
);
|
|
||||||
|
|
||||||
QObject::connect(
|
|
||||||
impl,
|
|
||||||
&impl::WorkspaceManager::workspaceDestroyed,
|
|
||||||
this,
|
|
||||||
&WindowsetManager::onWindowsetDestroyed
|
|
||||||
);
|
|
||||||
|
|
||||||
QObject::connect(
|
|
||||||
impl,
|
|
||||||
&impl::WorkspaceManager::groupCreated,
|
|
||||||
this,
|
|
||||||
&WindowsetManager::onProjectionCreated
|
|
||||||
);
|
|
||||||
|
|
||||||
QObject::connect(
|
|
||||||
impl,
|
|
||||||
&impl::WorkspaceManager::groupDestroyed,
|
|
||||||
this,
|
|
||||||
&WindowsetManager::onProjectionDestroyed
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WindowsetManager::scheduleCommit() {
|
|
||||||
if (this->commitScheduled) {
|
|
||||||
qCDebug(impl::logWorkspace) << "Workspace commit already scheduled.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
qCDebug(impl::logWorkspace) << "Scheduling workspace commit...";
|
|
||||||
this->commitScheduled = true;
|
|
||||||
QMetaObject::invokeMethod(this, &WindowsetManager::doCommit, Qt::QueuedConnection);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WindowsetManager::doCommit() { // NOLINT
|
|
||||||
qCDebug(impl::logWorkspace) << "Committing workspaces...";
|
|
||||||
impl::WorkspaceManager::instance()->commit();
|
|
||||||
this->commitScheduled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WindowsetManager::onServerCommit() {
|
|
||||||
// Projections are created/destroyed around windowsets to avoid any nulls making it
|
|
||||||
// to the qml engine.
|
|
||||||
|
|
||||||
Qt::beginPropertyUpdateGroup();
|
|
||||||
|
|
||||||
auto* wm = WindowManager::instance();
|
|
||||||
auto windowsets = wm->bWindowsets.value();
|
|
||||||
auto projections = wm->bWindowsetProjections.value();
|
|
||||||
|
|
||||||
for (auto* projImpl: this->pendingProjectionCreations) {
|
|
||||||
auto* projection = new WlWindowsetProjection(this, projImpl);
|
|
||||||
this->projectionsByImpl.insert(projImpl, projection);
|
|
||||||
projections.append(projection);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto* wsImpl: this->pendingWindowsetCreations) {
|
|
||||||
auto* ws = new WlWindowset(this, wsImpl);
|
|
||||||
this->windowsetByImpl.insert(wsImpl, ws);
|
|
||||||
windowsets.append(ws);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto* wsImpl: this->pendingWindowsetDestructions) {
|
|
||||||
windowsets.removeOne(this->windowsetByImpl.value(wsImpl));
|
|
||||||
this->windowsetByImpl.remove(wsImpl);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto* projImpl: this->pendingProjectionDestructions) {
|
|
||||||
projections.removeOne(this->projectionsByImpl.value(projImpl));
|
|
||||||
this->projectionsByImpl.remove(projImpl);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto* ws: windowsets) {
|
|
||||||
static_cast<WlWindowset*>(ws)->commitImpl(); // NOLINT
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto* projection: projections) {
|
|
||||||
static_cast<WlWindowsetProjection*>(projection)->commitImpl(); // NOLINT
|
|
||||||
}
|
|
||||||
|
|
||||||
this->pendingWindowsetCreations.clear();
|
|
||||||
this->pendingWindowsetDestructions.clear();
|
|
||||||
this->pendingProjectionCreations.clear();
|
|
||||||
this->pendingProjectionDestructions.clear();
|
|
||||||
|
|
||||||
wm->bWindowsets = windowsets;
|
|
||||||
wm->bWindowsetProjections = projections;
|
|
||||||
|
|
||||||
Qt::endPropertyUpdateGroup();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WindowsetManager::onWindowsetCreated(impl::Workspace* workspace) {
|
|
||||||
this->pendingWindowsetCreations.append(workspace);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WindowsetManager::onWindowsetDestroyed(impl::Workspace* workspace) {
|
|
||||||
if (!this->pendingWindowsetCreations.removeOne(workspace)) {
|
|
||||||
this->pendingWindowsetDestructions.append(workspace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WindowsetManager::onProjectionCreated(impl::WorkspaceGroup* group) {
|
|
||||||
this->pendingProjectionCreations.append(group);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WindowsetManager::onProjectionDestroyed(impl::WorkspaceGroup* group) {
|
|
||||||
if (!this->pendingProjectionCreations.removeOne(group)) {
|
|
||||||
this->pendingProjectionDestructions.append(group);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WindowsetManager* WindowsetManager::instance() {
|
|
||||||
static auto* instance = new WindowsetManager();
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
WlWindowset::WlWindowset(WindowsetManager* manager, impl::Workspace* impl)
|
|
||||||
: Windowset(manager)
|
|
||||||
, impl(impl) {
|
|
||||||
this->commitImpl();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlWindowset::commitImpl() {
|
|
||||||
Qt::beginPropertyUpdateGroup();
|
|
||||||
this->bId = this->impl->id;
|
|
||||||
this->bName = this->impl->name;
|
|
||||||
this->bCoordinates = this->impl->coordinates;
|
|
||||||
this->bActive = this->impl->active;
|
|
||||||
this->bShouldDisplay = !this->impl->hidden;
|
|
||||||
this->bUrgent = this->impl->urgent;
|
|
||||||
this->bCanActivate = this->impl->canActivate;
|
|
||||||
this->bCanDeactivate = this->impl->canDeactivate;
|
|
||||||
this->bCanSetProjection = this->impl->canAssign;
|
|
||||||
this->bProjection = this->manager()->projectionsByImpl.value(this->impl->group);
|
|
||||||
Qt::endPropertyUpdateGroup();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlWindowset::activate() {
|
|
||||||
if (!this->bCanActivate) {
|
|
||||||
qCritical(logWorkspace) << this << "cannot be activated";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
qCDebug(impl::logWorkspace) << "Calling activate() for" << this;
|
|
||||||
this->impl->activate();
|
|
||||||
WindowsetManager::instance()->scheduleCommit();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlWindowset::deactivate() {
|
|
||||||
if (!this->bCanDeactivate) {
|
|
||||||
qCritical(logWorkspace) << this << "cannot be deactivated";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
qCDebug(impl::logWorkspace) << "Calling deactivate() for" << this;
|
|
||||||
this->impl->deactivate();
|
|
||||||
WindowsetManager::instance()->scheduleCommit();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlWindowset::remove() {
|
|
||||||
if (!this->bCanRemove) {
|
|
||||||
qCritical(logWorkspace) << this << "cannot be removed";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
qCDebug(impl::logWorkspace) << "Calling remove() for" << this;
|
|
||||||
this->impl->remove();
|
|
||||||
WindowsetManager::instance()->scheduleCommit();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlWindowset::setProjection(WindowsetProjection* projection) {
|
|
||||||
if (!this->bCanSetProjection) {
|
|
||||||
qCritical(logWorkspace) << this << "cannot be assigned to a projection";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!projection) {
|
|
||||||
qCritical(logWorkspace) << "Cannot set a windowset's projection to null";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
WlWindowsetProjection* wlProjection = nullptr;
|
|
||||||
if (auto* p = dynamic_cast<WlWindowsetProjection*>(projection)) {
|
|
||||||
wlProjection = p;
|
|
||||||
} else if (auto* p = dynamic_cast<ScreenProjection*>(projection)) {
|
|
||||||
// In the 99% case, there will only be a single windowset on a screen.
|
|
||||||
// In the 1% case, the oldest projection (first in list) is most likely the desired one.
|
|
||||||
auto* screen = p->screen();
|
|
||||||
for (const auto& proj: WindowsetManager::instance()->projectionsByImpl.values()) {
|
|
||||||
if (proj->bQScreens.value().contains(screen)) {
|
|
||||||
wlProjection = proj;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!wlProjection) {
|
|
||||||
qCritical(logWorkspace) << "Cannot set a windowset's projection to" << projection
|
|
||||||
<< "as no wayland projection could be derived.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
qCDebug(impl::logWorkspace) << "Assigning" << this << "to" << projection;
|
|
||||||
this->impl->assign(wlProjection->impl->object());
|
|
||||||
WindowsetManager::instance()->scheduleCommit();
|
|
||||||
}
|
|
||||||
|
|
||||||
WlWindowsetProjection::WlWindowsetProjection(WindowsetManager* manager, impl::WorkspaceGroup* impl)
|
|
||||||
: WindowsetProjection(manager)
|
|
||||||
, impl(impl) {
|
|
||||||
this->commitImpl();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WlWindowsetProjection::commitImpl() {
|
|
||||||
// TODO: will not commit the correct screens if missing qt repr at commit time
|
|
||||||
this->bQScreens = this->impl->screens.screens();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace qs::wm::wayland
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <qhash.h>
|
|
||||||
#include <qlist.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qproperty.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
|
|
||||||
#include "../../windowmanager/windowset.hpp"
|
|
||||||
#include "ext_workspace.hpp"
|
|
||||||
|
|
||||||
namespace qs::wm::wayland {
|
|
||||||
namespace impl = qs::wayland::workspace;
|
|
||||||
|
|
||||||
class WlWindowset;
|
|
||||||
class WlWindowsetProjection;
|
|
||||||
|
|
||||||
class WindowsetManager: public QObject {
|
|
||||||
Q_OBJECT;
|
|
||||||
|
|
||||||
public:
|
|
||||||
static WindowsetManager* instance();
|
|
||||||
|
|
||||||
void scheduleCommit();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void doCommit();
|
|
||||||
void onServerCommit();
|
|
||||||
void onWindowsetCreated(impl::Workspace* workspace);
|
|
||||||
void onWindowsetDestroyed(impl::Workspace* workspace);
|
|
||||||
void onProjectionCreated(impl::WorkspaceGroup* group);
|
|
||||||
void onProjectionDestroyed(impl::WorkspaceGroup* group);
|
|
||||||
|
|
||||||
private:
|
|
||||||
WindowsetManager();
|
|
||||||
|
|
||||||
bool commitScheduled = false;
|
|
||||||
|
|
||||||
QList<impl::Workspace*> pendingWindowsetCreations;
|
|
||||||
QList<impl::Workspace*> pendingWindowsetDestructions;
|
|
||||||
QHash<impl::Workspace*, WlWindowset*> windowsetByImpl;
|
|
||||||
|
|
||||||
QList<impl::WorkspaceGroup*> pendingProjectionCreations;
|
|
||||||
QList<impl::WorkspaceGroup*> pendingProjectionDestructions;
|
|
||||||
QHash<impl::WorkspaceGroup*, WlWindowsetProjection*> projectionsByImpl;
|
|
||||||
|
|
||||||
friend class WlWindowset;
|
|
||||||
};
|
|
||||||
|
|
||||||
class WlWindowset: public Windowset {
|
|
||||||
public:
|
|
||||||
WlWindowset(WindowsetManager* manager, impl::Workspace* impl);
|
|
||||||
|
|
||||||
void commitImpl();
|
|
||||||
|
|
||||||
void activate() override;
|
|
||||||
void deactivate() override;
|
|
||||||
void remove() override;
|
|
||||||
void setProjection(WindowsetProjection* projection) override;
|
|
||||||
|
|
||||||
[[nodiscard]] WindowsetManager* manager() {
|
|
||||||
return static_cast<WindowsetManager*>(this->parent()); // NOLINT
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
impl::Workspace* impl = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
class WlWindowsetProjection: public WindowsetProjection {
|
|
||||||
public:
|
|
||||||
WlWindowsetProjection(WindowsetManager* manager, impl::WorkspaceGroup* impl);
|
|
||||||
|
|
||||||
void commitImpl();
|
|
||||||
|
|
||||||
[[nodiscard]] WindowsetManager* manager() {
|
|
||||||
return static_cast<WindowsetManager*>(this->parent()); // NOLINT
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
impl::WorkspaceGroup* impl = nullptr;
|
|
||||||
|
|
||||||
friend class WlWindowset;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace qs::wm::wayland
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
qt_add_library(quickshell-windowmanager STATIC
|
|
||||||
screenprojection.cpp
|
|
||||||
windowmanager.cpp
|
|
||||||
windowset.cpp
|
|
||||||
)
|
|
||||||
|
|
||||||
qt_add_qml_module(quickshell-windowmanager
|
|
||||||
URI Quickshell.WindowManager
|
|
||||||
VERSION 0.1
|
|
||||||
DEPENDENCIES QtQuick
|
|
||||||
)
|
|
||||||
|
|
||||||
qs_add_module_deps_light(quickshell-windowmanager Quickshell)
|
|
||||||
|
|
||||||
install_qml_module(quickshell-windowmanager)
|
|
||||||
|
|
||||||
qs_module_pch(quickshell-windowmanager SET large)
|
|
||||||
|
|
||||||
target_link_libraries(quickshell-windowmanager PRIVATE Qt::Quick)
|
|
||||||
target_link_libraries(quickshell PRIVATE quickshell-windowmanagerplugin)
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
name = "Quickshell.WindowManager"
|
|
||||||
description = "Window manager interface"
|
|
||||||
headers = [
|
|
||||||
"windowmanager.hpp",
|
|
||||||
"windowset.hpp",
|
|
||||||
"screenprojection.hpp",
|
|
||||||
]
|
|
||||||
-----
|
|
||||||
Currently only supports the [ext-workspace-v1](https://wayland.app/protocols/ext-workspace-v1) wayland protocol.
|
|
||||||
Support will be expanded in future releases.
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
#include "screenprojection.hpp"
|
|
||||||
|
|
||||||
#include <qlist.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qscreen.h>
|
|
||||||
|
|
||||||
#include "windowmanager.hpp"
|
|
||||||
#include "windowset.hpp"
|
|
||||||
|
|
||||||
namespace qs::wm {
|
|
||||||
|
|
||||||
ScreenProjection::ScreenProjection(QScreen* screen, QObject* parent)
|
|
||||||
: WindowsetProjection(parent)
|
|
||||||
, mScreen(screen) {
|
|
||||||
this->bQScreens = {screen};
|
|
||||||
this->bWindowsets.setBinding([this]() {
|
|
||||||
QList<Windowset*> result;
|
|
||||||
for (auto* ws: WindowManager::instance()->bindableWindowsets().value()) {
|
|
||||||
auto* proj = ws->bindableProjection().value();
|
|
||||||
if (proj && proj->bindableQScreens().value().contains(this->mScreen)) {
|
|
||||||
result.append(ws);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QScreen* ScreenProjection::screen() const { return this->mScreen; }
|
|
||||||
|
|
||||||
} // namespace qs::wm
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qqmlintegration.h>
|
|
||||||
#include <qscreen.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
|
|
||||||
#include "windowset.hpp"
|
|
||||||
|
|
||||||
namespace qs::wm {
|
|
||||||
|
|
||||||
///! WindowsetProjection covering one specific screen.
|
|
||||||
/// A ScreenProjection is a special type of @@WindowsetProjection which aggregates
|
|
||||||
/// all windowsets across all projections covering a specific screen.
|
|
||||||
///
|
|
||||||
/// When used with @@Windowset.setProjection(), an arbitrary projection on the screen
|
|
||||||
/// will be picked. Usually there is only one.
|
|
||||||
///
|
|
||||||
/// Use @@WindowManager.screenProjection() to get a ScreenProjection for a given screen.
|
|
||||||
class ScreenProjection: public WindowsetProjection {
|
|
||||||
Q_OBJECT;
|
|
||||||
QML_ELEMENT;
|
|
||||||
QML_UNCREATABLE("");
|
|
||||||
|
|
||||||
public:
|
|
||||||
ScreenProjection(QScreen* screen, QObject* parent);
|
|
||||||
|
|
||||||
[[nodiscard]] QScreen* screen() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
QScreen* mScreen;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace qs::wm
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls.Fusion
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Widgets
|
|
||||||
import Quickshell.WindowManager
|
|
||||||
|
|
||||||
WrapperRectangle {
|
|
||||||
id: delegate
|
|
||||||
required property Windowset modelData;
|
|
||||||
color: modelData.active ? "green" : "gray"
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Label { text: delegate.modelData.toString() }
|
|
||||||
Label { text: `Id: ${delegate.modelData.id} Name: ${delegate.modelData.name}` }
|
|
||||||
Label { text: `Coordinates: ${delegate.modelData.coordinates.toString()}`}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Label { text: "Group:" }
|
|
||||||
ComboBox {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
implicitContentWidthPolicy: ComboBox.WidestText
|
|
||||||
enabled: delegate.modelData.canSetProjection
|
|
||||||
model: [...WindowManager.windowsetProjections].map(w => w.toString())
|
|
||||||
currentIndex: WindowManager.windowsetProjections.indexOf(delegate.modelData.projection)
|
|
||||||
onActivated: i => delegate.modelData.setProjection(WindowManager.windowsetProjections[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Label { text: "Screen:" }
|
|
||||||
ComboBox {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
implicitContentWidthPolicy: ComboBox.WidestText
|
|
||||||
enabled: delegate.modelData.canSetProjection
|
|
||||||
model: [...Quickshell.screens].map(w => w.name)
|
|
||||||
currentIndex: Quickshell.screens.indexOf(delegate.modelData.projection.screens[0])
|
|
||||||
onActivated: i => delegate.modelData.setProjection(WindowManager.screenProjection(Quickshell.screens[i]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
DisplayCheckBox {
|
|
||||||
text: "Active"
|
|
||||||
checked: delegate.modelData.active
|
|
||||||
}
|
|
||||||
|
|
||||||
DisplayCheckBox {
|
|
||||||
text: "Urgent"
|
|
||||||
checked: delegate.modelData.urgent
|
|
||||||
}
|
|
||||||
|
|
||||||
DisplayCheckBox {
|
|
||||||
text: "Should Display"
|
|
||||||
checked: delegate.modelData.shouldDisplay
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Button {
|
|
||||||
text: "Activate"
|
|
||||||
enabled: delegate.modelData.canActivate
|
|
||||||
onClicked: delegate.modelData.activate()
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: "Deactivate"
|
|
||||||
enabled: delegate.modelData.canDeactivate
|
|
||||||
onClicked: delegate.modelData.deactivate()
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
text: "Remove"
|
|
||||||
enabled: delegate.modelData.canRemove
|
|
||||||
onClicked: delegate.modelData.remove()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
component DisplayCheckBox: CheckBox {
|
|
||||||
enabled: false
|
|
||||||
palette.disabled.text: parent.palette.active.text
|
|
||||||
palette.disabled.windowText: parent.palette.active.windowText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls.Fusion
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Widgets
|
|
||||||
import Quickshell.WindowManager
|
|
||||||
|
|
||||||
FloatingWindow {
|
|
||||||
ScrollView {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Repeater {
|
|
||||||
model: Quickshell.screens
|
|
||||||
|
|
||||||
WrapperRectangle {
|
|
||||||
id: delegate
|
|
||||||
required property ShellScreen modelData
|
|
||||||
color: "slategray"
|
|
||||||
margin: 5
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Label { text: `Screen: ${delegate.modelData.name}` }
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: ScriptModel {
|
|
||||||
values: WindowManager.screenProjection(delegate.modelData).windowsets
|
|
||||||
}
|
|
||||||
|
|
||||||
WorkspaceDelegate {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: ScriptModel {
|
|
||||||
values: WindowManager.windowsets.filter(w => w.projection == null)
|
|
||||||
}
|
|
||||||
|
|
||||||
WorkspaceDelegate {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import QtQuick
|
|
||||||
import QtQuick.Controls.Fusion
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Widgets
|
|
||||||
import Quickshell.WindowManager
|
|
||||||
|
|
||||||
FloatingWindow {
|
|
||||||
ScrollView {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Repeater {
|
|
||||||
model: WindowManager.windowsetProjections
|
|
||||||
|
|
||||||
WrapperRectangle {
|
|
||||||
id: delegate
|
|
||||||
required property WindowsetProjection modelData
|
|
||||||
color: "slategray"
|
|
||||||
margin: 5
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Label { text: delegate.modelData.toString() }
|
|
||||||
Label { text: `Screens: ${delegate.modelData.screens.map(s => s.name)}` }
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: ScriptModel {
|
|
||||||
values: delegate.modelData.windowsets
|
|
||||||
}
|
|
||||||
|
|
||||||
WorkspaceDelegate {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: ScriptModel {
|
|
||||||
values: WindowManager.windowsets.filter(w => w.projection == null)
|
|
||||||
}
|
|
||||||
|
|
||||||
WorkspaceDelegate {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
#include "windowmanager.hpp"
|
|
||||||
#include <functional>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include <qobject.h>
|
|
||||||
|
|
||||||
#include "../core/qmlscreen.hpp"
|
|
||||||
#include "screenprojection.hpp"
|
|
||||||
|
|
||||||
namespace qs::wm {
|
|
||||||
|
|
||||||
std::function<WindowManager*()> WindowManager::provider;
|
|
||||||
|
|
||||||
void WindowManager::setProvider(std::function<WindowManager*()> provider) {
|
|
||||||
WindowManager::provider = std::move(provider);
|
|
||||||
}
|
|
||||||
|
|
||||||
WindowManager* WindowManager::instance() {
|
|
||||||
static auto* instance = WindowManager::provider();
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
ScreenProjection* WindowManager::screenProjection(QuickshellScreenInfo* screen) {
|
|
||||||
auto* qscreen = screen->screen;
|
|
||||||
auto it = this->mScreenProjections.find(qscreen);
|
|
||||||
if (it != this->mScreenProjections.end()) {
|
|
||||||
return *it;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto* projection = new ScreenProjection(qscreen, this);
|
|
||||||
this->mScreenProjections.insert(qscreen, projection);
|
|
||||||
|
|
||||||
QObject::connect(qscreen, &QObject::destroyed, this, [this, projection, qscreen]() {
|
|
||||||
this->mScreenProjections.remove(qscreen);
|
|
||||||
delete projection;
|
|
||||||
});
|
|
||||||
|
|
||||||
return projection;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace qs::wm
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
#include <qhash.h>
|
|
||||||
#include <qlist.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qproperty.h>
|
|
||||||
#include <qqmlintegration.h>
|
|
||||||
#include <qscreen.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
|
|
||||||
#include "../core/qmlscreen.hpp"
|
|
||||||
#include "screenprojection.hpp"
|
|
||||||
#include "windowset.hpp"
|
|
||||||
|
|
||||||
namespace qs::wm {
|
|
||||||
|
|
||||||
class WindowManager: public QObject {
|
|
||||||
Q_OBJECT;
|
|
||||||
|
|
||||||
public:
|
|
||||||
static void setProvider(std::function<WindowManager*()> provider);
|
|
||||||
static WindowManager* instance();
|
|
||||||
|
|
||||||
Q_INVOKABLE ScreenProjection* screenProjection(QuickshellScreenInfo* screen);
|
|
||||||
|
|
||||||
[[nodiscard]] QBindable<QList<Windowset*>> bindableWindowsets() const {
|
|
||||||
return &this->bWindowsets;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] QBindable<QList<WindowsetProjection*>> bindableWindowsetProjections() const {
|
|
||||||
return &this->bWindowsetProjections;
|
|
||||||
}
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void windowsetsChanged();
|
|
||||||
void windowsetProjectionsChanged();
|
|
||||||
|
|
||||||
public:
|
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(
|
|
||||||
WindowManager,
|
|
||||||
QList<Windowset*>,
|
|
||||||
bWindowsets,
|
|
||||||
&WindowManager::windowsetsChanged
|
|
||||||
);
|
|
||||||
|
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(
|
|
||||||
WindowManager,
|
|
||||||
QList<WindowsetProjection*>,
|
|
||||||
bWindowsetProjections,
|
|
||||||
&WindowManager::windowsetProjectionsChanged
|
|
||||||
);
|
|
||||||
|
|
||||||
private:
|
|
||||||
static std::function<WindowManager*()> provider;
|
|
||||||
QHash<QScreen*, ScreenProjection*> mScreenProjections;
|
|
||||||
};
|
|
||||||
|
|
||||||
///! Window management interfaces exposed by the window manager.
|
|
||||||
class WindowManagerQml: public QObject {
|
|
||||||
Q_OBJECT;
|
|
||||||
QML_NAMED_ELEMENT(WindowManager);
|
|
||||||
QML_SINGLETON;
|
|
||||||
// clang-format off
|
|
||||||
/// All windowsets tracked by the WM across all projections.
|
|
||||||
Q_PROPERTY(QList<Windowset*> windowsets READ default BINDABLE bindableWindowsets);
|
|
||||||
/// All windowset projections tracked by the WM. Does not include
|
|
||||||
/// internal projections from @@screenProjection().
|
|
||||||
Q_PROPERTY(QList<WindowsetProjection*> windowsetProjections READ default BINDABLE bindableWindowsetProjections);
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
public:
|
|
||||||
/// Returns an internal WindowsetProjection that covers a single screen and contains all
|
|
||||||
/// windowsets on that screen, regardless of the WM-specified projection. Depending on
|
|
||||||
/// how the WM lays out its actual projections, multiple ScreenProjections may contain
|
|
||||||
/// the same Windowsets.
|
|
||||||
Q_INVOKABLE static ScreenProjection* screenProjection(QuickshellScreenInfo* screen) {
|
|
||||||
return WindowManager::instance()->screenProjection(screen);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] static QBindable<QList<Windowset*>> bindableWindowsets() {
|
|
||||||
return WindowManager::instance()->bindableWindowsets();
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] static QBindable<QList<WindowsetProjection*>> bindableWindowsetProjections() {
|
|
||||||
return WindowManager::instance()->bindableWindowsetProjections();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace qs::wm
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
#include "windowset.hpp"
|
|
||||||
|
|
||||||
#include <qlist.h>
|
|
||||||
#include <qlogging.h>
|
|
||||||
#include <qloggingcategory.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
|
|
||||||
#include "../core/qmlglobal.hpp"
|
|
||||||
#include "windowmanager.hpp"
|
|
||||||
|
|
||||||
namespace qs::wm {
|
|
||||||
|
|
||||||
Q_LOGGING_CATEGORY(logWorkspace, "quickshell.wm.workspace", QtWarningMsg);
|
|
||||||
|
|
||||||
void Windowset::activate() { qCCritical(logWorkspace) << this << "cannot be activated"; }
|
|
||||||
void Windowset::deactivate() { qCCritical(logWorkspace) << this << "cannot be deactivated"; }
|
|
||||||
void Windowset::remove() { qCCritical(logWorkspace) << this << "cannot be removed"; }
|
|
||||||
|
|
||||||
void Windowset::setProjection(WindowsetProjection* /*projection*/) {
|
|
||||||
qCCritical(logWorkspace) << this << "cannot be assigned to a projection";
|
|
||||||
}
|
|
||||||
|
|
||||||
WindowsetProjection::WindowsetProjection(QObject* parent): QObject(parent) {
|
|
||||||
this->bWindowsets.setBinding([this] {
|
|
||||||
QList<Windowset*> result;
|
|
||||||
for (auto* ws: WindowManager::instance()->bindableWindowsets().value()) {
|
|
||||||
if (ws->bindableProjection().value() == this) {
|
|
||||||
result.append(ws);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
|
|
||||||
this->bScreens.setBinding([this] {
|
|
||||||
QList<QuickshellScreenInfo*> screens;
|
|
||||||
|
|
||||||
for (auto* screen: this->bQScreens.value()) {
|
|
||||||
screens.append(QuickshellTracked::instance()->screenInfo(screen));
|
|
||||||
}
|
|
||||||
|
|
||||||
return screens;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace qs::wm
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <qcontainerfwd.h>
|
|
||||||
#include <qlist.h>
|
|
||||||
#include <qloggingcategory.h>
|
|
||||||
#include <qobject.h>
|
|
||||||
#include <qproperty.h>
|
|
||||||
#include <qqmlintegration.h>
|
|
||||||
#include <qscreen.h>
|
|
||||||
#include <qtmetamacros.h>
|
|
||||||
#include <qtypes.h>
|
|
||||||
|
|
||||||
class QuickshellScreenInfo;
|
|
||||||
|
|
||||||
namespace qs::wm {
|
|
||||||
|
|
||||||
Q_DECLARE_LOGGING_CATEGORY(logWorkspace);
|
|
||||||
|
|
||||||
class WindowsetProjection;
|
|
||||||
|
|
||||||
///! A group of windows worked with by a user, usually known as a Workspace or Tag.
|
|
||||||
/// A Windowset is a generic type that encompasses both "Workspaces" and "Tags" in window managers.
|
|
||||||
/// Because the definition encompasses both you may not necessarily need all features.
|
|
||||||
class Windowset: public QObject {
|
|
||||||
Q_OBJECT;
|
|
||||||
QML_ELEMENT;
|
|
||||||
QML_UNCREATABLE("");
|
|
||||||
// clang-format off
|
|
||||||
/// A persistent internal identifier for the windowset. This property should be identical
|
|
||||||
/// across restarts and destruction/recreation of a windowset.
|
|
||||||
Q_PROPERTY(QString id READ default NOTIFY idChanged BINDABLE bindableId);
|
|
||||||
/// Human readable name of the windowset.
|
|
||||||
Q_PROPERTY(QString name READ default NOTIFY nameChanged BINDABLE bindableName);
|
|
||||||
/// Coordinates of the workspace, represented as an N-dimensional array. Most WMs
|
|
||||||
/// will only expose one coordinate. If more than one is exposed, the first is
|
|
||||||
/// conventionally X, the second Y, and the third Z.
|
|
||||||
Q_PROPERTY(QList<qint32> coordinates READ default NOTIFY coordinatesChanged BINDABLE bindableCoordinates);
|
|
||||||
/// True if the windowset is currently active. In a workspace based WM, this means the
|
|
||||||
/// represented workspace is current. In a tag based WM, this means the represented tag
|
|
||||||
/// is active.
|
|
||||||
Q_PROPERTY(bool active READ default NOTIFY activeChanged BINDABLE bindableActive);
|
|
||||||
/// The projection this windowset is a member of. A projection is the set of screens covered by
|
|
||||||
/// a windowset.
|
|
||||||
Q_PROPERTY(WindowsetProjection* projection READ default NOTIFY projectionChanged BINDABLE bindableProjection);
|
|
||||||
/// If false, this windowset should generally be hidden from workspace pickers.
|
|
||||||
Q_PROPERTY(bool shouldDisplay READ default NOTIFY shouldDisplayChanged BINDABLE bindableShouldDisplay);
|
|
||||||
/// If true, a window in this windowset has been marked as urgent.
|
|
||||||
Q_PROPERTY(bool urgent READ default NOTIFY urgentChanged BINDABLE bindableUrgent);
|
|
||||||
/// If true, the windowset can be activated. In a workspace based WM, this will make the workspace
|
|
||||||
/// current, in a tag based wm, the tag will be activated.
|
|
||||||
Q_PROPERTY(bool canActivate READ default NOTIFY canActivateChanged BINDABLE bindableCanActivate);
|
|
||||||
/// If true, the windowset can be deactivated. In a workspace based WM, deactivation is usually implicit
|
|
||||||
/// and based on activation of another workspace.
|
|
||||||
Q_PROPERTY(bool canDeactivate READ default NOTIFY canDeactivateChanged BINDABLE bindableCanDeactivate);
|
|
||||||
/// If true, the windowset can be removed. This may be done implicitly by the WM as well.
|
|
||||||
Q_PROPERTY(bool canRemove READ default NOTIFY canRemoveChanged BINDABLE bindableCanRemove);
|
|
||||||
/// If true, the windowset can be moved to a different projection.
|
|
||||||
Q_PROPERTY(bool canSetProjection READ default NOTIFY canSetProjectionChanged BINDABLE bindableCanSetProjection);
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit Windowset(QObject* parent): QObject(parent) {}
|
|
||||||
|
|
||||||
/// Activate the windowset, making it the current workspace on a workspace based WM, or activating
|
|
||||||
/// the tag on a tag based WM. Requires @@canActivate.
|
|
||||||
Q_INVOKABLE virtual void activate();
|
|
||||||
/// Deactivate the windowset, hiding it. Requires @@canDeactivate.
|
|
||||||
Q_INVOKABLE virtual void deactivate();
|
|
||||||
/// Remove or destroy the windowset. Requires @@canRemove.
|
|
||||||
Q_INVOKABLE virtual void remove();
|
|
||||||
/// Move the windowset to a different projection. A projection represents the set of screens
|
|
||||||
/// a workspace spans. Requires @@canSetProjection.
|
|
||||||
Q_INVOKABLE virtual void setProjection(WindowsetProjection* projection);
|
|
||||||
|
|
||||||
[[nodiscard]] QBindable<QString> bindableId() const { return &this->bId; }
|
|
||||||
[[nodiscard]] QBindable<QString> bindableName() const { return &this->bName; }
|
|
||||||
[[nodiscard]] QBindable<QList<qint32>> bindableCoordinates() const { return &this->bCoordinates; }
|
|
||||||
[[nodiscard]] QBindable<bool> bindableActive() const { return &this->bActive; }
|
|
||||||
|
|
||||||
[[nodiscard]] QBindable<WindowsetProjection*> bindableProjection() const {
|
|
||||||
return &this->bProjection;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] QBindable<bool> bindableShouldDisplay() const { return &this->bShouldDisplay; }
|
|
||||||
[[nodiscard]] QBindable<bool> bindableUrgent() const { return &this->bUrgent; }
|
|
||||||
[[nodiscard]] QBindable<bool> bindableCanActivate() const { return &this->bCanActivate; }
|
|
||||||
[[nodiscard]] QBindable<bool> bindableCanDeactivate() const { return &this->bCanDeactivate; }
|
|
||||||
[[nodiscard]] QBindable<bool> bindableCanRemove() const { return &this->bCanRemove; }
|
|
||||||
|
|
||||||
[[nodiscard]] QBindable<bool> bindableCanSetProjection() const {
|
|
||||||
return &this->bCanSetProjection;
|
|
||||||
}
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void idChanged();
|
|
||||||
void nameChanged();
|
|
||||||
void coordinatesChanged();
|
|
||||||
void activeChanged();
|
|
||||||
void projectionChanged();
|
|
||||||
void shouldDisplayChanged();
|
|
||||||
void urgentChanged();
|
|
||||||
void canActivateChanged();
|
|
||||||
void canDeactivateChanged();
|
|
||||||
void canRemoveChanged();
|
|
||||||
void canSetProjectionChanged();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
// clang-format off
|
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(Windowset, QString, bId, &Windowset::idChanged);
|
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(Windowset, QString, bName, &Windowset::nameChanged);
|
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(Windowset, QList<qint32>, bCoordinates);
|
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(Windowset, bool, bActive, &Windowset::activeChanged);
|
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(Windowset, WindowsetProjection*, bProjection, &Windowset::projectionChanged);
|
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(Windowset, bool, bShouldDisplay, &Windowset::shouldDisplayChanged);
|
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(Windowset, bool, bUrgent, &Windowset::urgentChanged);
|
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(Windowset, bool, bCanActivate, &Windowset::canActivateChanged);
|
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(Windowset, bool, bCanDeactivate, &Windowset::canDeactivateChanged);
|
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(Windowset, bool, bCanRemove, &Windowset::canRemoveChanged);
|
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(Windowset, bool, bCanSetProjection, &Windowset::canSetProjectionChanged);
|
|
||||||
// clang-format on
|
|
||||||
};
|
|
||||||
|
|
||||||
///! A space occupiable by a Windowset.
|
|
||||||
/// A WindowsetProjection represents a space that can be occupied by one or more @@Windowset$s.
|
|
||||||
/// The space is one or more screens. Multiple projections may occupy the same screens.
|
|
||||||
///
|
|
||||||
/// @@WindowManager.screenProjection() can be used to get a projection representing all
|
|
||||||
/// @@Windowset$s on a given screen regardless of the WM's actual projection layout.
|
|
||||||
class WindowsetProjection: public QObject {
|
|
||||||
Q_OBJECT;
|
|
||||||
QML_ELEMENT;
|
|
||||||
QML_UNCREATABLE("");
|
|
||||||
// clang-format off
|
|
||||||
/// Screens the windowset projection spans, often a single screen or all screens.
|
|
||||||
Q_PROPERTY(QList<QuickshellScreenInfo*> screens READ default NOTIFY screensChanged BINDABLE bindableScreens);
|
|
||||||
/// Windowsets that are currently present on the projection.
|
|
||||||
Q_PROPERTY(QList<Windowset*> windowsets READ default NOTIFY windowsetsChanged BINDABLE bindableWindowsets);
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit WindowsetProjection(QObject* parent);
|
|
||||||
|
|
||||||
[[nodiscard]] QBindable<QList<QuickshellScreenInfo*>> bindableScreens() const {
|
|
||||||
return &this->bScreens;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] QBindable<QList<QScreen*>> bindableQScreens() const { return &this->bQScreens; }
|
|
||||||
|
|
||||||
[[nodiscard]] QBindable<QList<Windowset*>> bindableWindowsets() const {
|
|
||||||
return &this->bWindowsets;
|
|
||||||
}
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void screensChanged();
|
|
||||||
void windowsetsChanged();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(WindowsetProjection, QList<QScreen*>, bQScreens);
|
|
||||||
|
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(
|
|
||||||
WindowsetProjection,
|
|
||||||
QList<QuickshellScreenInfo*>,
|
|
||||||
bScreens,
|
|
||||||
&WindowsetProjection::screensChanged
|
|
||||||
);
|
|
||||||
|
|
||||||
Q_OBJECT_BINDABLE_PROPERTY(
|
|
||||||
WindowsetProjection,
|
|
||||||
QList<Windowset*>,
|
|
||||||
bWindowsets,
|
|
||||||
&WindowsetProjection::windowsetsChanged
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace qs::wm
|
|
||||||
@@ -17,7 +17,7 @@ qs_add_module_deps_light(quickshell-i3-ipc Quickshell)
|
|||||||
|
|
||||||
install_qml_module(quickshell-i3-ipc)
|
install_qml_module(quickshell-i3-ipc)
|
||||||
|
|
||||||
target_link_libraries(quickshell-i3-ipc PRIVATE Qt::Quick quickshell-core)
|
target_link_libraries(quickshell-i3-ipc PRIVATE Qt::Quick)
|
||||||
|
|
||||||
qs_module_pch(quickshell-i3-ipc SET large)
|
qs_module_pch(quickshell-i3-ipc SET large)
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <qbytearray.h>
|
#include <qbytearray.h>
|
||||||
#include <qbytearrayview.h>
|
#include <qbytearrayview.h>
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
|
#include <qdatastream.h>
|
||||||
#include <qjsonarray.h>
|
#include <qjsonarray.h>
|
||||||
#include <qjsondocument.h>
|
#include <qjsondocument.h>
|
||||||
#include <qjsonobject.h>
|
#include <qjsonobject.h>
|
||||||
@@ -14,7 +15,9 @@
|
|||||||
#include <qlocalsocket.h>
|
#include <qlocalsocket.h>
|
||||||
#include <qlogging.h>
|
#include <qlogging.h>
|
||||||
#include <qloggingcategory.h>
|
#include <qloggingcategory.h>
|
||||||
|
#include <qnamespace.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
|
#include <qsysinfo.h>
|
||||||
#include <qtenvironmentvariables.h>
|
#include <qtenvironmentvariables.h>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
@@ -86,6 +89,9 @@ I3Ipc::I3Ipc(const QList<QString>& events): mEvents(events) {
|
|||||||
QObject::connect(&this->liveEventSocket, &QLocalSocket::readyRead, this, &I3Ipc::eventSocketReady);
|
QObject::connect(&this->liveEventSocket, &QLocalSocket::readyRead, this, &I3Ipc::eventSocketReady);
|
||||||
QObject::connect(&this->liveEventSocket, &QLocalSocket::connected, this, &I3Ipc::subscribe);
|
QObject::connect(&this->liveEventSocket, &QLocalSocket::connected, this, &I3Ipc::subscribe);
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
|
this->liveEventSocketDs.setDevice(&this->liveEventSocket);
|
||||||
|
this->liveEventSocketDs.setByteOrder(static_cast<QDataStream::ByteOrder>(QSysInfo::ByteOrder));
|
||||||
}
|
}
|
||||||
|
|
||||||
void I3Ipc::makeRequest(const QByteArray& request) {
|
void I3Ipc::makeRequest(const QByteArray& request) {
|
||||||
@@ -139,21 +145,34 @@ void I3Ipc::reconnectIPC() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QVector<Event> I3Ipc::parseResponse() {
|
QVector<Event> I3Ipc::parseResponse() {
|
||||||
QVector<Event> events;
|
QVector<std::tuple<EventCode, QJsonDocument>> events;
|
||||||
|
const int magicLen = 6;
|
||||||
|
|
||||||
while (true) {
|
while (!this->liveEventSocketDs.atEnd()) {
|
||||||
this->eventReader.startTransaction();
|
this->liveEventSocketDs.startTransaction();
|
||||||
auto magic = this->eventReader.readBytes(6);
|
this->liveEventSocketDs.startTransaction();
|
||||||
auto size = this->eventReader.readI32();
|
|
||||||
auto type = this->eventReader.readI32();
|
|
||||||
auto payload = this->eventReader.readBytes(size);
|
|
||||||
if (!this->eventReader.commitTransaction()) return events;
|
|
||||||
|
|
||||||
if (magic.size() < 6 || strncmp(magic.data(), MAGIC.data(), 6) != 0) {
|
std::array<char, 6> buffer = {};
|
||||||
|
qint32 size = 0;
|
||||||
|
qint32 type = EventCode::Unknown;
|
||||||
|
|
||||||
|
this->liveEventSocketDs.readRawData(buffer.data(), magicLen);
|
||||||
|
this->liveEventSocketDs >> size;
|
||||||
|
this->liveEventSocketDs >> type;
|
||||||
|
|
||||||
|
if (!this->liveEventSocketDs.commitTransaction()) break;
|
||||||
|
|
||||||
|
QByteArray payload(size, Qt::Uninitialized);
|
||||||
|
|
||||||
|
this->liveEventSocketDs.readRawData(payload.data(), size);
|
||||||
|
|
||||||
|
if (!this->liveEventSocketDs.commitTransaction()) break;
|
||||||
|
|
||||||
|
if (strncmp(buffer.data(), MAGIC.data(), 6) != 0) {
|
||||||
qCWarning(logI3Ipc) << "No magic sequence found in string.";
|
qCWarning(logI3Ipc) << "No magic sequence found in string.";
|
||||||
this->reconnectIPC();
|
this->reconnectIPC();
|
||||||
break;
|
break;
|
||||||
}
|
};
|
||||||
|
|
||||||
if (I3IpcEvent::intToEvent(type) == EventCode::Unknown) {
|
if (I3IpcEvent::intToEvent(type) == EventCode::Unknown) {
|
||||||
qCWarning(logI3Ipc) << "Received unknown event";
|
qCWarning(logI3Ipc) << "Received unknown event";
|
||||||
@@ -185,7 +204,6 @@ void I3Ipc::eventSocketError(QLocalSocket::LocalSocketError error) const {
|
|||||||
|
|
||||||
void I3Ipc::eventSocketStateChanged(QLocalSocket::LocalSocketState state) {
|
void I3Ipc::eventSocketStateChanged(QLocalSocket::LocalSocketState state) {
|
||||||
if (state == QLocalSocket::ConnectedState) {
|
if (state == QLocalSocket::ConnectedState) {
|
||||||
this->eventReader.setDevice(&this->liveEventSocket);
|
|
||||||
qCInfo(logI3Ipc) << "I3 event socket connected.";
|
qCInfo(logI3Ipc) << "I3 event socket connected.";
|
||||||
emit this->connected();
|
emit this->connected();
|
||||||
} else if (state == QLocalSocket::UnconnectedState && this->valid) {
|
} else if (state == QLocalSocket::UnconnectedState && this->valid) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <qbytearrayview.h>
|
#include <qbytearrayview.h>
|
||||||
|
#include <qdatastream.h>
|
||||||
#include <qjsondocument.h>
|
#include <qjsondocument.h>
|
||||||
#include <qlocalsocket.h>
|
#include <qlocalsocket.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
@@ -8,8 +9,6 @@
|
|||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
|
|
||||||
#include "../../../core/streamreader.hpp"
|
|
||||||
|
|
||||||
namespace qs::i3::ipc {
|
namespace qs::i3::ipc {
|
||||||
|
|
||||||
constexpr std::string MAGIC = "i3-ipc";
|
constexpr std::string MAGIC = "i3-ipc";
|
||||||
@@ -93,7 +92,7 @@ protected:
|
|||||||
QVector<std::tuple<EventCode, QJsonDocument>> parseResponse();
|
QVector<std::tuple<EventCode, QJsonDocument>> parseResponse();
|
||||||
|
|
||||||
QLocalSocket liveEventSocket;
|
QLocalSocket liveEventSocket;
|
||||||
StreamReader eventReader;
|
QDataStream liveEventSocketDs;
|
||||||
|
|
||||||
QString mSocketPath;
|
QString mSocketPath;
|
||||||
bool valid = false;
|
bool valid = false;
|
||||||
|
|||||||
Reference in new issue
Block a user