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-pro-type-reinterpret-cast,
|
||||
-cppcoreguidelines-pro-type-vararg,
|
||||
-cppcoreguidelines-pro-type-union-access,
|
||||
-cppcoreguidelines-use-enum-class,
|
||||
google-global-names-in-headers,
|
||||
google-readability-casting,
|
||||
|
||||
@@ -15,7 +15,15 @@ Please make this descriptive enough to identify your specific package, for examp
|
||||
- `Nixpkgs`
|
||||
- `Fedora COPR (errornointernet/quickshell)`
|
||||
|
||||
If you are forking quickshell, please change `CRASHREPORT_URL` to your own issue tracker.
|
||||
`-DDISTRIBUTOR_DEBUGINFO_AVAILABLE=YES/NO`
|
||||
|
||||
If we can retrieve binaries and debug information for the package without actually running your
|
||||
distribution (e.g. from an website), and you would like to strip the binary, please set this to `YES`.
|
||||
|
||||
If we cannot retrieve debug information, please set this to `NO` and
|
||||
**ensure you aren't distributing stripped (non debuggable) binaries**.
|
||||
|
||||
In both cases you should build with `-DCMAKE_BUILD_TYPE=RelWithDebInfo` (then split or keep the debuginfo).
|
||||
|
||||
### QML Module dir
|
||||
Currently all QML modules are statically linked to quickshell, but this is where
|
||||
@@ -33,7 +41,6 @@ Quickshell has a set of base dependencies you will always need, names vary by di
|
||||
- `cmake`
|
||||
- `qt6base`
|
||||
- `qt6declarative`
|
||||
- `libdrm`
|
||||
- `qtshadertools` (build-time)
|
||||
- `spirv-tools` (build-time)
|
||||
- `pkg-config` (build-time)
|
||||
@@ -67,14 +74,7 @@ To disable: `-DCRASH_HANDLER=OFF`
|
||||
Dependencies: `cpptrace`
|
||||
|
||||
Note: `-DVENDOR_CPPTRACE=ON` can be set to vendor cpptrace using FetchContent.
|
||||
|
||||
When using FetchContent, `libunwind` is required, and `libdwarf` can be provided by the
|
||||
package manager or fetched with FetchContent.
|
||||
|
||||
*Please ensure binaries have usable symbols.* We do not necessarily need full debuginfo, but
|
||||
leaving symbols in the binary is extremely helpful. You can check if symbols are useful
|
||||
by sending a SIGSEGV to the process and ensuring symbols for the quickshell binary are present
|
||||
in the trace.
|
||||
This still requires `libunwind` to build.
|
||||
|
||||
### Jemalloc
|
||||
We recommend leaving Jemalloc enabled as it will mask memory fragmentation caused
|
||||
@@ -147,6 +147,7 @@ Enables streaming video from monitors and toplevel windows through various proto
|
||||
To disable: `-DSCREENCOPY=OFF`
|
||||
|
||||
Dependencies:
|
||||
- `libdrm`
|
||||
- `libgbm`
|
||||
- `vulkan-headers` (build-time)
|
||||
|
||||
@@ -242,7 +243,7 @@ Only `ninja` builds are tested, but makefiles may work.
|
||||
|
||||
#### Configuring the build
|
||||
```sh
|
||||
$ cmake -GNinja -B build -DCMAKE_BUILD_TYPE=Release [additional disable flags from above here]
|
||||
$ cmake -GNinja -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo [additional disable flags from above here]
|
||||
```
|
||||
|
||||
Note that features you do not supply dependencies for MUST be disabled with their associated flags
|
||||
|
||||
+1
-3
@@ -9,9 +9,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(QS_BUILD_OPTIONS "")
|
||||
|
||||
# should be changed for forks
|
||||
set(CRASHREPORT_URL "https://github.com/outfoxxed/quickshell/issues/new?template=crash2.yml" CACHE STRING "Bugreport URL")
|
||||
|
||||
function(boption VAR NAME DEFAULT)
|
||||
cmake_parse_arguments(PARSE_ARGV 3 arg "" "REQUIRES" "")
|
||||
|
||||
@@ -43,6 +40,7 @@ string(APPEND QS_BUILD_OPTIONS " Distributor: ${DISTRIBUTOR}")
|
||||
|
||||
message(STATUS "Quickshell configuration")
|
||||
message(STATUS " Distributor: ${DISTRIBUTOR}")
|
||||
boption(DISTRIBUTOR_DEBUGINFO_AVAILABLE "Distributor provided debuginfo" NO)
|
||||
boption(NO_PCH "Disable precompild headers (dev)" OFF)
|
||||
boption(BUILD_TESTING "Build tests (dev)" OFF)
|
||||
boption(ASAN "ASAN (dev)" OFF) # note: better output with gcc than clang
|
||||
|
||||
+221
-26
@@ -1,40 +1,235 @@
|
||||
# Contributing
|
||||
# Contributing / Development
|
||||
Instructions for development setup and upstreaming patches.
|
||||
|
||||
Thank you for taking the time to contribute.
|
||||
To ensure nobody's time is wasted, please follow the rules below.
|
||||
If you just want to build or package quickshell see [BUILD.md](BUILD.md).
|
||||
|
||||
## Acceptable Code Contributions
|
||||
## Development
|
||||
|
||||
- All changes submitted MUST be **fully understood by the submitter**. If you do not know why or how
|
||||
your change works, do not submit it to be merged. You must be able to explain your reasoning
|
||||
for every change.
|
||||
Install the dependencies listed in [BUILD.md](BUILD.md).
|
||||
You probably want all of them even if you don't use all of them
|
||||
to ensure tests work correctly and avoid passing a bunch of configure
|
||||
flags when you need to wipe the build directory.
|
||||
|
||||
- Changes MUST be submitted by a human who will be responsible for them. Changes submitted without
|
||||
a human in the loop such as automated tooling and AI Agents are **strictly disallowed**. Accounts
|
||||
responsible for such contribution attempts **will be banned**.
|
||||
Quickshell also uses `just` for common development command aliases.
|
||||
|
||||
- Changes MUST respect Quickshell's license and the license of any source works. Changes including
|
||||
code from any other works must disclose the source of the code, explain why it was used, and
|
||||
ensure the license is compatible.
|
||||
The dependencies are also available as a nix shell or nix flake which we recommend
|
||||
using with nix-direnv.
|
||||
|
||||
- Changes must follow the guidelines outlined in [HACKING.md](HACKING.md) for style and substance.
|
||||
Common aliases:
|
||||
- `just configure [<debug|release> [extra cmake args]]` (note that you must specify debug/release to specify extra args)
|
||||
- `just build` - runs the build, configuring if not configured already.
|
||||
- `just run [args]` - runs quickshell with the given arguments
|
||||
- `just clean` - clean up build artifacts. `just clean build` is somewhat common.
|
||||
|
||||
- Changes must stand on their own as a unit. Do not make multiple unrelated changes in one PR.
|
||||
Changes depending on prior merges should be marked as a draft.
|
||||
### Formatting
|
||||
All contributions should be formatted similarly to what already exists.
|
||||
Group related functionality together.
|
||||
|
||||
## Acceptable Non-code Contributions
|
||||
Run the formatter using `just fmt`.
|
||||
If the results look stupid, fix the clang-format file if possible,
|
||||
or disable clang-format in the affected area
|
||||
using `// clang-format off` and `// clang-format on`.
|
||||
|
||||
- Bug and crash reports. You must follow the instructions in the issue templates and provide the
|
||||
information requested.
|
||||
#### Style preferences not caught by clang-format
|
||||
These are flexible. You can ignore them if it looks or works better to
|
||||
for one reason or another.
|
||||
|
||||
- Feature requests can be made via Issues. Please check to ensure nobody else has requested the same feature.
|
||||
Use `auto` if the type of a variable can be deduced automatically, instead of
|
||||
redeclaring the returned value's type. Additionally, auto should be used when a
|
||||
constructor takes arguments.
|
||||
|
||||
- Do not make insubstantial or pointless changes.
|
||||
```cpp
|
||||
auto x = <expr>; // ok
|
||||
auto x = QString::number(3); // ok
|
||||
QString x; // ok
|
||||
QString x = "foo"; // ok
|
||||
auto x = QString("foo"); // ok
|
||||
|
||||
- Changes to project rules / policy / governance will not be entertained, except from significant
|
||||
long-term contributors. These changes should not be addressed through contribution channels.
|
||||
auto x = QString(); // avoid
|
||||
QString x(); // avoid
|
||||
QString x("foo"); // avoid
|
||||
```
|
||||
|
||||
## Merge timelines
|
||||
Put newlines around logical units of code, and after closing braces. If the
|
||||
most reasonable logical unit of code takes only a single line, it should be
|
||||
merged into the next single line logical unit if applicable.
|
||||
```cpp
|
||||
// multiple units
|
||||
auto x = <expr>; // unit 1
|
||||
auto y = <expr>; // unit 2
|
||||
|
||||
We handle work for the most part on a push basis. If your PR has been ignored for a while
|
||||
and is still relevant please bump it.
|
||||
auto x = <expr>; // unit 1
|
||||
emit this->y(); // unit 2
|
||||
|
||||
auto x1 = <expr>; // unit 1
|
||||
auto x2 = <expr>; // unit 1
|
||||
auto x3 = <expr>; // unit 1
|
||||
|
||||
auto y1 = <expr>; // unit 2
|
||||
auto y2 = <expr>; // unit 2
|
||||
auto y3 = <expr>; // unit 2
|
||||
|
||||
// one unit
|
||||
auto x = <expr>;
|
||||
if (x...) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// if more than one variable needs to be used then add a newline
|
||||
auto x = <expr>;
|
||||
auto y = <expr>;
|
||||
|
||||
if (x && y) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Class formatting:
|
||||
```cpp
|
||||
//! Doc comment summary
|
||||
/// Doc comment body
|
||||
class Foo: public QObject {
|
||||
// The Q_OBJECT macro comes first. Macros are ; terminated.
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_CLASSINFO(...);
|
||||
// Properties must stay on a single line or the doc generator won't be able to pick them up
|
||||
Q_PROPERTY(...);
|
||||
/// Doc comment
|
||||
Q_PROPERTY(...);
|
||||
/// Doc comment
|
||||
Q_PROPERTY(...);
|
||||
|
||||
public:
|
||||
// Classes should have explicit constructors if they aren't intended to
|
||||
// implicitly cast. The constructor can be inline in the header if it has no body.
|
||||
explicit Foo(QObject* parent = nullptr): QObject(parent) {}
|
||||
|
||||
// Instance functions if applicable.
|
||||
static Foo* instance();
|
||||
|
||||
// Member functions unrelated to properties come next
|
||||
void function();
|
||||
void function();
|
||||
void function();
|
||||
|
||||
// Then Q_INVOKABLEs
|
||||
Q_INVOKABLE function();
|
||||
/// Doc comment
|
||||
Q_INVOKABLE function();
|
||||
/// Doc comment
|
||||
Q_INVOKABLE function();
|
||||
|
||||
// Then property related functions, in the order (bindable, getter, setter).
|
||||
// Related functions may be included here as well. Function bodies may be inline
|
||||
// if they are a single expression. There should be a newline between each
|
||||
// property's methods.
|
||||
[[nodiscard]] QBindable<T> bindableFoo() { return &this->bFoo; }
|
||||
[[nodiscard]] T foo() const { return this->foo; }
|
||||
void setFoo();
|
||||
|
||||
[[nodiscard]] T bar() const { return this->foo; }
|
||||
void setBar();
|
||||
|
||||
signals:
|
||||
// Signals that are not property change related go first.
|
||||
// Property change signals go in property definition order.
|
||||
void asd();
|
||||
void asd2();
|
||||
void fooChanged();
|
||||
void barChanged();
|
||||
|
||||
public slots:
|
||||
// generally Q_INVOKABLEs are preferred to public slots.
|
||||
void slot();
|
||||
|
||||
private slots:
|
||||
// ...
|
||||
|
||||
private:
|
||||
// statics, then functions, then fields
|
||||
static const foo BAR;
|
||||
static void foo();
|
||||
|
||||
void foo();
|
||||
void bar();
|
||||
|
||||
// property related members are prefixed with `m`.
|
||||
QString mFoo;
|
||||
QString bar;
|
||||
|
||||
// Bindables go last and should be prefixed with `b`.
|
||||
Q_OBJECT_BINDABLE_PROPERTY(Foo, QString, bFoo, &Foo::fooChanged);
|
||||
};
|
||||
```
|
||||
|
||||
### Linter
|
||||
All contributions should pass the linter.
|
||||
|
||||
Note that running the linter requires disabling precompiled
|
||||
headers and including the test codepaths:
|
||||
```sh
|
||||
$ just configure debug -DNO_PCH=ON -DBUILD_TESTING=ON
|
||||
$ just lint-changed
|
||||
```
|
||||
|
||||
If the linter is complaining about something that you think it should not,
|
||||
please disable the lint in your MR and explain your reasoning if it isn't obvious.
|
||||
|
||||
### Tests
|
||||
If you feel like the feature you are working on is very complex or likely to break,
|
||||
please write some tests. We will ask you to directly if you send in an MR for an
|
||||
overly complex or breakable feature.
|
||||
|
||||
At least all tests that passed before your changes should still be passing
|
||||
by the time your contribution is ready.
|
||||
|
||||
You can run the tests using `just test` but you must enable them first
|
||||
using `-DBUILD_TESTING=ON`.
|
||||
|
||||
### Documentation
|
||||
Most of quickshell's documentation is automatically generated from the source code.
|
||||
You should annotate `Q_PROPERTY`s and `Q_INVOKABLE`s with doc comments. Note that the parser
|
||||
cannot handle random line breaks and will usually require you to disable clang-format if the
|
||||
lines are too long.
|
||||
|
||||
Before submitting an MR, if adding new features please make sure the documentation is generated
|
||||
reasonably using the `quickshell-docs` repo. We recommend checking it out at `/docs` in this repo.
|
||||
|
||||
Doc comments take the form `///` or `///!` (summary) and work with markdown.
|
||||
You can reference other types using the `@@[Module.][Type.][member]` shorthand
|
||||
where all parts are optional. If module or type are not specified they will
|
||||
be inferred as the current module. Member can be a `property`, `function()` or `signal(s)`.
|
||||
Look at existing code for how it works.
|
||||
|
||||
Quickshell modules additionally have a `module.md` file which contains a summary, description,
|
||||
and list of headers to scan for documentation.
|
||||
|
||||
## Contributing
|
||||
|
||||
### Commits
|
||||
Please structure your commit messages as `scope[!]: commit` where
|
||||
the scope is something like `core` or `service/mpris`. (pick what has been
|
||||
used historically or what makes sense if new). Add `!` for changes that break
|
||||
existing APIs or functionality.
|
||||
|
||||
Commit descriptions should contain a summary of the changes if they are not
|
||||
sufficiently addressed in the commit message.
|
||||
|
||||
Please squash/rebase additions or edits to previous changes and follow the
|
||||
commit style to keep the history easily searchable at a glance.
|
||||
Depending on the change, it is often reasonable to squash it into just
|
||||
a single commit. (If you do not follow this we will squash your changes
|
||||
for you.)
|
||||
|
||||
### Sending patches
|
||||
You may contribute by submitting a pull request on github, asking for
|
||||
an account on our git server, or emailing patches / git bundles
|
||||
directly to `[email protected]`.
|
||||
|
||||
### Getting help
|
||||
If you're getting stuck, you can come talk to us in the
|
||||
[quickshell-development matrix room](https://matrix.to/#/#quickshell-development:outfoxxed.me)
|
||||
for help on implementation, conventions, etc.
|
||||
Feel free to ask for advice early in your implementation if you are
|
||||
unsure.
|
||||
-226
@@ -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") }}
|
||||
|
||||
lint-staged:
|
||||
git diff --staged --name-only --diff-filter=d HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
|
||||
git diff --staged --name-only HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
|
||||
|
||||
configure target='debug' *FLAGS='':
|
||||
cmake -GNinja -B {{builddir}} \
|
||||
|
||||
@@ -7,9 +7,7 @@ This repo is hosted at:
|
||||
- https://github.com/quickshell-mirror/quickshell
|
||||
|
||||
# Contributing / Development
|
||||
- [HACKING.md](HACKING.md) - Development instructions and policy.
|
||||
- [CONTRIBUTING.md](CONTRIBUTING.md) - Contribution policy.
|
||||
- [BUILD.md](BUILD.md) - Packaging and build instructions.
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
||||
|
||||
#### License
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ set shell id.
|
||||
- Added Quickshell version checking and version gated preprocessing.
|
||||
- Added a way to detect if an icon is from the system icon theme or not.
|
||||
- Added vulkan support to screencopy.
|
||||
- Added generic WindowManager interface implementing ext-workspace.
|
||||
|
||||
## Other Changes
|
||||
|
||||
@@ -34,10 +33,6 @@ set shell id.
|
||||
- IPC operations filter available instances to the current display connection by default.
|
||||
- PwNodeLinkTracker ignores sound level monitoring programs.
|
||||
- Replaced breakpad with cpptrace.
|
||||
- Reloads are prevented if no file content has changed.
|
||||
- Added `QS_DISABLE_FILE_WATCHER` environment variable to disable file watching.
|
||||
- Added `QS_DISABLE_CRASH_HANDLER` environment variable to disable crash handling.
|
||||
- Added `QS_CRASHREPORT_URL` environment variable to allow overriding the crash reporter link.
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
@@ -55,14 +50,9 @@ set shell id.
|
||||
- Fixed ClippingRectangle related crashes.
|
||||
- Fixed crashes when monitors are unplugged.
|
||||
- Fixed crashes when default pipewire devices are lost.
|
||||
- Fixed ToplevelManager not clearing activeToplevel on deactivation.
|
||||
- Desktop action order is now preserved.
|
||||
- Fixed partial socket reads in greetd and hyprland on slow machines.
|
||||
|
||||
## Packaging Changes
|
||||
|
||||
- `glib` and `polkit` have been added as dependencies when compiling with polkit agent support.
|
||||
- `vulkan-headers` has been added as a build-time dependency for screencopy (Vulkan backend support).
|
||||
- `breakpad` has been replaced by `cpptrace`, which is far easier to package, and the `CRASH_REPORTER` cmake variable has been replaced with `CRASH_HANDLER` to stop this from being easy to ignore.
|
||||
- `DISTRIBUTOR_DEBUGINFO_AVAILABLE` was removed as it is no longer important without breakpad.
|
||||
- `libdrm` is now unconditionally required as a direct dependency.
|
||||
+12
-10
@@ -19,7 +19,6 @@
|
||||
wayland-protocols,
|
||||
wayland-scanner,
|
||||
xorg,
|
||||
libxcb ? xorg.libxcb,
|
||||
libdrm,
|
||||
libgbm ? null,
|
||||
vulkan-headers,
|
||||
@@ -76,21 +75,24 @@
|
||||
buildInputs = [
|
||||
qt6.qtbase
|
||||
qt6.qtdeclarative
|
||||
libdrm
|
||||
cli11
|
||||
]
|
||||
++ lib.optional withQtSvg qt6.qtsvg
|
||||
++ lib.optional withCrashHandler (cpptrace.overrideAttrs (prev: {
|
||||
cmakeFlags = prev.cmakeFlags ++ [
|
||||
"-DCPPTRACE_UNWIND_WITH_LIBUNWIND=TRUE"
|
||||
];
|
||||
buildInputs = prev.buildInputs ++ [ libunwind ];
|
||||
}))
|
||||
++ lib.optionals withCrashHandler [
|
||||
(cpptrace.overrideAttrs (prev: {
|
||||
cmakeFlags = prev.cmakeFlags ++ [
|
||||
"-DCPPTRACE_UNWIND_WITH_LIBUNWIND=TRUE"
|
||||
];
|
||||
buildInputs = prev.buildInputs ++ [ libunwind ];
|
||||
}))
|
||||
libunwind
|
||||
libdwarf
|
||||
]
|
||||
++ lib.optional withJemalloc jemalloc
|
||||
++ lib.optional (withWayland && lib.strings.compareVersions qt6.qtbase.version "6.10.0" == -1) qt6.qtwayland
|
||||
++ lib.optionals withWayland [ wayland wayland-protocols ]
|
||||
++ lib.optionals (withWayland && libgbm != null) [ libgbm vulkan-headers ]
|
||||
++ lib.optional withX11 libxcb
|
||||
++ lib.optionals (withWayland && libgbm != null) [ libdrm libgbm vulkan-headers ]
|
||||
++ lib.optional withX11 xorg.libxcb
|
||||
++ lib.optional withPam pam
|
||||
++ lib.optional withPipewire pipewire
|
||||
++ lib.optionals withPolkit [ polkit glib ];
|
||||
|
||||
@@ -11,7 +11,6 @@ add_subdirectory(window)
|
||||
add_subdirectory(io)
|
||||
add_subdirectory(widgets)
|
||||
add_subdirectory(ui)
|
||||
add_subdirectory(windowmanager)
|
||||
|
||||
if (CRASH_HANDLER)
|
||||
add_subdirectory(crash)
|
||||
|
||||
@@ -15,6 +15,12 @@ else()
|
||||
set(CRASH_HANDLER_DEF 0)
|
||||
endif()
|
||||
|
||||
if (DISTRIBUTOR_DEBUGINFO_AVAILABLE)
|
||||
set(DEBUGINFO_AVAILABLE 1)
|
||||
else()
|
||||
set(DEBUGINFO_AVAILABLE 0)
|
||||
endif()
|
||||
|
||||
configure_file(build.hpp.in build.hpp @ONLY ESCAPE_QUOTES)
|
||||
|
||||
target_include_directories(quickshell-build INTERFACE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
@@ -8,10 +8,10 @@
|
||||
#define QS_UNRELEASED_FEATURES "@UNRELEASED_FEATURES@"
|
||||
#define GIT_REVISION "@GIT_REVISION@"
|
||||
#define DISTRIBUTOR "@DISTRIBUTOR@"
|
||||
#define DISTRIBUTOR_DEBUGINFO_AVAILABLE @DEBUGINFO_AVAILABLE@
|
||||
#define CRASH_HANDLER @CRASH_HANDLER_DEF@
|
||||
#define BUILD_TYPE "@CMAKE_BUILD_TYPE@"
|
||||
#define COMPILER "@CMAKE_CXX_COMPILER_ID@ (@CMAKE_CXX_COMPILER_VERSION@)"
|
||||
#define COMPILE_FLAGS "@CMAKE_CXX_FLAGS@"
|
||||
#define BUILD_CONFIGURATION "@QS_BUILD_OPTIONS@"
|
||||
#define CRASHREPORT_URL "@CRASHREPORT_URL@"
|
||||
// NOLINTEND
|
||||
@@ -1,4 +1,3 @@
|
||||
pkg_check_modules(libdrm REQUIRED IMPORTED_TARGET libdrm)
|
||||
qt_add_library(quickshell-core STATIC
|
||||
plugin.cpp
|
||||
shell.cpp
|
||||
@@ -41,8 +40,6 @@ qt_add_library(quickshell-core STATIC
|
||||
scriptmodel.cpp
|
||||
colorquantizer.cpp
|
||||
toolsupport.cpp
|
||||
streamreader.cpp
|
||||
debuginfo.cpp
|
||||
)
|
||||
|
||||
qt_add_qml_module(quickshell-core
|
||||
@@ -55,7 +52,7 @@ qt_add_qml_module(quickshell-core
|
||||
|
||||
install_qml_module(quickshell-core)
|
||||
|
||||
target_link_libraries(quickshell-core PRIVATE Qt::Quick Qt::QuickPrivate Qt::Widgets quickshell-build PkgConfig::libdrm)
|
||||
target_link_libraries(quickshell-core PRIVATE Qt::Quick Qt::QuickPrivate Qt::Widgets quickshell-build)
|
||||
|
||||
qs_module_pch(quickshell-core SET large)
|
||||
|
||||
|
||||
@@ -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 entries = QHash<QString, QPair<Locale, QString>>();
|
||||
|
||||
auto actionOrder = QStringList();
|
||||
auto pendingActions = QHash<QString, DesktopActionData>();
|
||||
|
||||
auto finishCategory = [&data, &groupName, &entries, &actionOrder, &pendingActions]() {
|
||||
auto finishCategory = [&data, &groupName, &entries]() {
|
||||
if (groupName == "Desktop Entry") {
|
||||
if (entries.value("Type").second != "Application") return;
|
||||
|
||||
@@ -132,10 +129,9 @@ ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString&
|
||||
else if (key == "Terminal") data.terminal = value == "true";
|
||||
else if (key == "Categories") data.categories = value.split(u';', Qt::SkipEmptyParts);
|
||||
else if (key == "Keywords") data.keywords = value.split(u';', Qt::SkipEmptyParts);
|
||||
else if (key == "Actions") actionOrder = value.split(u';', Qt::SkipEmptyParts);
|
||||
}
|
||||
} else if (groupName.startsWith("Desktop Action ")) {
|
||||
auto actionName = groupName.sliced(15);
|
||||
auto actionName = groupName.sliced(16);
|
||||
DesktopActionData action;
|
||||
action.id = actionName;
|
||||
|
||||
@@ -151,7 +147,7 @@ ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString&
|
||||
}
|
||||
}
|
||||
|
||||
pendingActions.insert(actionName, action);
|
||||
data.actions.insert(actionName, action);
|
||||
}
|
||||
|
||||
entries.clear();
|
||||
@@ -197,13 +193,6 @@ ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString&
|
||||
}
|
||||
|
||||
finishCategory();
|
||||
|
||||
for (const auto& actionId: actionOrder) {
|
||||
if (pendingActions.contains(actionId)) {
|
||||
data.actions.append(pendingActions.value(actionId));
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -227,18 +216,17 @@ void DesktopEntry::updateState(const ParsedDesktopEntryData& newState) {
|
||||
this->updateActions(newState.actions);
|
||||
}
|
||||
|
||||
void DesktopEntry::updateActions(const QVector<DesktopActionData>& newActions) {
|
||||
void DesktopEntry::updateActions(const QHash<QString, DesktopActionData>& newActions) {
|
||||
auto old = this->mActions;
|
||||
this->mActions.clear();
|
||||
|
||||
for (const auto& d: newActions) {
|
||||
for (const auto& [key, d]: newActions.asKeyValueRange()) {
|
||||
DesktopAction* act = nullptr;
|
||||
auto found = std::ranges::find(old, d.id, &DesktopAction::mId);
|
||||
if (found != old.end()) {
|
||||
act = *found;
|
||||
if (auto found = old.find(key); found != old.end()) {
|
||||
act = found.value();
|
||||
old.erase(found);
|
||||
} else {
|
||||
act = new DesktopAction(d.id, this);
|
||||
this->mActions.insert(key, act);
|
||||
}
|
||||
|
||||
Qt::beginPropertyUpdateGroup();
|
||||
@@ -249,7 +237,6 @@ void DesktopEntry::updateActions(const QVector<DesktopActionData>& newActions) {
|
||||
Qt::endPropertyUpdateGroup();
|
||||
|
||||
act->mEntries = d.entries;
|
||||
this->mActions.append(act);
|
||||
}
|
||||
|
||||
for (auto* leftover: old) {
|
||||
@@ -263,7 +250,7 @@ void DesktopEntry::execute() const {
|
||||
|
||||
bool DesktopEntry::isValid() const { return !this->bName.value().isEmpty(); }
|
||||
|
||||
QVector<DesktopAction*> DesktopEntry::actions() const { return this->mActions; }
|
||||
QVector<DesktopAction*> DesktopEntry::actions() const { return this->mActions.values(); }
|
||||
|
||||
QVector<QString> DesktopEntry::parseExecString(const QString& execString) {
|
||||
QVector<QString> arguments;
|
||||
|
||||
@@ -43,7 +43,7 @@ struct ParsedDesktopEntryData {
|
||||
QVector<QString> categories;
|
||||
QVector<QString> keywords;
|
||||
QHash<QString, QString> entries;
|
||||
QVector<DesktopActionData> actions;
|
||||
QHash<QString, DesktopActionData> actions;
|
||||
};
|
||||
|
||||
/// A desktop entry. See @@DesktopEntries for details.
|
||||
@@ -164,10 +164,10 @@ public:
|
||||
// clang-format on
|
||||
|
||||
private:
|
||||
void updateActions(const QVector<DesktopActionData>& newActions);
|
||||
void updateActions(const QHash<QString, DesktopActionData>& newActions);
|
||||
|
||||
ParsedDesktopEntryData state;
|
||||
QVector<DesktopAction*> mActions;
|
||||
QHash<QString, DesktopAction*> mActions;
|
||||
|
||||
friend class DesktopAction;
|
||||
};
|
||||
|
||||
@@ -209,8 +209,6 @@ bool EngineGeneration::setExtraWatchedFiles(const QVector<QString>& files) {
|
||||
for (const auto& file: files) {
|
||||
if (!this->scanner.scannedFiles.contains(file)) {
|
||||
this->extraWatchedFiles.append(file);
|
||||
QByteArray data;
|
||||
this->scanner.readAndHashFile(file, data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,11 +229,6 @@ void EngineGeneration::onFileChanged(const QString& name) {
|
||||
auto fileInfo = QFileInfo(name);
|
||||
if (fileInfo.isFile() && fileInfo.size() == 0) return;
|
||||
|
||||
if (!this->scanner.hasFileContentChanged(name)) {
|
||||
qCDebug(logQmlScanner) << "Ignoring file change with unchanged content:" << name;
|
||||
return;
|
||||
}
|
||||
|
||||
emit this->filesChanged();
|
||||
}
|
||||
}
|
||||
@@ -244,11 +237,6 @@ void EngineGeneration::onDirectoryChanged() {
|
||||
// try to find any files that were just deleted from a replace operation
|
||||
for (auto& file: this->deletedWatchedFiles) {
|
||||
if (QFileInfo(file).exists()) {
|
||||
if (!this->scanner.hasFileContentChanged(file)) {
|
||||
qCDebug(logQmlScanner) << "Ignoring restored file with unchanged content:" << file;
|
||||
continue;
|
||||
}
|
||||
|
||||
emit this->filesChanged();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -60,9 +60,7 @@ void QuickshellSettings::setWorkingDirectory(QString workingDirectory) { // NOLI
|
||||
emit this->workingDirectoryChanged();
|
||||
}
|
||||
|
||||
bool QuickshellSettings::watchFiles() const {
|
||||
return this->mWatchFiles && qEnvironmentVariableIsEmpty("QS_DISABLE_FILE_WATCHER");
|
||||
}
|
||||
bool QuickshellSettings::watchFiles() const { return this->mWatchFiles; }
|
||||
|
||||
void QuickshellSettings::setWatchFiles(bool watchFiles) {
|
||||
if (watchFiles == this->mWatchFiles) return;
|
||||
|
||||
+11
-30
@@ -3,7 +3,6 @@
|
||||
#include <utility>
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qcryptographichash.h>
|
||||
#include <qdir.h>
|
||||
#include <qfileinfo.h>
|
||||
#include <qjsengine.h>
|
||||
@@ -22,25 +21,6 @@
|
||||
|
||||
QS_LOGGING_CATEGORY(logQmlScanner, "quickshell.qmlscanner", QtWarningMsg);
|
||||
|
||||
bool QmlScanner::readAndHashFile(const QString& path, QByteArray& data) {
|
||||
auto file = QFile(path);
|
||||
if (!file.open(QFile::ReadOnly)) return false;
|
||||
data = file.readAll();
|
||||
this->fileHashes.insert(path, QCryptographicHash::hash(data, QCryptographicHash::Md5));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QmlScanner::hasFileContentChanged(const QString& path) const {
|
||||
auto it = this->fileHashes.constFind(path);
|
||||
if (it == this->fileHashes.constEnd()) return true;
|
||||
|
||||
auto file = QFile(path);
|
||||
if (!file.open(QFile::ReadOnly)) return true;
|
||||
|
||||
auto newHash = QCryptographicHash::hash(file.readAll(), QCryptographicHash::Md5);
|
||||
return newHash != it.value();
|
||||
}
|
||||
|
||||
void QmlScanner::scanDir(const QDir& dir) {
|
||||
if (this->scannedDirs.contains(dir)) return;
|
||||
this->scannedDirs.push_back(dir);
|
||||
@@ -129,13 +109,13 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
|
||||
|
||||
qCDebug(logQmlScanner) << "Scanning qml file" << path;
|
||||
|
||||
QByteArray fileData;
|
||||
if (!this->readAndHashFile(path, fileData)) {
|
||||
auto file = QFile(path);
|
||||
if (!file.open(QFile::ReadOnly | QFile::Text)) {
|
||||
qCWarning(logQmlScanner) << "Failed to open file" << path;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto stream = QTextStream(&fileData);
|
||||
auto stream = QTextStream(&file);
|
||||
auto imports = QVector<QString>();
|
||||
|
||||
bool inHeader = true;
|
||||
@@ -239,6 +219,8 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
|
||||
postError("unclosed preprocessor if block");
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
if (isOverridden) {
|
||||
this->fileIntercepts.insert(path, overrideText);
|
||||
}
|
||||
@@ -275,11 +257,8 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
|
||||
continue;
|
||||
}
|
||||
|
||||
if (import.endsWith(".js")) {
|
||||
this->scannedFiles.push_back(cpath);
|
||||
QByteArray jsData;
|
||||
this->readAndHashFile(cpath, jsData);
|
||||
} else this->scanDir(cpath);
|
||||
if (import.endsWith(".js")) this->scannedFiles.push_back(cpath);
|
||||
else this->scanDir(cpath);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -294,12 +273,14 @@ void QmlScanner::scanQmlRoot(const QString& path) {
|
||||
bool QmlScanner::scanQmlJson(const QString& path) {
|
||||
qCDebug(logQmlScanner) << "Scanning qml.json file" << path;
|
||||
|
||||
QByteArray data;
|
||||
if (!this->readAndHashFile(path, data)) {
|
||||
auto file = QFile(path);
|
||||
if (!file.open(QFile::ReadOnly | QFile::Text)) {
|
||||
qCWarning(logQmlScanner) << "Failed to open file" << path;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto data = file.readAll();
|
||||
|
||||
// Importing this makes CI builds fail for some reason.
|
||||
QJsonParseError error; // NOLINT (misc-include-cleaner)
|
||||
auto json = QJsonDocument::fromJson(data, &error);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <qbytearray.h>
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdir.h>
|
||||
#include <qhash.h>
|
||||
@@ -22,7 +21,6 @@ public:
|
||||
|
||||
QVector<QDir> scannedDirs;
|
||||
QVector<QString> scannedFiles;
|
||||
QHash<QString, QByteArray> fileHashes;
|
||||
QHash<QString, QString> fileIntercepts;
|
||||
|
||||
struct ScanError {
|
||||
@@ -33,9 +31,6 @@ public:
|
||||
|
||||
QVector<ScanError> scanErrors;
|
||||
|
||||
bool readAndHashFile(const QString& path, QByteArray& data);
|
||||
[[nodiscard]] bool hasFileContentChanged(const QString& path) const;
|
||||
|
||||
private:
|
||||
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 <algorithm>
|
||||
#include <array>
|
||||
#include <cerrno>
|
||||
#include <csignal>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
@@ -65,27 +64,12 @@ void signalHandler(
|
||||
for (size_t i = 0; i < static_cast<size_t>(frameCount); i++) {
|
||||
auto frame = cpptrace::safe_object_frame();
|
||||
cpptrace::get_safe_object_frame(traceBuffer[i], &frame);
|
||||
|
||||
auto* wptr = reinterpret_cast<char*>(&frame);
|
||||
auto* end = wptr + sizeof(cpptrace::safe_object_frame); // NOLINT
|
||||
while (wptr != end) {
|
||||
auto r = write(CrashInfo::INSTANCE.traceFd, &frame, sizeof(cpptrace::safe_object_frame));
|
||||
if (r < 0 && errno == EINTR) continue;
|
||||
if (r <= 0) goto fail;
|
||||
wptr += r; // NOLINT
|
||||
}
|
||||
write(CrashInfo::INSTANCE.traceFd, &frame, sizeof(cpptrace::safe_object_frame));
|
||||
}
|
||||
|
||||
fail:;
|
||||
}
|
||||
|
||||
auto coredumpPid = fork();
|
||||
if (coredumpPid == 0) {
|
||||
// NOLINTBEGIN (misc-include-cleaner)
|
||||
sigset_t set;
|
||||
sigfillset(&set);
|
||||
sigprocmask(SIG_UNBLOCK, &set, nullptr);
|
||||
// NOLINTEND
|
||||
raise(sig);
|
||||
_exit(-1);
|
||||
}
|
||||
@@ -184,6 +168,7 @@ void CrashHandler::init() {
|
||||
// Set up alternate signal stack for stack overflow handling
|
||||
auto ss = stack_t();
|
||||
ss.ss_sp = new char[SIGSTKSZ];
|
||||
;
|
||||
ss.ss_size = SIGSTKSZ;
|
||||
ss.ss_flags = 0;
|
||||
sigaltstack(&ss, nullptr);
|
||||
|
||||
+15
-16
@@ -5,7 +5,6 @@
|
||||
#include <qapplication.h>
|
||||
#include <qboxlayout.h>
|
||||
#include <qconfig.h>
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdesktopservices.h>
|
||||
#include <qfont.h>
|
||||
#include <qfontinfo.h>
|
||||
@@ -13,22 +12,11 @@
|
||||
#include <qnamespace.h>
|
||||
#include <qobject.h>
|
||||
#include <qpushbutton.h>
|
||||
#include <qtenvironmentvariables.h>
|
||||
#include <qtversion.h>
|
||||
#include <qwidget.h>
|
||||
|
||||
#include "build.hpp"
|
||||
|
||||
namespace {
|
||||
QString crashreportUrl() {
|
||||
if (auto url = qEnvironmentVariable("QS_CRASHREPORT_URL"); !url.isEmpty()) {
|
||||
return url;
|
||||
}
|
||||
|
||||
return CRASHREPORT_URL;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
class ReportLabel: public QWidget {
|
||||
public:
|
||||
ReportLabel(const QString& label, const QString& content, QWidget* parent): QWidget(parent) {
|
||||
@@ -79,16 +67,22 @@ CrashReporterGui::CrashReporterGui(QString reportFolder, int pid)
|
||||
|
||||
if (qtVersionMatches) {
|
||||
mainLayout->addWidget(
|
||||
new QLabel("Please open a bug report for this issue on the issue tracker.")
|
||||
new QLabel("Please open a bug report for this issue via github or email.")
|
||||
);
|
||||
} else {
|
||||
mainLayout->addWidget(new QLabel(
|
||||
"Please rebuild Quickshell against the current Qt version.\n"
|
||||
"If this does not solve the problem, please open a bug report on the issue tracker."
|
||||
"If this does not solve the problem, please open a bug report via github or email."
|
||||
));
|
||||
}
|
||||
|
||||
mainLayout->addWidget(new ReportLabel("Tracker:", crashreportUrl(), this));
|
||||
mainLayout->addWidget(new ReportLabel(
|
||||
"Github:",
|
||||
"https://github.com/quickshell-mirror/quickshell/issues/new?template=crash2.yml",
|
||||
this
|
||||
));
|
||||
|
||||
mainLayout->addWidget(new ReportLabel("Email:", "[email protected]", this));
|
||||
|
||||
auto* buttons = new QWidget(this);
|
||||
buttons->setMinimumWidth(900);
|
||||
@@ -118,5 +112,10 @@ void CrashReporterGui::openFolder() {
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(this->reportFolder));
|
||||
}
|
||||
|
||||
void CrashReporterGui::openReportUrl() { QDesktopServices::openUrl(QUrl(crashreportUrl())); }
|
||||
void CrashReporterGui::openReportUrl() {
|
||||
QDesktopServices::openUrl(
|
||||
QUrl("https://github.com/outfoxxed/quickshell/issues/new?template=crash2.yml")
|
||||
);
|
||||
}
|
||||
|
||||
void CrashReporterGui::cancel() { QApplication::quit(); }
|
||||
+31
-3
@@ -6,6 +6,7 @@
|
||||
#include <cpptrace/basic.hpp>
|
||||
#include <cpptrace/formatting.hpp>
|
||||
#include <qapplication.h>
|
||||
#include <qconfig.h>
|
||||
#include <qcoreapplication.h>
|
||||
#include <qdatastream.h>
|
||||
#include <qdir.h>
|
||||
@@ -14,18 +15,19 @@
|
||||
#include <qloggingcategory.h>
|
||||
#include <qtenvironmentvariables.h>
|
||||
#include <qtextstream.h>
|
||||
#include <qtversion.h>
|
||||
#include <qtypes.h>
|
||||
#include <sys/sendfile.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../core/debuginfo.hpp"
|
||||
#include "../core/instanceinfo.hpp"
|
||||
#include "../core/logcat.hpp"
|
||||
#include "../core/logging.hpp"
|
||||
#include "../core/logging_p.hpp"
|
||||
#include "../core/paths.hpp"
|
||||
#include "../core/ringbuf.hpp"
|
||||
#include "build.hpp"
|
||||
#include "interface.hpp"
|
||||
|
||||
namespace {
|
||||
@@ -169,15 +171,41 @@ void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instance) {
|
||||
qCCritical(logCrashReporter) << "Failed to open crash info file for writing.";
|
||||
} else {
|
||||
auto stream = QTextStream(&extraInfoFile);
|
||||
stream << qs::debuginfo::combinedInfo();
|
||||
stream << "===== Build Information =====\n";
|
||||
stream << "Git Revision: " << GIT_REVISION << '\n';
|
||||
stream << "Buildtime Qt Version: " << QT_VERSION_STR << "\n";
|
||||
stream << "Build Type: " << BUILD_TYPE << '\n';
|
||||
stream << "Compiler: " << COMPILER << '\n';
|
||||
stream << "Complie Flags: " << COMPILE_FLAGS << "\n\n";
|
||||
stream << "Build configuration:\n" << BUILD_CONFIGURATION << "\n";
|
||||
|
||||
stream << "\n===== Instance Information =====\n";
|
||||
stream << "\n===== Runtime Information =====\n";
|
||||
stream << "Runtime Qt Version: " << qVersion() << '\n';
|
||||
stream << "Signal: " << strsignal(crashSignal) << " (" << crashSignal << ")\n"; // NOLINT
|
||||
stream << "Crashed process ID: " << crashProc << '\n';
|
||||
stream << "Run ID: " << instance.instanceId << '\n';
|
||||
stream << "Shell ID: " << instance.shellId << '\n';
|
||||
stream << "Config Path: " << instance.configPath << '\n';
|
||||
|
||||
stream << "\n===== System Information =====\n\n";
|
||||
stream << "/etc/os-release:";
|
||||
auto osReleaseFile = QFile("/etc/os-release");
|
||||
if (osReleaseFile.open(QFile::ReadOnly)) {
|
||||
stream << '\n' << osReleaseFile.readAll() << '\n';
|
||||
osReleaseFile.close();
|
||||
} else {
|
||||
stream << "FAILED TO OPEN\n";
|
||||
}
|
||||
|
||||
stream << "/etc/lsb-release:";
|
||||
auto lsbReleaseFile = QFile("/etc/lsb-release");
|
||||
if (lsbReleaseFile.open(QFile::ReadOnly)) {
|
||||
stream << '\n' << lsbReleaseFile.readAll();
|
||||
lsbReleaseFile.close();
|
||||
} else {
|
||||
stream << "FAILED TO OPEN\n";
|
||||
}
|
||||
|
||||
stream << "\n===== Stacktrace =====\n";
|
||||
if (stacktrace.empty()) {
|
||||
stream << "(no trace available)\n";
|
||||
|
||||
+1
-4
@@ -3,7 +3,6 @@
|
||||
#include <variant>
|
||||
|
||||
#include <qbuffer.h>
|
||||
#include <qcoreapplication.h>
|
||||
#include <qlocalserver.h>
|
||||
#include <qlocalsocket.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*/) {
|
||||
qInfo() << "Exiting due to IPC request.";
|
||||
auto* generation = EngineGeneration::currentGeneration();
|
||||
if (generation) generation->quit();
|
||||
else QCoreApplication::exit(0);
|
||||
EngineGeneration::currentGeneration()->quit();
|
||||
}
|
||||
|
||||
} // namespace qs::ipc
|
||||
+15
-5
@@ -25,12 +25,12 @@
|
||||
#include <qtversion.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../core/debuginfo.hpp"
|
||||
#include "../core/instanceinfo.hpp"
|
||||
#include "../core/logging.hpp"
|
||||
#include "../core/paths.hpp"
|
||||
#include "../io/ipccomm.hpp"
|
||||
#include "../ipc/ipc.hpp"
|
||||
#include "build.hpp"
|
||||
#include "launch_p.hpp"
|
||||
|
||||
namespace qs::launch {
|
||||
@@ -519,10 +519,20 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
|
||||
}
|
||||
|
||||
if (state.misc.printVersion) {
|
||||
if (state.log.verbosity == 0) {
|
||||
qCInfo(logBare).noquote() << "Quickshell" << qs::debuginfo::qsVersion();
|
||||
} else {
|
||||
qCInfo(logBare).noquote() << qs::debuginfo::combinedInfo();
|
||||
qCInfo(logBare).noquote().nospace() << "quickshell " << QS_VERSION << ", revision "
|
||||
<< GIT_REVISION << ", distributed by: " << DISTRIBUTOR;
|
||||
|
||||
if (state.log.verbosity > 1) {
|
||||
qCInfo(logBare).noquote() << "\nBuildtime Qt Version:" << QT_VERSION_STR;
|
||||
qCInfo(logBare).noquote() << "Runtime Qt Version:" << qVersion();
|
||||
qCInfo(logBare).noquote() << "Compiler:" << COMPILER;
|
||||
qCInfo(logBare).noquote() << "Compile Flags:" << COMPILE_FLAGS;
|
||||
}
|
||||
|
||||
if (state.log.verbosity > 0) {
|
||||
qCInfo(logBare).noquote() << "\nBuild Type:" << BUILD_TYPE;
|
||||
qCInfo(logBare).noquote() << "Build configuration:";
|
||||
qCInfo(logBare).noquote().nospace() << BUILD_CONFIGURATION;
|
||||
}
|
||||
} else if (*state.subcommand.log) {
|
||||
return readLogFile(state);
|
||||
|
||||
@@ -138,11 +138,9 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
|
||||
};
|
||||
|
||||
#if CRASH_HANDLER
|
||||
if (qEnvironmentVariableIsSet("QS_DISABLE_CRASH_HANDLER")) {
|
||||
qInfo() << "Crash handling disabled.";
|
||||
} else {
|
||||
crash::CrashHandler::init();
|
||||
crash::CrashHandler::init();
|
||||
|
||||
{
|
||||
auto* log = LogManager::instance();
|
||||
crash::CrashHandler::setRelaunchInfo({
|
||||
.instance = InstanceInfo::CURRENT,
|
||||
|
||||
@@ -12,7 +12,7 @@ qt_add_qml_module(quickshell-service-greetd
|
||||
install_qml_module(quickshell-service-greetd)
|
||||
|
||||
# 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)
|
||||
|
||||
|
||||
@@ -145,7 +145,6 @@ void GreetdConnection::setInactive() {
|
||||
QString GreetdConnection::user() const { return this->mUser; }
|
||||
|
||||
void GreetdConnection::onSocketConnected() {
|
||||
this->reader.setDevice(&this->socket);
|
||||
qCDebug(logGreetd) << "Connected to greetd socket.";
|
||||
|
||||
if (this->mTargetActive) {
|
||||
@@ -161,84 +160,82 @@ void GreetdConnection::onSocketError(QLocalSocket::LocalSocketError error) {
|
||||
}
|
||||
|
||||
void GreetdConnection::onSocketReady() {
|
||||
while (true) {
|
||||
this->reader.startTransaction();
|
||||
auto length = this->reader.readI32();
|
||||
auto text = this->reader.readBytes(length);
|
||||
if (!this->reader.commitTransaction()) return;
|
||||
qint32 length = 0;
|
||||
|
||||
auto json = QJsonDocument::fromJson(text).object();
|
||||
auto type = json.value("type").toString();
|
||||
this->socket.read(reinterpret_cast<char*>(&length), sizeof(qint32));
|
||||
|
||||
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") {
|
||||
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();
|
||||
qCDebug(logGreetd).noquote() << "Received greetd response:" << text;
|
||||
|
||||
if (this->mExitAfterLaunch) {
|
||||
qCDebug(logGreetd) << "Quitting.";
|
||||
EngineGeneration::currentGeneration()->quit();
|
||||
}
|
||||
if (type == "success") {
|
||||
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();
|
||||
|
||||
break;
|
||||
default: goto unexpected;
|
||||
}
|
||||
} 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 (this->mExitAfterLaunch) {
|
||||
qCDebug(logGreetd) << "Quitting.";
|
||||
EngineGeneration::currentGeneration()->quit();
|
||||
}
|
||||
|
||||
if (errorType == "auth_error") {
|
||||
emit this->authFailure(desc);
|
||||
this->setActive(false);
|
||||
} else if (errorType == "error") {
|
||||
qCWarning(logGreetd) << "Greetd error occurred" << desc;
|
||||
emit this->error(desc);
|
||||
} else goto unexpected;
|
||||
break;
|
||||
default: goto unexpected;
|
||||
}
|
||||
} else if (type == "error") {
|
||||
auto errorType = json.value("error_type").toString();
|
||||
auto desc = json.value("description").toString();
|
||||
|
||||
// errors terminate the session
|
||||
this->setInactive();
|
||||
} else if (type == "auth_message") {
|
||||
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";
|
||||
// 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;
|
||||
}
|
||||
|
||||
this->mResponseRequired = responseRequired;
|
||||
emit this->authMessage(message, error, responseRequired, echoResponse);
|
||||
|
||||
if (!responseRequired) {
|
||||
this->sendRequest({{"type", "post_auth_message_response"}});
|
||||
}
|
||||
if (errorType == "auth_error") {
|
||||
emit this->authFailure(desc);
|
||||
this->setActive(false);
|
||||
} else if (errorType == "error") {
|
||||
qCWarning(logGreetd) << "Greetd error occurred" << desc;
|
||||
emit this->error(desc);
|
||||
} else goto unexpected;
|
||||
|
||||
continue;
|
||||
unexpected:
|
||||
qCCritical(logGreetd) << "Received unexpected greetd response" << text;
|
||||
this->setActive(false);
|
||||
}
|
||||
// errors terminate the session
|
||||
this->setInactive();
|
||||
} else if (type == "auth_message") {
|
||||
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) {
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../core/streamreader.hpp"
|
||||
|
||||
///! State of the Greetd connection.
|
||||
/// See @@Greetd.state.
|
||||
class GreetdState: public QObject {
|
||||
@@ -76,5 +74,4 @@ private:
|
||||
bool mResponseRequired = false;
|
||||
QString mUser;
|
||||
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) {
|
||||
auto* self = static_cast<PwCore*>(data);
|
||||
|
||||
// Pipewire's documentation describes the error event as being fatal, however it isn't.
|
||||
// We're not sure what causes these ENOENTs on device removal, presumably something in
|
||||
// the teardown sequence, but they're harmless. Attempting to handle them as a fatal
|
||||
// error causes unnecessary triggers for shells.
|
||||
if (res == -ENOENT) {
|
||||
qCDebug(logLoop) << "Pipewire ENOENT on object" << id << "with code" << res << message;
|
||||
return;
|
||||
if (message != nullptr) {
|
||||
qCWarning(logLoop) << "Fatal pipewire error on object" << id << "with code" << res << message;
|
||||
} else {
|
||||
qCWarning(logLoop) << "Fatal pipewire error on object" << id << "with code" << res;
|
||||
}
|
||||
|
||||
qCWarning(logLoop) << "Pipewire error on object" << id << "with code" << res << message;
|
||||
|
||||
emit self->fatalError();
|
||||
}
|
||||
|
||||
|
||||
@@ -123,8 +123,6 @@ list(APPEND WAYLAND_MODULES Quickshell.Wayland._IdleNotify)
|
||||
add_subdirectory(shortcuts_inhibit)
|
||||
list(APPEND WAYLAND_MODULES Quickshell.Wayland._ShortcutsInhibitor)
|
||||
|
||||
add_subdirectory(windowmanager)
|
||||
|
||||
# widgets for qmenu
|
||||
target_link_libraries(quickshell-wayland PRIVATE
|
||||
Qt::Quick Qt::Widgets Qt::WaylandClient Qt::WaylandClientPrivate
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
#include <qscopedpointer.h>
|
||||
#include <qsgrendererinterface.h>
|
||||
#include <qsgtexture_platform.h>
|
||||
#include <qtypes.h>
|
||||
#include <qvulkanfunctions.h>
|
||||
#include <qvulkaninstance.h>
|
||||
#include <qwayland-linux-dmabuf-v1.h>
|
||||
@@ -36,6 +35,7 @@
|
||||
#include <sys/mman.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <qtypes.h>
|
||||
#include <vulkan/vulkan_core.h>
|
||||
#include <wayland-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_ARGB2101010:
|
||||
case DRM_FORMAT_ABGR2101010:
|
||||
case DRM_FORMAT_ABGR16161616F: return true;
|
||||
default: return false;
|
||||
case DRM_FORMAT_ABGR16161616F:
|
||||
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
|
||||
// takes ownership of the fd on succcess. Without dup, WlDmaBuffer would double-close.
|
||||
const int dupFd =
|
||||
dup(this->planes[0].fd); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
|
||||
const int dupFd = dup(this->planes[0].fd); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
|
||||
if (dupFd < 0) {
|
||||
qCWarning(logDmabuf) << "Failed to dup() fd for DMA-BUF import";
|
||||
goto cleanup_fail; // NOLINT
|
||||
@@ -908,12 +909,12 @@ WlBufferQSGTexture* WlDmaBuffer::createQsgTextureVulkan(QQuickWindow* window) co
|
||||
// find the graphics queue family index for the ownrship transfer.
|
||||
uint32_t graphicsQueueFamily = 0;
|
||||
uint32_t queueFamilyCount = 0;
|
||||
instFuncs->vkGetPhysicalDeviceQueueFamilyProperties(physDevice, &queueFamilyCount, nullptr);
|
||||
instFuncs->vkGetPhysicalDeviceQueueFamilyProperties(
|
||||
physDevice, &queueFamilyCount, nullptr
|
||||
);
|
||||
std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
|
||||
instFuncs->vkGetPhysicalDeviceQueueFamilyProperties(
|
||||
physDevice,
|
||||
&queueFamilyCount,
|
||||
queueFamilies.data()
|
||||
physDevice, &queueFamilyCount, queueFamilies.data()
|
||||
);
|
||||
for (uint32_t i = 0; i < queueFamilyCount; ++i) {
|
||||
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;
|
||||
return tex;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ qs_add_module_deps_light(quickshell-hyprland-ipc Quickshell)
|
||||
|
||||
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)
|
||||
target_sources(quickshell-hyprland-ipc PRIVATE
|
||||
|
||||
@@ -93,7 +93,6 @@ void HyprlandIpc::eventSocketError(QLocalSocket::LocalSocketError error) const {
|
||||
|
||||
void HyprlandIpc::eventSocketStateChanged(QLocalSocket::LocalSocketState state) {
|
||||
if (state == QLocalSocket::ConnectedState) {
|
||||
this->eventReader.setDevice(&this->eventSocket);
|
||||
qCInfo(logHyprlandIpc) << "Hyprland event socket connected.";
|
||||
emit this->connected();
|
||||
} else if (state == QLocalSocket::UnconnectedState && this->valid) {
|
||||
@@ -105,11 +104,11 @@ void HyprlandIpc::eventSocketStateChanged(QLocalSocket::LocalSocketState state)
|
||||
|
||||
void HyprlandIpc::eventSocketReady() {
|
||||
while (true) {
|
||||
this->eventReader.startTransaction();
|
||||
auto rawEvent = this->eventReader.readUntil('\n');
|
||||
if (!this->eventReader.commitTransaction()) return;
|
||||
auto rawEvent = this->eventSocket.readLine();
|
||||
if (rawEvent.isEmpty()) break;
|
||||
|
||||
rawEvent.chop(1); // remove trailing \n
|
||||
// remove trailing \n
|
||||
rawEvent.truncate(rawEvent.length() - 1);
|
||||
auto splitIdx = rawEvent.indexOf(">>");
|
||||
auto event = QByteArrayView(rawEvent.data(), splitIdx);
|
||||
auto data = QByteArrayView(
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
#include "../../../core/model.hpp"
|
||||
#include "../../../core/qmlscreen.hpp"
|
||||
#include "../../../core/streamreader.hpp"
|
||||
#include "../../../wayland/toplevel_management/handle.hpp"
|
||||
|
||||
namespace qs::hyprland::ipc {
|
||||
@@ -140,7 +139,6 @@ private:
|
||||
static bool compareWorkspaces(HyprlandWorkspace* a, HyprlandWorkspace* b);
|
||||
|
||||
QLocalSocket eventSocket;
|
||||
StreamReader eventReader;
|
||||
QString mRequestSocketPath;
|
||||
QString mEventSocketPath;
|
||||
bool valid = false;
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include <qqmlcomponent.h>
|
||||
#include <qqmlengine.h>
|
||||
#include <qqmllist.h>
|
||||
#include <qquickgraphicsconfiguration.h>
|
||||
#include <qquickitem.h>
|
||||
#include <qquickwindow.h>
|
||||
#include <qscreen.h>
|
||||
@@ -217,15 +216,6 @@ void WlSessionLockSurface::onReload(QObject* oldInstance) {
|
||||
|
||||
if (this->window == nullptr) {
|
||||
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());
|
||||
|
||||
@@ -161,11 +161,7 @@ void ToplevelManager::onToplevelReady(impl::ToplevelHandle* handle) {
|
||||
|
||||
void ToplevelManager::onToplevelActiveChanged() {
|
||||
auto* toplevel = qobject_cast<Toplevel*>(this->sender());
|
||||
if (toplevel->activated()) {
|
||||
this->setActiveToplevel(toplevel);
|
||||
} else if (toplevel == this->mActiveToplevel) {
|
||||
this->setActiveToplevel(nullptr);
|
||||
}
|
||||
if (toplevel->activated()) this->setActiveToplevel(toplevel);
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <qbytearray.h>
|
||||
#include <qbytearrayview.h>
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdatastream.h>
|
||||
#include <qjsonarray.h>
|
||||
#include <qjsondocument.h>
|
||||
#include <qjsonobject.h>
|
||||
@@ -14,7 +15,9 @@
|
||||
#include <qlocalsocket.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qnamespace.h>
|
||||
#include <qobject.h>
|
||||
#include <qsysinfo.h>
|
||||
#include <qtenvironmentvariables.h>
|
||||
#include <qtmetamacros.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::connected, this, &I3Ipc::subscribe);
|
||||
// clang-format on
|
||||
|
||||
this->liveEventSocketDs.setDevice(&this->liveEventSocket);
|
||||
this->liveEventSocketDs.setByteOrder(static_cast<QDataStream::ByteOrder>(QSysInfo::ByteOrder));
|
||||
}
|
||||
|
||||
void I3Ipc::makeRequest(const QByteArray& request) {
|
||||
@@ -139,21 +145,34 @@ void I3Ipc::reconnectIPC() {
|
||||
}
|
||||
|
||||
QVector<Event> I3Ipc::parseResponse() {
|
||||
QVector<Event> events;
|
||||
QVector<std::tuple<EventCode, QJsonDocument>> events;
|
||||
const int magicLen = 6;
|
||||
|
||||
while (true) {
|
||||
this->eventReader.startTransaction();
|
||||
auto magic = this->eventReader.readBytes(6);
|
||||
auto size = this->eventReader.readI32();
|
||||
auto type = this->eventReader.readI32();
|
||||
auto payload = this->eventReader.readBytes(size);
|
||||
if (!this->eventReader.commitTransaction()) return events;
|
||||
while (!this->liveEventSocketDs.atEnd()) {
|
||||
this->liveEventSocketDs.startTransaction();
|
||||
this->liveEventSocketDs.startTransaction();
|
||||
|
||||
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.";
|
||||
this->reconnectIPC();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if (I3IpcEvent::intToEvent(type) == EventCode::Unknown) {
|
||||
qCWarning(logI3Ipc) << "Received unknown event";
|
||||
@@ -185,7 +204,6 @@ void I3Ipc::eventSocketError(QLocalSocket::LocalSocketError error) const {
|
||||
|
||||
void I3Ipc::eventSocketStateChanged(QLocalSocket::LocalSocketState state) {
|
||||
if (state == QLocalSocket::ConnectedState) {
|
||||
this->eventReader.setDevice(&this->liveEventSocket);
|
||||
qCInfo(logI3Ipc) << "I3 event socket connected.";
|
||||
emit this->connected();
|
||||
} else if (state == QLocalSocket::UnconnectedState && this->valid) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <qbytearrayview.h>
|
||||
#include <qdatastream.h>
|
||||
#include <qjsondocument.h>
|
||||
#include <qlocalsocket.h>
|
||||
#include <qobject.h>
|
||||
@@ -8,8 +9,6 @@
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../../core/streamreader.hpp"
|
||||
|
||||
namespace qs::i3::ipc {
|
||||
|
||||
constexpr std::string MAGIC = "i3-ipc";
|
||||
@@ -93,7 +92,7 @@ protected:
|
||||
QVector<std::tuple<EventCode, QJsonDocument>> parseResponse();
|
||||
|
||||
QLocalSocket liveEventSocket;
|
||||
StreamReader eventReader;
|
||||
QDataStream liveEventSocketDs;
|
||||
|
||||
QString mSocketPath;
|
||||
bool valid = false;
|
||||
|
||||
Reference in new issue
Block a user