mirror of
https://git.outfoxxed.me/quickshell/quickshell.git
synced 2026-04-10 06:11:54 +10:00
Compare commits
11 commits
e2b0f8705e
...
6705e2da77
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6705e2da77 | ||
|
|
9e8eecf2b8 | ||
|
|
1b2519d9f3 | ||
|
|
1123d5ab4f | ||
|
|
4b77936c80 | ||
|
|
e32b909354 | ||
|
|
178c04b59c | ||
|
|
706d6de7b0 | ||
|
|
9a9c605250 | ||
|
|
bd62179277 | ||
|
|
cf1a2aeb2d |
58 changed files with 1661 additions and 992 deletions
|
|
@ -20,6 +20,7 @@ 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,
|
||||
|
|
|
|||
14
BUILD.md
14
BUILD.md
|
|
@ -15,7 +15,7 @@ Please make this descriptive enough to identify your specific package, for examp
|
|||
- `Nixpkgs`
|
||||
- `Fedora COPR (errornointernet/quickshell)`
|
||||
|
||||
Please leave at least symbol names attached to the binary for debugging purposes.
|
||||
If you are forking quickshell, please change `CRASHREPORT_URL` to your own issue tracker.
|
||||
|
||||
### QML Module dir
|
||||
Currently all QML modules are statically linked to quickshell, but this is where
|
||||
|
|
@ -33,6 +33,7 @@ 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,7 +68,13 @@ 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.
|
||||
When using FetchContent, `libunwind` is required, and `libdwarf` can be provided by the
|
||||
package manager or fetched with FetchContent.
|
||||
|
||||
*Please ensure binaries have usable symbols.* We do not necessarily need full debuginfo, but
|
||||
leaving symbols in the binary is extremely helpful. You can check if symbols are useful
|
||||
by sending a SIGSEGV to the process and ensuring symbols for the quickshell binary are present
|
||||
in the trace.
|
||||
|
||||
### Jemalloc
|
||||
We recommend leaving Jemalloc enabled as it will mask memory fragmentation caused
|
||||
|
|
@ -140,7 +147,6 @@ Enables streaming video from monitors and toplevel windows through various proto
|
|||
To disable: `-DSCREENCOPY=OFF`
|
||||
|
||||
Dependencies:
|
||||
- `libdrm`
|
||||
- `libgbm`
|
||||
- `vulkan-headers` (build-time)
|
||||
|
||||
|
|
@ -236,7 +242,7 @@ Only `ninja` builds are tested, but makefiles may work.
|
|||
|
||||
#### Configuring the build
|
||||
```sh
|
||||
$ cmake -GNinja -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo [additional disable flags from above here]
|
||||
$ cmake -GNinja -B build -DCMAKE_BUILD_TYPE=Release [additional disable flags from above here]
|
||||
```
|
||||
|
||||
Note that features you do not supply dependencies for MUST be disabled with their associated flags
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ 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" "")
|
||||
|
||||
|
|
|
|||
247
CONTRIBUTING.md
247
CONTRIBUTING.md
|
|
@ -1,235 +1,40 @@
|
|||
# Contributing / Development
|
||||
Instructions for development setup and upstreaming patches.
|
||||
# Contributing
|
||||
|
||||
If you just want to build or package quickshell see [BUILD.md](BUILD.md).
|
||||
Thank you for taking the time to contribute.
|
||||
To ensure nobody's time is wasted, please follow the rules below.
|
||||
|
||||
## Development
|
||||
## Acceptable Code Contributions
|
||||
|
||||
Install the dependencies listed in [BUILD.md](BUILD.md).
|
||||
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.
|
||||
- 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.
|
||||
|
||||
Quickshell also uses `just` for common development command aliases.
|
||||
- Changes MUST be submitted by a human who will be responsible for them. Changes submitted without
|
||||
a human in the loop such as automated tooling and AI Agents are **strictly disallowed**. Accounts
|
||||
responsible for such contribution attempts **will be banned**.
|
||||
|
||||
The dependencies are also available as a nix shell or nix flake which we recommend
|
||||
using with nix-direnv.
|
||||
- Changes MUST respect Quickshell's license and the license of any source works. Changes including
|
||||
code from any other works must disclose the source of the code, explain why it was used, and
|
||||
ensure the license is compatible.
|
||||
|
||||
Common aliases:
|
||||
- `just configure [<debug|release> [extra cmake args]]` (note that you must specify debug/release to specify extra args)
|
||||
- `just build` - runs the build, configuring if not configured already.
|
||||
- `just run [args]` - runs quickshell with the given arguments
|
||||
- `just clean` - clean up build artifacts. `just clean build` is somewhat common.
|
||||
- Changes must follow the guidelines outlined in [HACKING.md](HACKING.md) for style and substance.
|
||||
|
||||
### Formatting
|
||||
All contributions should be formatted similarly to what already exists.
|
||||
Group related functionality together.
|
||||
- Changes must stand on their own as a unit. Do not make multiple unrelated changes in one PR.
|
||||
Changes depending on prior merges should be marked as a draft.
|
||||
|
||||
Run the formatter using `just fmt`.
|
||||
If the results look stupid, fix the clang-format file if possible,
|
||||
or disable clang-format in the affected area
|
||||
using `// clang-format off` and `// clang-format on`.
|
||||
## Acceptable Non-code Contributions
|
||||
|
||||
#### Style preferences not caught by clang-format
|
||||
These are flexible. You can ignore them if it looks or works better to
|
||||
for one reason or another.
|
||||
- Bug and crash reports. You must follow the instructions in the issue templates and provide the
|
||||
information requested.
|
||||
|
||||
Use `auto` if the type of a variable can be deduced automatically, instead of
|
||||
redeclaring the returned value's type. Additionally, auto should be used when a
|
||||
constructor takes arguments.
|
||||
- Feature requests can be made via Issues. Please check to ensure nobody else has requested the same feature.
|
||||
|
||||
```cpp
|
||||
auto x = <expr>; // ok
|
||||
auto x = QString::number(3); // ok
|
||||
QString x; // ok
|
||||
QString x = "foo"; // ok
|
||||
auto x = QString("foo"); // ok
|
||||
- Do not make insubstantial or pointless changes.
|
||||
|
||||
auto x = QString(); // avoid
|
||||
QString x(); // avoid
|
||||
QString x("foo"); // avoid
|
||||
```
|
||||
- Changes to project rules / policy / governance will not be entertained, except from significant
|
||||
long-term contributors. These changes should not be addressed through contribution channels.
|
||||
|
||||
Put newlines around logical units of code, and after closing braces. If the
|
||||
most reasonable logical unit of code takes only a single line, it should be
|
||||
merged into the next single line logical unit if applicable.
|
||||
```cpp
|
||||
// multiple units
|
||||
auto x = <expr>; // unit 1
|
||||
auto y = <expr>; // unit 2
|
||||
## Merge timelines
|
||||
|
||||
auto x = <expr>; // unit 1
|
||||
emit this->y(); // unit 2
|
||||
|
||||
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 `outfoxxed@outfoxxed.me`.
|
||||
|
||||
### 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.
|
||||
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.
|
||||
|
|
|
|||
226
HACKING.md
Normal file
226
HACKING.md
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
## Development
|
||||
|
||||
Install the dependencies listed in [BUILD.md](BUILD.md).
|
||||
You probably want all of them even if you don't use all of them
|
||||
to ensure tests work correctly and avoid passing a bunch of configure
|
||||
flags when you need to wipe the build directory.
|
||||
|
||||
The dependencies are also available as a nix shell or nix flake which we recommend
|
||||
using with nix-direnv.
|
||||
|
||||
Quickshell uses `just` for common development command aliases.
|
||||
|
||||
Common aliases:
|
||||
- `just configure [<debug|release> [extra cmake args]]` (note that you must specify debug/release to specify extra args)
|
||||
- `just build` - runs the build, configuring if not configured already.
|
||||
- `just run [args]` - runs quickshell with the given arguments
|
||||
- `just clean` - clean up build artifacts. `just clean build` is somewhat common.
|
||||
|
||||
### Formatting
|
||||
All contributions should be formatted similarly to what already exists.
|
||||
Group related functionality together.
|
||||
|
||||
Run the formatter using `just fmt`.
|
||||
If the results look stupid, fix the clang-format file if possible,
|
||||
or disable clang-format in the affected area
|
||||
using `// clang-format off` and `// clang-format on`.
|
||||
|
||||
#### Style preferences not caught by clang-format
|
||||
These are flexible. You can ignore them if it looks or works better to
|
||||
for one reason or another.
|
||||
|
||||
Use `auto` if the type of a variable can be deduced automatically, instead of
|
||||
redeclaring the returned value's type. Additionally, auto should be used when a
|
||||
constructor takes arguments.
|
||||
|
||||
```cpp
|
||||
auto x = <expr>; // ok
|
||||
auto x = QString::number(3); // ok
|
||||
QString x; // ok
|
||||
QString x = "foo"; // ok
|
||||
auto x = QString("foo"); // ok
|
||||
|
||||
auto x = QString(); // avoid
|
||||
QString x(); // avoid
|
||||
QString x("foo"); // avoid
|
||||
```
|
||||
|
||||
Put newlines around logical units of code, and after closing braces. If the
|
||||
most reasonable logical unit of code takes only a single line, it should be
|
||||
merged into the next single line logical unit if applicable.
|
||||
```cpp
|
||||
// multiple units
|
||||
auto x = <expr>; // unit 1
|
||||
auto y = <expr>; // unit 2
|
||||
|
||||
auto x = <expr>; // unit 1
|
||||
emit this->y(); // unit 2
|
||||
|
||||
auto x1 = <expr>; // unit 1
|
||||
auto x2 = <expr>; // unit 1
|
||||
auto x3 = <expr>; // unit 1
|
||||
|
||||
auto y1 = <expr>; // unit 2
|
||||
auto y2 = <expr>; // unit 2
|
||||
auto y3 = <expr>; // unit 2
|
||||
|
||||
// one unit
|
||||
auto x = <expr>;
|
||||
if (x...) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// if more than one variable needs to be used then add a newline
|
||||
auto x = <expr>;
|
||||
auto y = <expr>;
|
||||
|
||||
if (x && y) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Class formatting:
|
||||
```cpp
|
||||
//! Doc comment summary
|
||||
/// Doc comment body
|
||||
class Foo: public QObject {
|
||||
// The Q_OBJECT macro comes first. Macros are ; terminated.
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_CLASSINFO(...);
|
||||
// Properties must stay on a single line or the doc generator won't be able to pick them up
|
||||
Q_PROPERTY(...);
|
||||
/// Doc comment
|
||||
Q_PROPERTY(...);
|
||||
/// Doc comment
|
||||
Q_PROPERTY(...);
|
||||
|
||||
public:
|
||||
// Classes should have explicit constructors if they aren't intended to
|
||||
// implicitly cast. The constructor can be inline in the header if it has no body.
|
||||
explicit Foo(QObject* parent = nullptr): QObject(parent) {}
|
||||
|
||||
// Instance functions if applicable.
|
||||
static Foo* instance();
|
||||
|
||||
// Member functions unrelated to properties come next
|
||||
void function();
|
||||
void function();
|
||||
void function();
|
||||
|
||||
// Then Q_INVOKABLEs
|
||||
Q_INVOKABLE function();
|
||||
/// Doc comment
|
||||
Q_INVOKABLE function();
|
||||
/// Doc comment
|
||||
Q_INVOKABLE function();
|
||||
|
||||
// Then property related functions, in the order (bindable, getter, setter).
|
||||
// Related functions may be included here as well. Function bodies may be inline
|
||||
// if they are a single expression. There should be a newline between each
|
||||
// property's methods.
|
||||
[[nodiscard]] QBindable<T> bindableFoo() { return &this->bFoo; }
|
||||
[[nodiscard]] T foo() const { return this->foo; }
|
||||
void setFoo();
|
||||
|
||||
[[nodiscard]] T bar() const { return this->foo; }
|
||||
void setBar();
|
||||
|
||||
signals:
|
||||
// Signals that are not property change related go first.
|
||||
// Property change signals go in property definition order.
|
||||
void asd();
|
||||
void asd2();
|
||||
void fooChanged();
|
||||
void barChanged();
|
||||
|
||||
public slots:
|
||||
// generally Q_INVOKABLEs are preferred to public slots.
|
||||
void slot();
|
||||
|
||||
private slots:
|
||||
// ...
|
||||
|
||||
private:
|
||||
// statics, then functions, then fields
|
||||
static const foo BAR;
|
||||
static void foo();
|
||||
|
||||
void foo();
|
||||
void bar();
|
||||
|
||||
// property related members are prefixed with `m`.
|
||||
QString mFoo;
|
||||
QString bar;
|
||||
|
||||
// Bindables go last and should be prefixed with `b`.
|
||||
Q_OBJECT_BINDABLE_PROPERTY(Foo, QString, bFoo, &Foo::fooChanged);
|
||||
};
|
||||
```
|
||||
|
||||
Use lowercase .h suffixed Qt headers, e.g. `<qwindow.h>` over `<QWindow>`.
|
||||
|
||||
### Linter
|
||||
All contributions should pass the linter.
|
||||
|
||||
Note that running the linter requires disabling precompiled
|
||||
headers and including the test codepaths:
|
||||
```sh
|
||||
$ just configure debug -DNO_PCH=ON -DBUILD_TESTING=ON
|
||||
$ just lint-changed
|
||||
```
|
||||
|
||||
If the linter is complaining about something that you think it should not,
|
||||
please disable the lint in your MR and explain your reasoning if it isn't obvious.
|
||||
|
||||
### Tests
|
||||
If you feel like the feature you are working on is very complex or likely to break,
|
||||
please write some tests. We will ask you to directly if you send in an MR for an
|
||||
overly complex or breakable feature.
|
||||
|
||||
At least all tests that passed before your changes should still be passing
|
||||
by the time your contribution is ready.
|
||||
|
||||
You can run the tests using `just test` but you must enable them first
|
||||
using `-DBUILD_TESTING=ON`.
|
||||
|
||||
### Documentation
|
||||
Most of quickshell's documentation is automatically generated from the source code.
|
||||
You should annotate `Q_PROPERTY`s and `Q_INVOKABLE`s with doc comments. Note that the parser
|
||||
cannot handle random line breaks and will usually require you to disable clang-format if the
|
||||
lines are too long.
|
||||
|
||||
Make sure new files containing doc comments are added to a `module.md` file.
|
||||
See existing module files for reference.
|
||||
|
||||
Doc comments take the form `///` or `///!` (summary) and work with markdown.
|
||||
You can reference other types using the `@@[Module.][Type.][member]` shorthand
|
||||
where all parts are optional. If module or type are not specified they will
|
||||
be inferred as the current module. Member can be a `property`, `function()` or `signal(s)`.
|
||||
Look at existing code for how it works.
|
||||
|
||||
If you have made a user visible change since the last tagged release, describe it in
|
||||
[changelog/next.md](changelog/next.md).
|
||||
|
||||
## Contributing
|
||||
|
||||
### Commits
|
||||
Please structure your commit messages as `scope: commit` where
|
||||
the scope is something like `core` or `service/mpris`. (pick what has been
|
||||
used historically or what makes sense if new).
|
||||
|
||||
Commit descriptions should contain a summary of the changes if they are not
|
||||
sufficiently addressed in the commit message.
|
||||
|
||||
Please squash/rebase additions or edits to previous changes and follow the
|
||||
commit style to keep the history easily searchable at a glance.
|
||||
Depending on the change, it is often reasonable to squash it into just
|
||||
a single commit. (If you do not follow this we will squash your changes
|
||||
for you.)
|
||||
|
||||
### Getting help
|
||||
If you're getting stuck, you can come talk to us in the
|
||||
[quickshell-development matrix room](https://matrix.to/#/#quickshell-development:outfoxxed.me)
|
||||
for help on implementation, conventions, etc. There is also a bridged [discord server](https://discord.gg/UtZeT3xNyT).
|
||||
Feel free to ask for advice early in your implementation if you are
|
||||
unsure.
|
||||
|
|
@ -7,7 +7,9 @@ This repo is hosted at:
|
|||
- https://github.com/quickshell-mirror/quickshell
|
||||
|
||||
# Contributing / Development
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
||||
- [HACKING.md](HACKING.md) - Development instructions and policy.
|
||||
- [CONTRIBUTING.md](CONTRIBUTING.md) - Contribution policy.
|
||||
- [BUILD.md](BUILD.md) - Packaging and build instructions.
|
||||
|
||||
#### License
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ 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
|
||||
|
||||
|
|
@ -33,6 +34,10 @@ 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
|
||||
|
||||
|
|
@ -50,7 +55,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
|
||||
|
||||
|
|
@ -58,3 +65,4 @@ set shell id.
|
|||
- `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.
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@
|
|||
buildInputs = [
|
||||
qt6.qtbase
|
||||
qt6.qtdeclarative
|
||||
libdrm
|
||||
cli11
|
||||
]
|
||||
++ lib.optional withQtSvg qt6.qtsvg
|
||||
|
|
@ -88,7 +89,7 @@
|
|||
++ 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) [ libdrm libgbm vulkan-headers ]
|
||||
++ lib.optionals (withWayland && libgbm != null) [ libgbm vulkan-headers ]
|
||||
++ lib.optional withX11 libxcb
|
||||
++ lib.optional withPam pam
|
||||
++ lib.optional withPipewire pipewire
|
||||
|
|
|
|||
|
|
@ -13,4 +13,5 @@
|
|||
#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,3 +1,4 @@
|
|||
pkg_check_modules(libdrm REQUIRED IMPORTED_TARGET libdrm)
|
||||
qt_add_library(quickshell-core STATIC
|
||||
plugin.cpp
|
||||
shell.cpp
|
||||
|
|
@ -40,6 +41,8 @@ qt_add_library(quickshell-core STATIC
|
|||
scriptmodel.cpp
|
||||
colorquantizer.cpp
|
||||
toolsupport.cpp
|
||||
streamreader.cpp
|
||||
debuginfo.cpp
|
||||
)
|
||||
|
||||
qt_add_qml_module(quickshell-core
|
||||
|
|
@ -52,7 +55,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)
|
||||
target_link_libraries(quickshell-core PRIVATE Qt::Quick Qt::QuickPrivate Qt::Widgets quickshell-build PkgConfig::libdrm)
|
||||
|
||||
qs_module_pch(quickshell-core SET large)
|
||||
|
||||
|
|
|
|||
175
src/core/debuginfo.cpp
Normal file
175
src/core/debuginfo.cpp
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
#include "debuginfo.hpp"
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <string_view>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <qconfig.h>
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdebug.h>
|
||||
#include <qfile.h>
|
||||
#include <qfloat16.h>
|
||||
#include <qhashfunctions.h>
|
||||
#include <qscopeguard.h>
|
||||
#include <qtversion.h>
|
||||
#include <unistd.h>
|
||||
#include <xf86drm.h>
|
||||
|
||||
#include "build.hpp"
|
||||
|
||||
extern char** environ; // NOLINT
|
||||
|
||||
namespace qs::debuginfo {
|
||||
|
||||
QString qsVersion() {
|
||||
return QS_VERSION " (revision " GIT_REVISION ", distributed by " DISTRIBUTOR ")";
|
||||
}
|
||||
|
||||
QString qtVersion() { return qVersion() % QStringLiteral(" (built against " QT_VERSION_STR ")"); }
|
||||
|
||||
QString gpuInfo() {
|
||||
auto deviceCount = drmGetDevices2(0, nullptr, 0);
|
||||
if (deviceCount < 0) return "Failed to get DRM device count: " % QString::number(deviceCount);
|
||||
auto* devices = new drmDevicePtr[deviceCount];
|
||||
auto devicesArrayGuard = qScopeGuard([&] { delete[] devices; });
|
||||
auto r = drmGetDevices2(0, devices, deviceCount);
|
||||
if (deviceCount < 0) return "Failed to get DRM devices: " % QString::number(r);
|
||||
auto devicesGuard = qScopeGuard([&] {
|
||||
for (auto i = 0; i != deviceCount; ++i) drmFreeDevice(&devices[i]); // NOLINT
|
||||
});
|
||||
|
||||
QString info;
|
||||
auto stream = QTextStream(&info);
|
||||
|
||||
for (auto i = 0; i != deviceCount; ++i) {
|
||||
auto* device = devices[i]; // NOLINT
|
||||
|
||||
int deviceNodeType = -1;
|
||||
if (device->available_nodes & (1 << DRM_NODE_RENDER)) deviceNodeType = DRM_NODE_RENDER;
|
||||
else if (device->available_nodes & (1 << DRM_NODE_PRIMARY)) deviceNodeType = DRM_NODE_PRIMARY;
|
||||
|
||||
if (deviceNodeType == -1) continue;
|
||||
|
||||
auto* deviceNode = device->nodes[DRM_NODE_RENDER]; // NOLINT
|
||||
|
||||
auto driver = [&]() -> QString {
|
||||
auto fd = open(deviceNode, O_RDWR | O_CLOEXEC);
|
||||
if (fd == -1) return "<failed to open device node>";
|
||||
auto fdGuard = qScopeGuard([&] { close(fd); });
|
||||
auto* ver = drmGetVersion(fd);
|
||||
if (!ver) return "<drmGetVersion failed>";
|
||||
auto verGuard = qScopeGuard([&] { drmFreeVersion(ver); });
|
||||
|
||||
// clang-format off
|
||||
return QString(ver->name)
|
||||
% ' ' % QString::number(ver->version_major)
|
||||
% '.' % QString::number(ver->version_minor)
|
||||
% '.' % QString::number(ver->version_patchlevel)
|
||||
% " (" % ver->desc % ')';
|
||||
// clang-format on
|
||||
}();
|
||||
|
||||
QString product = "unknown";
|
||||
QString address = "unknown";
|
||||
|
||||
auto hex = [](int num, int pad) { return QString::number(num, 16).rightJustified(pad, '0'); };
|
||||
|
||||
switch (device->bustype) {
|
||||
case DRM_BUS_PCI: {
|
||||
auto* b = device->businfo.pci;
|
||||
auto* d = device->deviceinfo.pci;
|
||||
address = "PCI " % hex(b->bus, 2) % ':' % hex(b->dev, 2) % '.' % hex(b->func, 1);
|
||||
product = hex(d->vendor_id, 4) % ':' % hex(d->device_id, 4);
|
||||
} break;
|
||||
case DRM_BUS_USB: {
|
||||
auto* b = device->businfo.usb;
|
||||
auto* d = device->deviceinfo.usb;
|
||||
address = "USB " % QString::number(b->bus) % ':' % QString::number(b->dev);
|
||||
product = hex(d->vendor, 4) % ':' % hex(d->product, 4);
|
||||
} break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
stream << "GPU " << deviceNode << "\n Driver: " << driver << "\n Model: " << product
|
||||
<< "\n Address: " << address << '\n';
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
QString systemInfo() {
|
||||
QString info;
|
||||
auto stream = QTextStream(&info);
|
||||
|
||||
stream << gpuInfo() << '\n';
|
||||
|
||||
stream << "/etc/os-release:";
|
||||
auto osReleaseFile = QFile("/etc/os-release");
|
||||
if (osReleaseFile.open(QFile::ReadOnly)) {
|
||||
stream << '\n' << osReleaseFile.readAll() << '\n';
|
||||
osReleaseFile.close();
|
||||
} else {
|
||||
stream << "FAILED TO OPEN\n";
|
||||
}
|
||||
|
||||
stream << "/etc/lsb-release:";
|
||||
auto lsbReleaseFile = QFile("/etc/lsb-release");
|
||||
if (lsbReleaseFile.open(QFile::ReadOnly)) {
|
||||
stream << '\n' << lsbReleaseFile.readAll();
|
||||
lsbReleaseFile.close();
|
||||
} else {
|
||||
stream << "FAILED TO OPEN\n";
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
QString envInfo() {
|
||||
QString info;
|
||||
auto stream = QTextStream(&info);
|
||||
|
||||
for (auto** envp = environ; *envp != nullptr; ++envp) { // NOLINT
|
||||
auto prefixes = std::array<std::string_view, 5> {
|
||||
"QS_",
|
||||
"QT_",
|
||||
"QML_",
|
||||
"QML2_",
|
||||
"QSG_",
|
||||
};
|
||||
|
||||
for (const auto& prefix: prefixes) {
|
||||
if (strncmp(prefix.data(), *envp, prefix.length()) == 0) goto print;
|
||||
}
|
||||
continue;
|
||||
|
||||
print:
|
||||
stream << *envp << '\n';
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
QString combinedInfo() {
|
||||
QString info;
|
||||
auto stream = QTextStream(&info);
|
||||
|
||||
stream << "===== Version Information =====\n";
|
||||
stream << "Quickshell: " << qsVersion() << '\n';
|
||||
stream << "Qt: " << qtVersion() << '\n';
|
||||
|
||||
stream << "\n===== Build Information =====\n";
|
||||
stream << "Build Type: " << BUILD_TYPE << '\n';
|
||||
stream << "Compiler: " << COMPILER << '\n';
|
||||
stream << "Compile Flags: " << COMPILE_FLAGS << '\n';
|
||||
stream << "Configuration:\n" << BUILD_CONFIGURATION << '\n';
|
||||
|
||||
stream << "\n===== System Information =====\n";
|
||||
stream << systemInfo();
|
||||
|
||||
stream << "\n===== Environment (trimmed) =====\n";
|
||||
stream << envInfo();
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
} // namespace qs::debuginfo
|
||||
14
src/core/debuginfo.hpp
Normal file
14
src/core/debuginfo.hpp
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
|
||||
namespace qs::debuginfo {
|
||||
|
||||
QString qsVersion();
|
||||
QString qtVersion();
|
||||
QString gpuInfo();
|
||||
QString systemInfo();
|
||||
QString envInfo();
|
||||
QString combinedInfo();
|
||||
|
||||
} // namespace qs::debuginfo
|
||||
|
|
@ -209,6 +209,8 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -229,6 +231,11 @@ 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -237,6 +244,11 @@ 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,7 +60,9 @@ void QuickshellSettings::setWorkingDirectory(QString workingDirectory) { // NOLI
|
|||
emit this->workingDirectoryChanged();
|
||||
}
|
||||
|
||||
bool QuickshellSettings::watchFiles() const { return this->mWatchFiles; }
|
||||
bool QuickshellSettings::watchFiles() const {
|
||||
return this->mWatchFiles && qEnvironmentVariableIsEmpty("QS_DISABLE_FILE_WATCHER");
|
||||
}
|
||||
|
||||
void QuickshellSettings::setWatchFiles(bool watchFiles) {
|
||||
if (watchFiles == this->mWatchFiles) return;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <utility>
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qcryptographichash.h>
|
||||
#include <qdir.h>
|
||||
#include <qfileinfo.h>
|
||||
#include <qjsengine.h>
|
||||
|
|
@ -21,6 +22,25 @@
|
|||
|
||||
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);
|
||||
|
|
@ -109,13 +129,13 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
|
|||
|
||||
qCDebug(logQmlScanner) << "Scanning qml file" << path;
|
||||
|
||||
auto file = QFile(path);
|
||||
if (!file.open(QFile::ReadOnly | QFile::Text)) {
|
||||
QByteArray fileData;
|
||||
if (!this->readAndHashFile(path, fileData)) {
|
||||
qCWarning(logQmlScanner) << "Failed to open file" << path;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto stream = QTextStream(&file);
|
||||
auto stream = QTextStream(&fileData);
|
||||
auto imports = QVector<QString>();
|
||||
|
||||
bool inHeader = true;
|
||||
|
|
@ -219,8 +239,6 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
|
|||
postError("unclosed preprocessor if block");
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
if (isOverridden) {
|
||||
this->fileIntercepts.insert(path, overrideText);
|
||||
}
|
||||
|
|
@ -257,8 +275,11 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
|
|||
continue;
|
||||
}
|
||||
|
||||
if (import.endsWith(".js")) this->scannedFiles.push_back(cpath);
|
||||
else this->scanDir(cpath);
|
||||
if (import.endsWith(".js")) {
|
||||
this->scannedFiles.push_back(cpath);
|
||||
QByteArray jsData;
|
||||
this->readAndHashFile(cpath, jsData);
|
||||
} else this->scanDir(cpath);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
@ -273,14 +294,12 @@ void QmlScanner::scanQmlRoot(const QString& path) {
|
|||
bool QmlScanner::scanQmlJson(const QString& path) {
|
||||
qCDebug(logQmlScanner) << "Scanning qml.json file" << path;
|
||||
|
||||
auto file = QFile(path);
|
||||
if (!file.open(QFile::ReadOnly | QFile::Text)) {
|
||||
QByteArray data;
|
||||
if (!this->readAndHashFile(path, data)) {
|
||||
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,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <qbytearray.h>
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdir.h>
|
||||
#include <qhash.h>
|
||||
|
|
@ -21,6 +22,7 @@ public:
|
|||
|
||||
QVector<QDir> scannedDirs;
|
||||
QVector<QString> scannedFiles;
|
||||
QHash<QString, QByteArray> fileHashes;
|
||||
QHash<QString, QString> fileIntercepts;
|
||||
|
||||
struct ScanError {
|
||||
|
|
@ -31,6 +33,9 @@ public:
|
|||
|
||||
QVector<ScanError> scanErrors;
|
||||
|
||||
bool readAndHashFile(const QString& path, QByteArray& data);
|
||||
[[nodiscard]] bool hasFileContentChanged(const QString& path) const;
|
||||
|
||||
private:
|
||||
QDir rootPath;
|
||||
|
||||
|
|
|
|||
98
src/core/streamreader.cpp
Normal file
98
src/core/streamreader.cpp
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
#include "streamreader.hpp"
|
||||
#include <cstring>
|
||||
|
||||
#include <qbytearray.h>
|
||||
#include <qiodevice.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
void StreamReader::setDevice(QIODevice* device) {
|
||||
this->reset();
|
||||
this->device = device;
|
||||
}
|
||||
|
||||
void StreamReader::startTransaction() {
|
||||
this->cursor = 0;
|
||||
this->failed = false;
|
||||
}
|
||||
|
||||
bool StreamReader::fill() {
|
||||
auto available = this->device->bytesAvailable();
|
||||
if (available <= 0) return false;
|
||||
auto oldSize = this->buffer.size();
|
||||
this->buffer.resize(oldSize + available);
|
||||
auto bytesRead = this->device->read(this->buffer.data() + oldSize, available); // NOLINT
|
||||
|
||||
if (bytesRead <= 0) {
|
||||
this->buffer.resize(oldSize);
|
||||
return false;
|
||||
}
|
||||
|
||||
this->buffer.resize(oldSize + bytesRead);
|
||||
return true;
|
||||
}
|
||||
|
||||
QByteArray StreamReader::readBytes(qsizetype count) {
|
||||
if (this->failed) return {};
|
||||
|
||||
auto needed = this->cursor + count;
|
||||
|
||||
while (this->buffer.size() < needed) {
|
||||
if (!this->fill()) {
|
||||
this->failed = true;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
auto result = this->buffer.mid(this->cursor, count);
|
||||
this->cursor += count;
|
||||
return result;
|
||||
}
|
||||
|
||||
QByteArray StreamReader::readUntil(char terminator) {
|
||||
if (this->failed) return {};
|
||||
|
||||
auto searchFrom = this->cursor;
|
||||
auto idx = this->buffer.indexOf(terminator, searchFrom);
|
||||
|
||||
while (idx == -1) {
|
||||
searchFrom = this->buffer.size();
|
||||
if (!this->fill()) {
|
||||
this->failed = true;
|
||||
return {};
|
||||
}
|
||||
|
||||
idx = this->buffer.indexOf(terminator, searchFrom);
|
||||
}
|
||||
|
||||
auto length = idx - this->cursor + 1;
|
||||
auto result = this->buffer.mid(this->cursor, length);
|
||||
this->cursor += length;
|
||||
return result;
|
||||
}
|
||||
|
||||
void StreamReader::readInto(char* ptr, qsizetype count) {
|
||||
auto data = this->readBytes(count);
|
||||
if (!data.isEmpty()) memcpy(ptr, data.data(), count);
|
||||
}
|
||||
|
||||
qint32 StreamReader::readI32() {
|
||||
qint32 value = 0;
|
||||
this->readInto(reinterpret_cast<char*>(&value), sizeof(qint32));
|
||||
return value;
|
||||
}
|
||||
|
||||
bool StreamReader::commitTransaction() {
|
||||
if (this->failed) {
|
||||
this->cursor = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
this->buffer.remove(0, this->cursor);
|
||||
this->cursor = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void StreamReader::reset() {
|
||||
this->buffer.clear();
|
||||
this->cursor = 0;
|
||||
}
|
||||
26
src/core/streamreader.hpp
Normal file
26
src/core/streamreader.hpp
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
|
||||
#include <qbytearray.h>
|
||||
#include <qiodevice.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
class StreamReader {
|
||||
public:
|
||||
void setDevice(QIODevice* device);
|
||||
|
||||
void startTransaction();
|
||||
QByteArray readBytes(qsizetype count);
|
||||
QByteArray readUntil(char terminator);
|
||||
void readInto(char* ptr, qsizetype count);
|
||||
qint32 readI32();
|
||||
bool commitTransaction();
|
||||
void reset();
|
||||
|
||||
private:
|
||||
bool fill();
|
||||
|
||||
QIODevice* device = nullptr;
|
||||
QByteArray buffer;
|
||||
qsizetype cursor = 0;
|
||||
bool failed = false;
|
||||
};
|
||||
|
|
@ -81,6 +81,11 @@ void signalHandler(
|
|||
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include <qapplication.h>
|
||||
#include <qboxlayout.h>
|
||||
#include <qconfig.h>
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdesktopservices.h>
|
||||
#include <qfont.h>
|
||||
#include <qfontinfo.h>
|
||||
|
|
@ -12,11 +13,22 @@
|
|||
#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) {
|
||||
|
|
@ -67,22 +79,16 @@ CrashReporterGui::CrashReporterGui(QString reportFolder, int pid)
|
|||
|
||||
if (qtVersionMatches) {
|
||||
mainLayout->addWidget(
|
||||
new QLabel("Please open a bug report for this issue via github or email.")
|
||||
new QLabel("Please open a bug report for this issue on the issue tracker.")
|
||||
);
|
||||
} else {
|
||||
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 via github or email."
|
||||
"If this does not solve the problem, please open a bug report on the issue tracker."
|
||||
));
|
||||
}
|
||||
|
||||
mainLayout->addWidget(new ReportLabel(
|
||||
"Github:",
|
||||
"https://github.com/quickshell-mirror/quickshell/issues/new?template=crash2.yml",
|
||||
this
|
||||
));
|
||||
|
||||
mainLayout->addWidget(new ReportLabel("Email:", "quickshell-bugs@outfoxxed.me", this));
|
||||
mainLayout->addWidget(new ReportLabel("Tracker:", crashreportUrl(), this));
|
||||
|
||||
auto* buttons = new QWidget(this);
|
||||
buttons->setMinimumWidth(900);
|
||||
|
|
@ -112,10 +118,5 @@ void CrashReporterGui::openFolder() {
|
|||
QDesktopServices::openUrl(QUrl::fromLocalFile(this->reportFolder));
|
||||
}
|
||||
|
||||
void CrashReporterGui::openReportUrl() {
|
||||
QDesktopServices::openUrl(
|
||||
QUrl("https://github.com/outfoxxed/quickshell/issues/new?template=crash2.yml")
|
||||
);
|
||||
}
|
||||
|
||||
void CrashReporterGui::openReportUrl() { QDesktopServices::openUrl(QUrl(crashreportUrl())); }
|
||||
void CrashReporterGui::cancel() { QApplication::quit(); }
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@
|
|||
#include <cpptrace/basic.hpp>
|
||||
#include <cpptrace/formatting.hpp>
|
||||
#include <qapplication.h>
|
||||
#include <qconfig.h>
|
||||
#include <qcoreapplication.h>
|
||||
#include <qdatastream.h>
|
||||
#include <qdir.h>
|
||||
|
|
@ -15,19 +14,18 @@
|
|||
#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 {
|
||||
|
|
@ -171,41 +169,15 @@ void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instance) {
|
|||
qCCritical(logCrashReporter) << "Failed to open crash info file for writing.";
|
||||
} else {
|
||||
auto stream = QTextStream(&extraInfoFile);
|
||||
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 << qs::debuginfo::combinedInfo();
|
||||
|
||||
stream << "\n===== Runtime Information =====\n";
|
||||
stream << "Runtime Qt Version: " << qVersion() << '\n';
|
||||
stream << "\n===== Instance Information =====\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";
|
||||
|
|
|
|||
|
|
@ -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,20 +519,10 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
|
|||
}
|
||||
|
||||
if (state.misc.printVersion) {
|
||||
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;
|
||||
if (state.log.verbosity == 0) {
|
||||
qCInfo(logBare).noquote() << "Quickshell" << qs::debuginfo::qsVersion();
|
||||
} else {
|
||||
qCInfo(logBare).noquote() << qs::debuginfo::combinedInfo();
|
||||
}
|
||||
} else if (*state.subcommand.log) {
|
||||
return readLogFile(state);
|
||||
|
|
|
|||
|
|
@ -138,9 +138,11 @@ 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();
|
||||
|
||||
{
|
||||
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)
|
||||
target_link_libraries(quickshell-service-greetd PRIVATE Qt::Quick quickshell-core)
|
||||
|
||||
qs_module_pch(quickshell-service-greetd)
|
||||
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@ 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) {
|
||||
|
|
@ -160,11 +161,12 @@ void GreetdConnection::onSocketError(QLocalSocket::LocalSocketError error) {
|
|||
}
|
||||
|
||||
void GreetdConnection::onSocketReady() {
|
||||
qint32 length = 0;
|
||||
while (true) {
|
||||
this->reader.startTransaction();
|
||||
auto length = this->reader.readI32();
|
||||
auto text = this->reader.readBytes(length);
|
||||
if (!this->reader.commitTransaction()) return;
|
||||
|
||||
this->socket.read(reinterpret_cast<char*>(&length), sizeof(qint32));
|
||||
|
||||
auto text = this->socket.read(length);
|
||||
auto json = QJsonDocument::fromJson(text).object();
|
||||
auto type = json.value("type").toString();
|
||||
|
||||
|
|
@ -232,10 +234,11 @@ void GreetdConnection::onSocketReady() {
|
|||
}
|
||||
} else goto unexpected;
|
||||
|
||||
return;
|
||||
unexpected:
|
||||
continue;
|
||||
unexpected:
|
||||
qCCritical(logGreetd) << "Received unexpected greetd response" << text;
|
||||
this->setActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
void GreetdConnection::sendRequest(const QJsonObject& json) {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../core/streamreader.hpp"
|
||||
|
||||
///! State of the Greetd connection.
|
||||
/// See @@Greetd.state.
|
||||
class GreetdState: public QObject {
|
||||
|
|
@ -74,4 +76,5 @@ private:
|
|||
bool mResponseRequired = false;
|
||||
QString mUser;
|
||||
QLocalSocket socket;
|
||||
StreamReader reader;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
target_link_libraries(quickshell-hyprland-ipc PRIVATE Qt::Quick quickshell-core)
|
||||
|
||||
if (WAYLAND_TOPLEVEL_MANAGEMENT)
|
||||
target_sources(quickshell-hyprland-ipc PRIVATE
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ 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) {
|
||||
|
|
@ -104,11 +105,11 @@ void HyprlandIpc::eventSocketStateChanged(QLocalSocket::LocalSocketState state)
|
|||
|
||||
void HyprlandIpc::eventSocketReady() {
|
||||
while (true) {
|
||||
auto rawEvent = this->eventSocket.readLine();
|
||||
if (rawEvent.isEmpty()) break;
|
||||
this->eventReader.startTransaction();
|
||||
auto rawEvent = this->eventReader.readUntil('\n');
|
||||
if (!this->eventReader.commitTransaction()) return;
|
||||
|
||||
// remove trailing \n
|
||||
rawEvent.truncate(rawEvent.length() - 1);
|
||||
rawEvent.chop(1); // remove trailing \n
|
||||
auto splitIdx = rawEvent.indexOf(">>");
|
||||
auto event = QByteArrayView(rawEvent.data(), splitIdx);
|
||||
auto data = QByteArrayView(
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include "../../../core/model.hpp"
|
||||
#include "../../../core/qmlscreen.hpp"
|
||||
#include "../../../core/streamreader.hpp"
|
||||
#include "../../../wayland/toplevel_management/handle.hpp"
|
||||
|
||||
namespace qs::hyprland::ipc {
|
||||
|
|
@ -139,6 +140,7 @@ private:
|
|||
static bool compareWorkspaces(HyprlandWorkspace* a, HyprlandWorkspace* b);
|
||||
|
||||
QLocalSocket eventSocket;
|
||||
StreamReader eventReader;
|
||||
QString mRequestSocketPath;
|
||||
QString mEventSocketPath;
|
||||
bool valid = false;
|
||||
|
|
|
|||
|
|
@ -161,7 +161,11 @@ void ToplevelManager::onToplevelReady(impl::ToplevelHandle* handle) {
|
|||
|
||||
void ToplevelManager::onToplevelActiveChanged() {
|
||||
auto* toplevel = qobject_cast<Toplevel*>(this->sender());
|
||||
if (toplevel->activated()) this->setActiveToplevel(toplevel);
|
||||
if (toplevel->activated()) {
|
||||
this->setActiveToplevel(toplevel);
|
||||
} else if (toplevel == this->mActiveToplevel) {
|
||||
this->setActiveToplevel(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void ToplevelManager::onToplevelClosed() {
|
||||
|
|
|
|||
|
|
@ -1,20 +1,19 @@
|
|||
qt_add_library(quickshell-wayland-windowsystem STATIC
|
||||
windowmanager.cpp
|
||||
workspace.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-foreign-toplevel ext-foreign-toplevel-list-v1 "${WAYLAND_PROTOCOLS}/staging/ext-foreign-toplevel-list")
|
||||
wl_proto(wlp-ext-workspace ext-workspace-v1 "${WAYLAND_PROTOCOLS}/staging/ext-workspace")
|
||||
|
||||
target_link_libraries(quickshell-wayland-windowsystem PRIVATE
|
||||
Qt::WaylandClient Qt::WaylandClientPrivate wayland-client
|
||||
Qt::Quick # for pch? potentially, check w/ gcc
|
||||
|
||||
wlp-ext-foreign-toplevel wlp-ext-workspace
|
||||
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,15 +1,20 @@
|
|||
#include "ext_workspace.hpp"
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qobject.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 {
|
||||
|
||||
Q_LOGGING_CATEGORY(logWorkspace, "quickshell.wm.wayland.workspace");
|
||||
QS_LOGGING_CATEGORY(logWorkspace, "quickshell.wm.wayland.workspace", QtWarningMsg);
|
||||
|
||||
WorkspaceManager::WorkspaceManager(): QWaylandClientExtensionTemplate(1) { this->initialize(); }
|
||||
|
||||
|
|
@ -126,7 +131,7 @@ WorkspaceGroup::~WorkspaceGroup() {
|
|||
if (this->isInitialized()) this->destroy();
|
||||
}
|
||||
|
||||
void WorkspaceGroup::ext_workspace_group_handle_v1_capabilities(uint32_t capabilities) {
|
||||
void WorkspaceGroup::ext_workspace_group_handle_v1_capabilities(quint32 capabilities) {
|
||||
this->canCreateWorkspace =
|
||||
capabilities & ext_workspace_group_handle_v1::group_capabilities_create_workspace;
|
||||
|
||||
|
|
@ -144,7 +149,8 @@ void WorkspaceGroup::ext_workspace_group_handle_v1_output_leave(::wl_output* out
|
|||
this->screens.removeOutput(output);
|
||||
}
|
||||
|
||||
void WorkspaceGroup::ext_workspace_group_handle_v1_workspace_enter(::ext_workspace_handle_v1* handle
|
||||
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;
|
||||
|
|
@ -152,7 +158,8 @@ void WorkspaceGroup::ext_workspace_group_handle_v1_workspace_enter(::ext_workspa
|
|||
if (workspace) workspace->enterGroup(this);
|
||||
}
|
||||
|
||||
void WorkspaceGroup::ext_workspace_group_handle_v1_workspace_leave(::ext_workspace_handle_v1* handle
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
#include <qlist.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qscreen.h>
|
||||
#include <qscreen_platform.h>
|
||||
#include <qtclasshelpermacros.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
|
@ -12,11 +11,12 @@
|
|||
#include <qwaylandclientextension.h>
|
||||
#include <wayland-ext-workspace-v1-client-protocol.h>
|
||||
|
||||
#include "../../core/logcat.hpp"
|
||||
#include "../output_tracking.hpp"
|
||||
|
||||
namespace qs::wayland::workspace {
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(logWorkspace);
|
||||
QS_DECLARE_LOGGING_CATEGORY(logWorkspace);
|
||||
|
||||
class WorkspaceGroup;
|
||||
class Workspace;
|
||||
|
|
@ -83,8 +83,8 @@ 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(uint32_t state) override;
|
||||
void ext_workspace_handle_v1_capabilities(uint32_t capabilities) 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:
|
||||
|
|
@ -106,7 +106,7 @@ public:
|
|||
bool canCreateWorkspace : 1 = false;
|
||||
|
||||
protected:
|
||||
void ext_workspace_group_handle_v1_capabilities(uint32_t capabilities) override;
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
#include <qcontainerfwd.h>
|
||||
#include <qguiapplication.h>
|
||||
#include <qlist.h>
|
||||
|
||||
#include "../../core/plugin.hpp"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,20 @@
|
|||
#include "windowmanager.hpp"
|
||||
|
||||
#include "../../windowmanager/windowmanager.hpp"
|
||||
#include "windowset.hpp"
|
||||
|
||||
namespace qs::wm::wayland {
|
||||
|
||||
WaylandWindowManager* WaylandWindowManager::instance() {
|
||||
static auto* instance = new WaylandWindowManager();
|
||||
static auto* instance = []() {
|
||||
auto* wm = new WaylandWindowManager();
|
||||
WindowsetManager::instance();
|
||||
return wm;
|
||||
}();
|
||||
return instance;
|
||||
}
|
||||
|
||||
void installWmProvider() {
|
||||
void installWmProvider() { // NOLINT (misc-use-internal-linkage)
|
||||
qs::wm::WindowManager::setProvider([]() { return WaylandWindowManager::instance(); });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
#include <qtmetamacros.h>
|
||||
|
||||
#include "../../windowmanager/windowmanager.hpp"
|
||||
#include "workspace.hpp"
|
||||
#include "windowset.hpp"
|
||||
|
||||
namespace qs::wm::wayland {
|
||||
|
||||
|
|
@ -12,14 +12,6 @@ class WaylandWindowManager: public WindowManager {
|
|||
|
||||
public:
|
||||
static WaylandWindowManager* instance();
|
||||
|
||||
[[nodiscard]] UntypedObjectModel* workspaces() const override {
|
||||
return &WorkspaceManager::instance()->mWorkspaces;
|
||||
}
|
||||
|
||||
[[nodiscard]] UntypedObjectModel* workspaceGroups() const override {
|
||||
return &WorkspaceManager::instance()->mWorkspaceGroups;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace qs::wm::wayland
|
||||
|
|
|
|||
252
src/wayland/windowmanager/windowset.cpp
Normal file
252
src/wayland/windowmanager/windowset.cpp
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
#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
|
||||
85
src/wayland/windowmanager/windowset.hpp
Normal file
85
src/wayland/windowmanager/windowset.hpp
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
#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,198 +0,0 @@
|
|||
#include "workspace.hpp"
|
||||
|
||||
#include <qlist.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qobject.h>
|
||||
#include <qproperty.h>
|
||||
|
||||
#include "ext_workspace.hpp"
|
||||
|
||||
namespace qs::wm::wayland {
|
||||
|
||||
WorkspaceManager::WorkspaceManager() {
|
||||
auto* impl = impl::WorkspaceManager::instance();
|
||||
|
||||
QObject::connect(
|
||||
impl,
|
||||
&impl::WorkspaceManager::serverCommit,
|
||||
this,
|
||||
&WorkspaceManager::onServerCommit
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
impl,
|
||||
&impl::WorkspaceManager::workspaceCreated,
|
||||
this,
|
||||
&WorkspaceManager::onWorkspaceCreated
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
impl,
|
||||
&impl::WorkspaceManager::workspaceDestroyed,
|
||||
this,
|
||||
&WorkspaceManager::onWorkspaceDestroyed
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
impl,
|
||||
&impl::WorkspaceManager::groupCreated,
|
||||
this,
|
||||
&WorkspaceManager::onGroupCreated
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
impl,
|
||||
&impl::WorkspaceManager::groupDestroyed,
|
||||
this,
|
||||
&WorkspaceManager::onGroupDestroyed
|
||||
);
|
||||
}
|
||||
|
||||
void WorkspaceManager::commit() {
|
||||
qCDebug(impl::logWorkspace) << "Committing workspaces";
|
||||
impl::WorkspaceManager::instance()->commit();
|
||||
}
|
||||
|
||||
void WorkspaceManager::onServerCommit() {
|
||||
// Groups are created/destroyed around workspaces to avoid any nulls making it
|
||||
// to the qml engine.
|
||||
|
||||
for (auto* groupImpl: this->pendingGroupCreations) {
|
||||
auto* group = new WlWorkspaceGroup(this, groupImpl);
|
||||
this->groupsByImpl.insert(groupImpl, group);
|
||||
this->mWorkspaceGroups.insertObject(group);
|
||||
}
|
||||
|
||||
for (auto* wsImpl: this->pendingWorkspaceCreations) {
|
||||
auto* ws = new WlWorkspace(this, wsImpl);
|
||||
this->workspaceByImpl.insert(wsImpl, ws);
|
||||
this->mWorkspaces.insertObject(ws);
|
||||
}
|
||||
|
||||
for (auto* wsImpl: this->pendingWorkspaceDestructions) {
|
||||
this->mWorkspaces.removeObject(this->workspaceByImpl.value(wsImpl));
|
||||
this->workspaceByImpl.remove(wsImpl);
|
||||
}
|
||||
|
||||
for (auto* groupImpl: this->pendingGroupDestructions) {
|
||||
this->mWorkspaceGroups.removeObject(this->groupsByImpl.value(groupImpl));
|
||||
this->groupsByImpl.remove(groupImpl);
|
||||
}
|
||||
|
||||
for (auto* ws: this->mWorkspaces.valueList()) ws->commitImpl();
|
||||
for (auto* group: this->mWorkspaceGroups.valueList()) group->commitImpl();
|
||||
|
||||
this->pendingWorkspaceCreations.clear();
|
||||
this->pendingWorkspaceDestructions.clear();
|
||||
this->pendingGroupCreations.clear();
|
||||
this->pendingGroupDestructions.clear();
|
||||
}
|
||||
|
||||
void WorkspaceManager::onWorkspaceCreated(impl::Workspace* workspace) {
|
||||
this->pendingWorkspaceCreations.append(workspace);
|
||||
}
|
||||
|
||||
void WorkspaceManager::onWorkspaceDestroyed(impl::Workspace* workspace) {
|
||||
if (!this->pendingWorkspaceCreations.removeOne(workspace)) {
|
||||
this->pendingWorkspaceDestructions.append(workspace);
|
||||
}
|
||||
}
|
||||
|
||||
void WorkspaceManager::onGroupCreated(impl::WorkspaceGroup* group) {
|
||||
this->pendingGroupCreations.append(group);
|
||||
}
|
||||
|
||||
void WorkspaceManager::onGroupDestroyed(impl::WorkspaceGroup* group) {
|
||||
if (!this->pendingGroupCreations.removeOne(group)) {
|
||||
this->pendingGroupDestructions.append(group);
|
||||
}
|
||||
}
|
||||
|
||||
WorkspaceManager* WorkspaceManager::instance() {
|
||||
static auto* instance = new WorkspaceManager();
|
||||
return instance;
|
||||
}
|
||||
|
||||
WlWorkspace::WlWorkspace(WorkspaceManager* manager, impl::Workspace* impl)
|
||||
: Workspace(manager)
|
||||
, impl(impl) {
|
||||
this->commitImpl();
|
||||
}
|
||||
|
||||
void WlWorkspace::commitImpl() {
|
||||
Qt::beginPropertyUpdateGroup();
|
||||
this->bId = this->impl->id;
|
||||
this->bName = this->impl->name;
|
||||
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->bCanSetGroup = this->impl->canAssign;
|
||||
this->bGroup = this->manager()->groupsByImpl.value(this->impl->group);
|
||||
Qt::endPropertyUpdateGroup();
|
||||
}
|
||||
|
||||
void WlWorkspace::activate() {
|
||||
if (!this->bCanActivate) {
|
||||
qCritical(logWorkspace) << this << "cannot be activated";
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(impl::logWorkspace) << "Calling activate() for" << this;
|
||||
this->impl->activate();
|
||||
WorkspaceManager::commit();
|
||||
}
|
||||
|
||||
void WlWorkspace::deactivate() {
|
||||
if (!this->bCanDeactivate) {
|
||||
qCritical(logWorkspace) << this << "cannot be deactivated";
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(impl::logWorkspace) << "Calling deactivate() for" << this;
|
||||
this->impl->deactivate();
|
||||
WorkspaceManager::commit();
|
||||
}
|
||||
|
||||
void WlWorkspace::remove() {
|
||||
if (!this->bCanRemove) {
|
||||
qCritical(logWorkspace) << this << "cannot be removed";
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(impl::logWorkspace) << "Calling remove() for" << this;
|
||||
this->impl->remove();
|
||||
WorkspaceManager::commit();
|
||||
}
|
||||
|
||||
void WlWorkspace::setGroup(WorkspaceGroup* group) {
|
||||
if (!this->bCanSetGroup) {
|
||||
qCritical(logWorkspace) << this << "cannot be assigned to a group";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!group) {
|
||||
qCritical(logWorkspace) << "Cannot set a workspace's group to null";
|
||||
return;
|
||||
}
|
||||
|
||||
qCDebug(impl::logWorkspace) << "Assigning" << this << "to" << group;
|
||||
// NOLINTNEXTLINE: A WorkspaceGroup will always be a WlWorkspaceGroup under wayland.
|
||||
this->impl->assign(static_cast<WlWorkspaceGroup*>(group)->impl->object());
|
||||
WorkspaceManager::commit();
|
||||
}
|
||||
|
||||
WlWorkspaceGroup::WlWorkspaceGroup(WorkspaceManager* manager, impl::WorkspaceGroup* impl)
|
||||
: WorkspaceGroup(manager)
|
||||
, impl(impl) {
|
||||
this->commitImpl();
|
||||
}
|
||||
|
||||
void WlWorkspaceGroup::commitImpl() {
|
||||
// TODO: will not commit the correct screens if missing qt repr at commit time
|
||||
this->bScreens = this->impl->screens.screens();
|
||||
}
|
||||
|
||||
} // namespace qs::wm::wayland
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <qhash.h>
|
||||
#include <qobject.h>
|
||||
#include <qproperty.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
#include "../../core/model.hpp"
|
||||
#include "../../windowmanager/workspace.hpp"
|
||||
#include "ext_workspace.hpp"
|
||||
|
||||
namespace qs::wm::wayland {
|
||||
namespace impl = qs::wayland::workspace;
|
||||
|
||||
class WlWorkspace;
|
||||
class WlWorkspaceGroup;
|
||||
|
||||
class WorkspaceManager: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
static WorkspaceManager* instance();
|
||||
|
||||
ObjectModel<WlWorkspace> mWorkspaces {this};
|
||||
ObjectModel<WlWorkspaceGroup> mWorkspaceGroups {this};
|
||||
|
||||
static void commit();
|
||||
|
||||
private slots:
|
||||
void onServerCommit();
|
||||
void onWorkspaceCreated(impl::Workspace* workspace);
|
||||
void onWorkspaceDestroyed(impl::Workspace* workspace);
|
||||
void onGroupCreated(impl::WorkspaceGroup* group);
|
||||
void onGroupDestroyed(impl::WorkspaceGroup* group);
|
||||
|
||||
private:
|
||||
WorkspaceManager();
|
||||
|
||||
QList<impl::Workspace*> pendingWorkspaceCreations;
|
||||
QList<impl::Workspace*> pendingWorkspaceDestructions;
|
||||
QHash<impl::Workspace*, WlWorkspace*> workspaceByImpl;
|
||||
|
||||
QList<impl::WorkspaceGroup*> pendingGroupCreations;
|
||||
QList<impl::WorkspaceGroup*> pendingGroupDestructions;
|
||||
QHash<impl::WorkspaceGroup*, WlWorkspaceGroup*> groupsByImpl;
|
||||
|
||||
friend class WlWorkspace;
|
||||
};
|
||||
|
||||
class WlWorkspace: public Workspace {
|
||||
public:
|
||||
WlWorkspace(WorkspaceManager* manager, impl::Workspace* impl);
|
||||
|
||||
void commitImpl();
|
||||
|
||||
void activate() override;
|
||||
void deactivate() override;
|
||||
void remove() override;
|
||||
void setGroup(WorkspaceGroup* group) override;
|
||||
|
||||
[[nodiscard]] WorkspaceManager* manager() {
|
||||
return static_cast<WorkspaceManager*>(this->parent()); // NOLINT
|
||||
}
|
||||
|
||||
private:
|
||||
impl::Workspace* impl = nullptr;
|
||||
};
|
||||
|
||||
class WlWorkspaceGroup: public WorkspaceGroup {
|
||||
public:
|
||||
WlWorkspaceGroup(WorkspaceManager* manager, impl::WorkspaceGroup* impl);
|
||||
|
||||
void commitImpl();
|
||||
|
||||
[[nodiscard]] WorkspaceManager* manager() {
|
||||
return static_cast<WorkspaceManager*>(this->parent()); // NOLINT
|
||||
}
|
||||
|
||||
private:
|
||||
impl::WorkspaceGroup* impl = nullptr;
|
||||
|
||||
friend class WlWorkspace;
|
||||
};
|
||||
|
||||
} // namespace qs::wm::wayland
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
qt_add_library(quickshell-windowmanager STATIC
|
||||
screenprojection.cpp
|
||||
windowmanager.cpp
|
||||
workspace.cpp
|
||||
workspacemodel.cpp
|
||||
windowset.cpp
|
||||
)
|
||||
|
||||
qt_add_qml_module(quickshell-windowmanager
|
||||
|
|
@ -14,5 +14,7 @@ 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-windowmanager)
|
||||
target_link_libraries(quickshell PRIVATE quickshell-windowmanagerplugin)
|
||||
|
|
|
|||
10
src/windowmanager/module.md
Normal file
10
src/windowmanager/module.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
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.
|
||||
30
src/windowmanager/screenprojection.cpp
Normal file
30
src/windowmanager/screenprojection.cpp
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#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
|
||||
34
src/windowmanager/screenprojection.hpp
Normal file
34
src/windowmanager/screenprojection.hpp
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#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
|
||||
86
src/windowmanager/test/manual/WorkspaceDelegate.qml
Normal file
86
src/windowmanager/test/manual/WorkspaceDelegate.qml
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
45
src/windowmanager/test/manual/screenproj.qml
Normal file
45
src/windowmanager/test/manual/screenproj.qml
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
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 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,11 +11,11 @@ FloatingWindow {
|
|||
|
||||
ColumnLayout {
|
||||
Repeater {
|
||||
model: WindowManager.workspaceGroups
|
||||
model: WindowManager.windowsetProjections
|
||||
|
||||
WrapperRectangle {
|
||||
id: delegate
|
||||
required property WorkspaceGroup modelData
|
||||
required property WindowsetProjection modelData
|
||||
color: "slategray"
|
||||
margin: 5
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ FloatingWindow {
|
|||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: [...WindowManager.workspaces.values].filter(w => w.group == delegate.modelData)
|
||||
values: delegate.modelData.windowsets
|
||||
}
|
||||
|
||||
WorkspaceDelegate {}
|
||||
|
|
@ -36,77 +36,11 @@ FloatingWindow {
|
|||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: WindowManager.workspaces.values.filter(w => w.group == null)
|
||||
values: WindowManager.windowsets.filter(w => w.projection == null)
|
||||
}
|
||||
|
||||
WorkspaceDelegate {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component WorkspaceDelegate: WrapperRectangle {
|
||||
id: delegate
|
||||
required property Workspace modelData;
|
||||
color: modelData.active ? "green" : "gray"
|
||||
|
||||
ColumnLayout {
|
||||
Label { text: delegate.modelData.toString() }
|
||||
Label { text: `Id: ${delegate.modelData.id} Name: ${delegate.modelData.name}` }
|
||||
|
||||
RowLayout {
|
||||
Label { text: "Group:" }
|
||||
ComboBox {
|
||||
Layout.fillWidth: true
|
||||
implicitContentWidthPolicy: ComboBox.WidestText
|
||||
enabled: delegate.modelData.canSetGroup
|
||||
model: [...WindowManager.workspaceGroups.values].map(w => w.toString())
|
||||
currentIndex: WindowManager.workspaceGroups.values.indexOf(delegate.modelData.group)
|
||||
onActivated: i => delegate.modelData.setGroup(WindowManager.workspaceGroups.values[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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,11 @@
|
|||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
#include <qobject.h>
|
||||
|
||||
#include "../core/qmlscreen.hpp"
|
||||
#include "screenprojection.hpp"
|
||||
|
||||
namespace qs::wm {
|
||||
|
||||
std::function<WindowManager*()> WindowManager::provider;
|
||||
|
|
@ -15,4 +20,22 @@ WindowManager* WindowManager::instance() {
|
|||
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
|
||||
|
|
|
|||
|
|
@ -2,12 +2,17 @@
|
|||
|
||||
#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/model.hpp"
|
||||
#include "workspace.hpp"
|
||||
#include "../core/qmlscreen.hpp"
|
||||
#include "screenprojection.hpp"
|
||||
#include "windowset.hpp"
|
||||
|
||||
namespace qs::wm {
|
||||
|
||||
|
|
@ -18,32 +23,68 @@ public:
|
|||
static void setProvider(std::function<WindowManager*()> provider);
|
||||
static WindowManager* instance();
|
||||
|
||||
[[nodiscard]] virtual UntypedObjectModel* workspaces() const {
|
||||
return UntypedObjectModel::emptyInstance();
|
||||
Q_INVOKABLE ScreenProjection* screenProjection(QuickshellScreenInfo* screen);
|
||||
|
||||
[[nodiscard]] QBindable<QList<Windowset*>> bindableWindowsets() const {
|
||||
return &this->bWindowsets;
|
||||
}
|
||||
|
||||
[[nodiscard]] virtual UntypedObjectModel* workspaceGroups() const {
|
||||
return UntypedObjectModel::emptyInstance();
|
||||
[[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;
|
||||
Q_PROPERTY(UntypedObjectModel* workspaces READ workspaces CONSTANT);
|
||||
Q_PROPERTY(UntypedObjectModel* workspaceGroups READ workspaceGroups CONSTANT);
|
||||
// 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:
|
||||
[[nodiscard]] static UntypedObjectModel* workspaces() {
|
||||
return WindowManager::instance()->workspaces();
|
||||
/// 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 UntypedObjectModel* workspaceGroups() {
|
||||
return WindowManager::instance()->workspaceGroups();
|
||||
[[nodiscard]] static QBindable<QList<Windowset*>> bindableWindowsets() {
|
||||
return WindowManager::instance()->bindableWindowsets();
|
||||
}
|
||||
|
||||
[[nodiscard]] static QBindable<QList<WindowsetProjection*>> bindableWindowsetProjections() {
|
||||
return WindowManager::instance()->bindableWindowsetProjections();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
45
src/windowmanager/windowset.cpp
Normal file
45
src/windowmanager/windowset.cpp
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#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
|
||||
175
src/windowmanager/windowset.hpp
Normal file
175
src/windowmanager/windowset.hpp
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
#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
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
#include "workspace.hpp"
|
||||
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
#include "../core/qmlglobal.hpp"
|
||||
|
||||
namespace qs::wm {
|
||||
|
||||
Q_LOGGING_CATEGORY(logWorkspace, "quickshell.wm.workspace", QtWarningMsg);
|
||||
|
||||
void Workspace::activate() { qCCritical(logWorkspace) << this << "cannot be activated"; }
|
||||
void Workspace::deactivate() { qCCritical(logWorkspace) << this << "cannot be deactivated"; }
|
||||
void Workspace::remove() { qCCritical(logWorkspace) << this << "cannot be removed"; }
|
||||
|
||||
void Workspace::setGroup(WorkspaceGroup* /*group*/) {
|
||||
qCCritical(logWorkspace) << this << "cannot be assigned to a group";
|
||||
}
|
||||
|
||||
void WorkspaceGroup::onScreensChanged() {
|
||||
mCachedScreens.clear();
|
||||
|
||||
for (auto* screen: this->bScreens.value()) {
|
||||
mCachedScreens.append(QuickshellTracked::instance()->screenInfo(screen));
|
||||
}
|
||||
|
||||
emit this->screensChanged();
|
||||
}
|
||||
|
||||
} // namespace qs::wm
|
||||
|
|
@ -1,119 +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 WorkspaceGroup;
|
||||
|
||||
class Workspace: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_UNCREATABLE("");
|
||||
// clang-format off
|
||||
// persistent id
|
||||
Q_PROPERTY(QString id READ default BINDABLE bindableId NOTIFY idChanged);
|
||||
Q_PROPERTY(QString name READ default BINDABLE bindableName NOTIFY nameChanged);
|
||||
// currently visible
|
||||
Q_PROPERTY(bool active READ default BINDABLE bindableActive NOTIFY activeChanged);
|
||||
Q_PROPERTY(WorkspaceGroup* group READ default BINDABLE bindableGroup NOTIFY groupChanged);
|
||||
// in workspace pickers
|
||||
Q_PROPERTY(bool shouldDisplay READ default BINDABLE bindableShouldDisplay NOTIFY shouldDisplayChanged);
|
||||
Q_PROPERTY(bool urgent READ default BINDABLE bindableUrgent NOTIFY urgentChanged);
|
||||
Q_PROPERTY(bool canActivate READ default BINDABLE bindableCanActivate NOTIFY canActivateChanged);
|
||||
Q_PROPERTY(bool canDeactivate READ default BINDABLE bindableCanDeactivate NOTIFY canDeactivateChanged);
|
||||
Q_PROPERTY(bool canRemove READ default BINDABLE bindableCanRemove NOTIFY canRemoveChanged);
|
||||
Q_PROPERTY(bool canSetGroup READ default BINDABLE bindableCanSetGroup NOTIFY canSetGroupChanged);
|
||||
// clang-format on
|
||||
|
||||
public:
|
||||
explicit Workspace(QObject* parent): QObject(parent) {}
|
||||
|
||||
Q_INVOKABLE virtual void activate();
|
||||
Q_INVOKABLE virtual void deactivate();
|
||||
Q_INVOKABLE virtual void remove();
|
||||
Q_INVOKABLE virtual void setGroup(WorkspaceGroup* group);
|
||||
|
||||
[[nodiscard]] QBindable<QString> bindableId() const { return &this->bId; }
|
||||
[[nodiscard]] QBindable<QString> bindableName() const { return &this->bName; }
|
||||
[[nodiscard]] QBindable<bool> bindableActive() const { return &this->bActive; }
|
||||
[[nodiscard]] QBindable<WorkspaceGroup*> bindableGroup() const { return &this->bGroup; }
|
||||
[[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> bindableCanSetGroup() const { return &this->bCanSetGroup; }
|
||||
|
||||
signals:
|
||||
void idChanged();
|
||||
void nameChanged();
|
||||
void activeChanged();
|
||||
void groupChanged();
|
||||
void shouldDisplayChanged();
|
||||
void urgentChanged();
|
||||
void canActivateChanged();
|
||||
void canDeactivateChanged();
|
||||
void canRemoveChanged();
|
||||
void canSetGroupChanged();
|
||||
|
||||
protected:
|
||||
Q_OBJECT_BINDABLE_PROPERTY(Workspace, QString, bId, &Workspace::idChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(Workspace, QString, bName, &Workspace::nameChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(Workspace, bool, bActive, &Workspace::activeChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(Workspace, WorkspaceGroup*, bGroup, &Workspace::groupChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(Workspace, bool, bShouldDisplay, &Workspace::shouldDisplayChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(Workspace, bool, bUrgent, &Workspace::urgentChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(Workspace, bool, bCanActivate, &Workspace::canActivateChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(Workspace, bool, bCanDeactivate, &Workspace::canDeactivateChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(Workspace, bool, bCanRemove, &Workspace::canRemoveChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(Workspace, bool, bCanSetGroup, &Workspace::canSetGroupChanged);
|
||||
//Q_OBJECT_BINDABLE_PROPERTY(Workspace, qint32, bIndex);
|
||||
};
|
||||
|
||||
class WorkspaceGroup: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_UNCREATABLE("");
|
||||
/// Screens the workspace group is present on.
|
||||
///
|
||||
/// > [!WARNING] This is not a model. Use @@Quickshell.ScriptModel if you need it to
|
||||
/// > behave like one.
|
||||
Q_PROPERTY(QList<QuickshellScreenInfo*> screens READ screens NOTIFY screensChanged);
|
||||
|
||||
public:
|
||||
explicit WorkspaceGroup(QObject* parent): QObject(parent) {}
|
||||
|
||||
[[nodiscard]] const QList<QuickshellScreenInfo*>& screens() const { return this->mCachedScreens; }
|
||||
|
||||
signals:
|
||||
void screensChanged();
|
||||
|
||||
private slots:
|
||||
void onScreensChanged();
|
||||
|
||||
protected:
|
||||
Q_OBJECT_BINDABLE_PROPERTY(
|
||||
WorkspaceGroup,
|
||||
QList<QScreen*>,
|
||||
bScreens,
|
||||
&WorkspaceGroup::onScreensChanged
|
||||
);
|
||||
|
||||
private:
|
||||
QList<QuickshellScreenInfo*> mCachedScreens;
|
||||
};
|
||||
|
||||
} // namespace qs::wm
|
||||
|
|
@ -1 +0,0 @@
|
|||
#include "workspacemodel.hpp"
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <qobject.h>
|
||||
#include <qproperty.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
namespace qs::windowsystem {
|
||||
|
||||
class WorkspaceModel: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
|
||||
public:
|
||||
enum ConflictStrategy : quint8 {
|
||||
KeepFirst = 0,
|
||||
ShowDuplicates,
|
||||
};
|
||||
Q_ENUM(ConflictStrategy);
|
||||
|
||||
signals:
|
||||
void fromChanged();
|
||||
void toChanged();
|
||||
void screensChanged();
|
||||
void conflictStrategyChanged();
|
||||
|
||||
private:
|
||||
Q_OBJECT_BINDABLE_PROPERTY(WorkspaceModel, qint32, bFrom, &WorkspaceModel::fromChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(WorkspaceModel, qint32, bTo, &WorkspaceModel::toChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(
|
||||
WorkspaceModel,
|
||||
ConflictStrategy,
|
||||
bConflictStrategy,
|
||||
&WorkspaceModel::conflictStrategyChanged
|
||||
);
|
||||
};
|
||||
|
||||
} // namespace qs::windowsystem
|
||||
|
|
@ -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)
|
||||
target_link_libraries(quickshell-i3-ipc PRIVATE Qt::Quick quickshell-core)
|
||||
|
||||
qs_module_pch(quickshell-i3-ipc SET large)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
#include <qbytearray.h>
|
||||
#include <qbytearrayview.h>
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdatastream.h>
|
||||
#include <qjsonarray.h>
|
||||
#include <qjsondocument.h>
|
||||
#include <qjsonobject.h>
|
||||
|
|
@ -15,9 +14,7 @@
|
|||
#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>
|
||||
|
|
@ -89,9 +86,6 @@ 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) {
|
||||
|
|
@ -145,34 +139,21 @@ void I3Ipc::reconnectIPC() {
|
|||
}
|
||||
|
||||
QVector<Event> I3Ipc::parseResponse() {
|
||||
QVector<std::tuple<EventCode, QJsonDocument>> events;
|
||||
const int magicLen = 6;
|
||||
QVector<Event> events;
|
||||
|
||||
while (!this->liveEventSocketDs.atEnd()) {
|
||||
this->liveEventSocketDs.startTransaction();
|
||||
this->liveEventSocketDs.startTransaction();
|
||||
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;
|
||||
|
||||
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) {
|
||||
if (magic.size() < 6 || strncmp(magic.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";
|
||||
|
|
@ -204,6 +185,7 @@ 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,7 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <qbytearrayview.h>
|
||||
#include <qdatastream.h>
|
||||
#include <qjsondocument.h>
|
||||
#include <qlocalsocket.h>
|
||||
#include <qobject.h>
|
||||
|
|
@ -9,6 +8,8 @@
|
|||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../../core/streamreader.hpp"
|
||||
|
||||
namespace qs::i3::ipc {
|
||||
|
||||
constexpr std::string MAGIC = "i3-ipc";
|
||||
|
|
@ -92,7 +93,7 @@ protected:
|
|||
QVector<std::tuple<EventCode, QJsonDocument>> parseResponse();
|
||||
|
||||
QLocalSocket liveEventSocket;
|
||||
QDataStream liveEventSocketDs;
|
||||
StreamReader eventReader;
|
||||
|
||||
QString mSocketPath;
|
||||
bool valid = false;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue