diff --git a/.clang-tidy b/.clang-tidy
index da14682..c83ed8f 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -20,7 +20,6 @@ Checks: >
-cppcoreguidelines-avoid-do-while,
-cppcoreguidelines-pro-type-reinterpret-cast,
-cppcoreguidelines-pro-type-vararg,
- -cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-use-enum-class,
google-global-names-in-headers,
google-readability-casting,
diff --git a/.github/ISSUE_TEMPLATE/crash.yml b/.github/ISSUE_TEMPLATE/crash.yml
index b5a995a..c8b4804 100644
--- a/.github/ISSUE_TEMPLATE/crash.yml
+++ b/.github/ISSUE_TEMPLATE/crash.yml
@@ -1,17 +1,82 @@
-name: Crash Report (v1)
-description: Quickshell has crashed (old)
+name: Crash Report
+description: Quickshell has crashed
labels: ["bug", "crash"]
body:
- - type: markdown
+ - type: textarea
+ id: crashinfo
attributes:
- value: |
- Thank you for taking the time to click the report button.
- At this point most of the worst issues in 0.2.1 and before have been fixed and we are
- preparing for a new release. Please do not report crashes from 0.2.1 or before for now.
- - type: checkboxes
- id: donotcheck
+ label: General crash information
+ description: |
+ Paste the contents of the `info.txt` file in your crash folder here.
+ value: " General information
+
+
+ ```
+
+
+
+ ```
+
+
+ "
+ validations:
+ required: true
+ - type: textarea
+ id: userinfo
attributes:
- label: Do not check this box
- options:
- - label: Do not check this box
- required: true
+ label: What caused the crash
+ description: |
+ Any information likely to help debug the crash. What were you doing when the crash occurred,
+ what changes did you make, can you get it to happen again?
+ - type: textarea
+ id: dump
+ attributes:
+ label: Minidump
+ description: |
+ Attach `minidump.dmp.log` here. If it is too big to upload, compress it.
+
+ You may skip this step if quickshell crashed while processing a password
+ or other sensitive information. If you skipped it write why instead.
+ validations:
+ required: true
+ - type: textarea
+ id: logs
+ attributes:
+ label: Log file
+ description: |
+ Attach `log.qslog.log` here. If it is too big to upload, compress it.
+
+ You can preview the log if you'd like using `quickshell read-log `.
+ validations:
+ required: true
+ - type: textarea
+ id: config
+ attributes:
+ label: Configuration
+ description: |
+ Attach your configuration here, preferrably in full (not just one file).
+ Compress it into a zip, tar, etc.
+
+ This will help us reproduce the crash ourselves.
+ - type: textarea
+ id: bt
+ attributes:
+ label: Backtrace
+ description: |
+ If you have gdb installed and use systemd, or otherwise know how to get a backtrace,
+ we would appreciate one. (You may have gdb installed without knowing it)
+
+ 1. Run `coredumpctl debug ` where `pid` is the number shown after "Crashed process ID"
+ in the crash reporter.
+ 2. Once it loads, type `bt -full` (then enter)
+ 3. Copy the output and attach it as a file or in a spoiler.
+ - type: textarea
+ id: exe
+ attributes:
+ label: Executable
+ description: |
+ If the crash folder contains a executable.txt file, upload it here. If not you can ignore this field.
+ If it is too big to upload, compress it.
+
+ Note: executable.txt is the quickshell binary. It has a .txt extension due to github's limitations on
+ filetypes.
diff --git a/.github/ISSUE_TEMPLATE/crash2.yml b/.github/ISSUE_TEMPLATE/crash2.yml
deleted file mode 100644
index 6984460..0000000
--- a/.github/ISSUE_TEMPLATE/crash2.yml
+++ /dev/null
@@ -1,49 +0,0 @@
-name: Crash Report (v2)
-description: Quickshell has crashed
-labels: ["bug", "crash"]
-body:
- - type: textarea
- id: userinfo
- attributes:
- label: What caused the crash
- description: |
- Any information likely to help debug the crash. What were you doing when the crash occurred,
- what changes did you make, can you get it to happen again?
- - type: upload
- id: report
- attributes:
- label: Report file
- description: Attach `report.txt` here.
- validations:
- required: true
- - type: upload
- id: logs
- attributes:
- label: Log file
- description: |
- Attach `log.qslog.log` here. If it is too big to upload, compress it.
-
- You can preview the log if you'd like using `quickshell read-log `.
- validations:
- required: true
- - type: textarea
- id: config
- attributes:
- label: Configuration
- description: |
- Attach or link your configuration here, preferrably in full (not just one file).
- Compress it into a zip, tar, etc.
-
- This will help us reproduce the crash ourselves.
- - type: textarea
- id: bt
- attributes:
- label: Backtrace
- description: |
- GDB usually produces better stacktraces than quickshell can. Consider attaching a gdb backtrace
- following the instructions below.
-
- 1. Run `coredumpctl debug ` where `pid` is the number shown after "Crashed process ID"
- in the crash reporter.
- 2. Once it loads, type `bt -full` (then enter)
- 3. Copy the output and attach it as a file or in a spoiler.
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 7b8cbce..8d19f58 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -55,11 +55,10 @@ jobs:
libpipewire \
cli11 \
polkit \
- jemalloc \
- libunwind \
- git # for cpptrace clone
+ jemalloc
- name: Build
+ # breakpad is annoying to build in ci due to makepkg not running as root
run: |
- cmake -GNinja -B build -DVENDOR_CPPTRACE=ON
+ cmake -GNinja -B build -DCRASH_REPORTER=OFF
cmake --build build
diff --git a/BUILD.md b/BUILD.md
index d624a06..c9459b5 100644
--- a/BUILD.md
+++ b/BUILD.md
@@ -15,7 +15,15 @@ Please make this descriptive enough to identify your specific package, for examp
- `Nixpkgs`
- `Fedora COPR (errornointernet/quickshell)`
-If you are forking quickshell, please change `CRASHREPORT_URL` to your own issue tracker.
+`-DDISTRIBUTOR_DEBUGINFO_AVAILABLE=YES/NO`
+
+If we can retrieve binaries and debug information for the package without actually running your
+distribution (e.g. from an website), and you would like to strip the binary, please set this to `YES`.
+
+If we cannot retrieve debug information, please set this to `NO` and
+**ensure you aren't distributing stripped (non debuggable) binaries**.
+
+In both cases you should build with `-DCMAKE_BUILD_TYPE=RelWithDebInfo` (then split or keep the debuginfo).
### QML Module dir
Currently all QML modules are statically linked to quickshell, but this is where
@@ -33,7 +41,6 @@ Quickshell has a set of base dependencies you will always need, names vary by di
- `cmake`
- `qt6base`
- `qt6declarative`
-- `libdrm`
- `qtshadertools` (build-time)
- `spirv-tools` (build-time)
- `pkg-config` (build-time)
@@ -57,24 +64,14 @@ At least Qt 6.6 is required.
All features are enabled by default and some have their own dependencies.
-### Crash Handler
-The crash reporter catches crashes, restarts Quickshell when it crashes,
+### Crash Reporter
+The crash reporter catches crashes, restarts quickshell when it crashes,
and collects useful crash information in one place. Leaving this enabled will
enable us to fix bugs far more easily.
-To disable: `-DCRASH_HANDLER=OFF`
+To disable: `-DCRASH_REPORTER=OFF`
-Dependencies: `cpptrace`
-
-Note: `-DVENDOR_CPPTRACE=ON` can be set to vendor cpptrace using FetchContent.
-
-When using FetchContent, `libunwind` is required, and `libdwarf` can be provided by the
-package manager or fetched with FetchContent.
-
-*Please ensure binaries have usable symbols.* We do not necessarily need full debuginfo, but
-leaving symbols in the binary is extremely helpful. You can check if symbols are useful
-by sending a SIGSEGV to the process and ensuring symbols for the quickshell binary are present
-in the trace.
+Dependencies: `google-breakpad` (static library)
### Jemalloc
We recommend leaving Jemalloc enabled as it will mask memory fragmentation caused
@@ -147,6 +144,7 @@ Enables streaming video from monitors and toplevel windows through various proto
To disable: `-DSCREENCOPY=OFF`
Dependencies:
+- `libdrm`
- `libgbm`
- `vulkan-headers` (build-time)
@@ -242,7 +240,7 @@ Only `ninja` builds are tested, but makefiles may work.
#### Configuring the build
```sh
-$ cmake -GNinja -B build -DCMAKE_BUILD_TYPE=Release [additional disable flags from above here]
+$ cmake -GNinja -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo [additional disable flags from above here]
```
Note that features you do not supply dependencies for MUST be disabled with their associated flags
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1226342..7633f4f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -9,9 +9,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(QS_BUILD_OPTIONS "")
-# should be changed for forks
-set(CRASHREPORT_URL "https://github.com/outfoxxed/quickshell/issues/new?template=crash2.yml" CACHE STRING "Bugreport URL")
-
function(boption VAR NAME DEFAULT)
cmake_parse_arguments(PARSE_ARGV 3 arg "" "REQUIRES" "")
@@ -43,17 +40,19 @@ string(APPEND QS_BUILD_OPTIONS " Distributor: ${DISTRIBUTOR}")
message(STATUS "Quickshell configuration")
message(STATUS " Distributor: ${DISTRIBUTOR}")
+boption(DISTRIBUTOR_DEBUGINFO_AVAILABLE "Distributor provided debuginfo" NO)
boption(NO_PCH "Disable precompild headers (dev)" OFF)
boption(BUILD_TESTING "Build tests (dev)" OFF)
boption(ASAN "ASAN (dev)" OFF) # note: better output with gcc than clang
boption(FRAME_POINTERS "Keep Frame Pointers (dev)" ${ASAN})
if (CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
+ boption(CRASH_REPORTER "Crash Handling" OFF)
boption(USE_JEMALLOC "Use jemalloc" OFF)
else()
+ boption(CRASH_REPORTER "Crash Handling" ON)
boption(USE_JEMALLOC "Use jemalloc" ON)
endif()
-boption(CRASH_HANDLER "Crash Handling" ON)
boption(SOCKETS "Unix Sockets" ON)
boption(WAYLAND "Wayland" ON)
boption(WAYLAND_WLR_LAYERSHELL " Wlroots Layer-Shell" ON REQUIRES WAYLAND)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 73e7931..39fab13 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,40 +1,235 @@
-# Contributing
+# Contributing / Development
+Instructions for development setup and upstreaming patches.
-Thank you for taking the time to contribute.
-To ensure nobody's time is wasted, please follow the rules below.
+If you just want to build or package quickshell see [BUILD.md](BUILD.md).
-## Acceptable Code Contributions
+## Development
-- All changes submitted MUST be **fully understood by the submitter**. If you do not know why or how
- your change works, do not submit it to be merged. You must be able to explain your reasoning
- for every change.
+Install the dependencies listed in [BUILD.md](BUILD.md).
+You probably want all of them even if you don't use all of them
+to ensure tests work correctly and avoid passing a bunch of configure
+flags when you need to wipe the build directory.
-- Changes MUST be submitted by a human who will be responsible for them. Changes submitted without
- a human in the loop such as automated tooling and AI Agents are **strictly disallowed**. Accounts
- responsible for such contribution attempts **will be banned**.
+Quickshell also uses `just` for common development command aliases.
-- Changes MUST respect Quickshell's license and the license of any source works. Changes including
- code from any other works must disclose the source of the code, explain why it was used, and
- ensure the license is compatible.
+The dependencies are also available as a nix shell or nix flake which we recommend
+using with nix-direnv.
-- Changes must follow the guidelines outlined in [HACKING.md](HACKING.md) for style and substance.
+Common aliases:
+- `just configure [ [extra cmake args]]` (note that you must specify debug/release to specify extra args)
+- `just build` - runs the build, configuring if not configured already.
+- `just run [args]` - runs quickshell with the given arguments
+- `just clean` - clean up build artifacts. `just clean build` is somewhat common.
-- Changes must stand on their own as a unit. Do not make multiple unrelated changes in one PR.
- Changes depending on prior merges should be marked as a draft.
+### Formatting
+All contributions should be formatted similarly to what already exists.
+Group related functionality together.
-## Acceptable Non-code Contributions
+Run the formatter using `just fmt`.
+If the results look stupid, fix the clang-format file if possible,
+or disable clang-format in the affected area
+using `// clang-format off` and `// clang-format on`.
-- Bug and crash reports. You must follow the instructions in the issue templates and provide the
- information requested.
+#### Style preferences not caught by clang-format
+These are flexible. You can ignore them if it looks or works better to
+for one reason or another.
-- Feature requests can be made via Issues. Please check to ensure nobody else has requested the same feature.
+Use `auto` if the type of a variable can be deduced automatically, instead of
+redeclaring the returned value's type. Additionally, auto should be used when a
+constructor takes arguments.
-- Do not make insubstantial or pointless changes.
+```cpp
+auto x = ; // ok
+auto x = QString::number(3); // ok
+QString x; // ok
+QString x = "foo"; // ok
+auto x = QString("foo"); // ok
-- Changes to project rules / policy / governance will not be entertained, except from significant
- long-term contributors. These changes should not be addressed through contribution channels.
+auto x = QString(); // avoid
+QString x(); // avoid
+QString x("foo"); // avoid
+```
-## Merge timelines
+Put newlines around logical units of code, and after closing braces. If the
+most reasonable logical unit of code takes only a single line, it should be
+merged into the next single line logical unit if applicable.
+```cpp
+// multiple units
+auto x = ; // unit 1
+auto y = ; // unit 2
-We handle work for the most part on a push basis. If your PR has been ignored for a while
-and is still relevant please bump it.
+auto x = ; // unit 1
+emit this->y(); // unit 2
+
+auto x1 = ; // unit 1
+auto x2 = ; // unit 1
+auto x3 = ; // unit 1
+
+auto y1 = ; // unit 2
+auto y2 = ; // unit 2
+auto y3 = ; // unit 2
+
+// one unit
+auto x = ;
+if (x...) {
+ // ...
+}
+
+// if more than one variable needs to be used then add a newline
+auto x = ;
+auto y = ;
+
+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 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.
diff --git a/HACKING.md b/HACKING.md
deleted file mode 100644
index 69357f1..0000000
--- a/HACKING.md
+++ /dev/null
@@ -1,226 +0,0 @@
-## Development
-
-Install the dependencies listed in [BUILD.md](BUILD.md).
-You probably want all of them even if you don't use all of them
-to ensure tests work correctly and avoid passing a bunch of configure
-flags when you need to wipe the build directory.
-
-The dependencies are also available as a nix shell or nix flake which we recommend
-using with nix-direnv.
-
-Quickshell uses `just` for common development command aliases.
-
-Common aliases:
-- `just configure [ [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 = ; // 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 = ; // unit 1
-auto y = ; // unit 2
-
-auto x = ; // unit 1
-emit this->y(); // unit 2
-
-auto x1 = ; // unit 1
-auto x2 = ; // unit 1
-auto x3 = ; // unit 1
-
-auto y1 = ; // unit 2
-auto y2 = ; // unit 2
-auto y3 = ; // unit 2
-
-// one unit
-auto x = ;
-if (x...) {
- // ...
-}
-
-// if more than one variable needs to be used then add a newline
-auto x = ;
-auto y = ;
-
-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 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. `` over ``.
-
-### 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.
diff --git a/Justfile b/Justfile
index 801eb2a..2d6377e 100644
--- a/Justfile
+++ b/Justfile
@@ -13,7 +13,7 @@ lint-changed:
git diff --name-only HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
lint-staged:
- git diff --staged --name-only --diff-filter=d HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
+ git diff --staged --name-only HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }}
configure target='debug' *FLAGS='':
cmake -GNinja -B {{builddir}} \
diff --git a/README.md b/README.md
index 365bdb5..4491d24 100644
--- a/README.md
+++ b/README.md
@@ -7,9 +7,7 @@ This repo is hosted at:
- https://github.com/quickshell-mirror/quickshell
# Contributing / Development
-- [HACKING.md](HACKING.md) - Development instructions and policy.
-- [CONTRIBUTING.md](CONTRIBUTING.md) - Contribution policy.
-- [BUILD.md](BUILD.md) - Packaging and build instructions.
+See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
#### License
diff --git a/changelog/next.md b/changelog/next.md
index cceb79e..7180d53 100644
--- a/changelog/next.md
+++ b/changelog/next.md
@@ -26,18 +26,12 @@ 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
- FreeBSD is now partially supported.
- IPC operations filter available instances to the current display connection by default.
- PwNodeLinkTracker ignores sound level monitoring programs.
-- Replaced breakpad with cpptrace.
-- Reloads are prevented if no file content has changed.
-- Added `QS_DISABLE_FILE_WATCHER` environment variable to disable file watching.
-- Added `QS_DISABLE_CRASH_HANDLER` environment variable to disable crash handling.
-- Added `QS_CRASHREPORT_URL` environment variable to allow overriding the crash reporter link.
## Bug Fixes
@@ -48,23 +42,14 @@ set shell id.
- Fixed volumes not initializing if a pipewire device was already loaded before its node.
- Fixed hyprland active toplevel not resetting after window closes.
- Fixed hyprland ipc window names and titles being reversed.
-- Fixed a hyprland ipc crash when refreshing toplevels before workspaces.
- Fixed missing signals for system tray item title and description updates.
- Fixed asynchronous loaders not working after reload.
- Fixed asynchronous loaders not working before window creation.
- Fixed memory leak in IPC handlers.
- Fixed ClippingRectangle related crashes.
- Fixed crashes when monitors are unplugged.
-- Fixed crashes when default pipewire devices are lost.
-- Fixed ToplevelManager not clearing activeToplevel on deactivation.
-- Desktop action order is now preserved.
-- Fixed partial socket reads in greetd and hyprland on slow machines.
-- Worked around Qt bug causing crashes when plugging and unplugging monitors.
## Packaging Changes
-- `glib` and `polkit` have been added as dependencies when compiling with polkit agent support.
-- `vulkan-headers` has been added as a build-time dependency for screencopy (Vulkan backend support).
-- `breakpad` has been replaced by `cpptrace`, which is far easier to package, and the `CRASH_REPORTER` cmake variable has been replaced with `CRASH_HANDLER` to stop this from being easy to ignore.
-- `DISTRIBUTOR_DEBUGINFO_AVAILABLE` was removed as it is no longer important without breakpad.
-- `libdrm` is now unconditionally required as a direct dependency.
+`glib` and `polkit` have been added as dependencies when compiling with polkit agent support.
+`vulkan-headers` has been added as a build-time dependency for screencopy (Vulkan backend support).
diff --git a/default.nix b/default.nix
index 749ef49..7783774 100644
--- a/default.nix
+++ b/default.nix
@@ -10,16 +10,13 @@
ninja,
spirv-tools,
qt6,
- cpptrace ? null,
- libunwind,
- libdwarf,
+ breakpad,
jemalloc,
cli11,
wayland,
wayland-protocols,
wayland-scanner,
xorg,
- libxcb ? xorg.libxcb,
libdrm,
libgbm ? null,
vulkan-headers,
@@ -52,8 +49,6 @@
withPolkit ? true,
withNetworkManager ? true,
}: let
- withCrashHandler = withCrashReporter && cpptrace != null && lib.strings.compareVersions cpptrace.version "0.7.2" >= 0;
-
unwrapped = stdenv.mkDerivation {
pname = "quickshell${lib.optionalString debug "-debug"}";
version = "0.2.1";
@@ -76,21 +71,15 @@
buildInputs = [
qt6.qtbase
qt6.qtdeclarative
- libdrm
cli11
]
++ lib.optional withQtSvg qt6.qtsvg
- ++ lib.optional withCrashHandler (cpptrace.overrideAttrs (prev: {
- cmakeFlags = prev.cmakeFlags ++ [
- "-DCPPTRACE_UNWIND_WITH_LIBUNWIND=TRUE"
- ];
- buildInputs = prev.buildInputs ++ [ libunwind ];
- }))
+ ++ lib.optional withCrashReporter breakpad
++ lib.optional withJemalloc jemalloc
++ lib.optional (withWayland && lib.strings.compareVersions qt6.qtbase.version "6.10.0" == -1) qt6.qtwayland
++ lib.optionals withWayland [ wayland wayland-protocols ]
- ++ lib.optionals (withWayland && libgbm != null) [ libgbm vulkan-headers ]
- ++ lib.optional withX11 libxcb
+ ++ lib.optionals (withWayland && libgbm != null) [ libdrm libgbm vulkan-headers ]
+ ++ lib.optional withX11 xorg.libxcb
++ lib.optional withPam pam
++ lib.optional withPipewire pipewire
++ lib.optionals withPolkit [ polkit glib ];
@@ -102,7 +91,7 @@
(lib.cmakeFeature "INSTALL_QML_PREFIX" qt6.qtbase.qtQmlPrefix)
(lib.cmakeBool "DISTRIBUTOR_DEBUGINFO_AVAILABLE" true)
(lib.cmakeFeature "GIT_REVISION" gitRev)
- (lib.cmakeBool "CRASH_HANDLER" withCrashHandler)
+ (lib.cmakeBool "CRASH_REPORTER" withCrashReporter)
(lib.cmakeBool "USE_JEMALLOC" withJemalloc)
(lib.cmakeBool "WAYLAND" withWayland)
(lib.cmakeBool "SCREENCOPY" (libgbm != null))
diff --git a/quickshell.scm b/quickshell.scm
index 780bb96..3f82160 100644
--- a/quickshell.scm
+++ b/quickshell.scm
@@ -56,7 +56,8 @@
#~(list "-GNinja"
"-DDISTRIBUTOR=\"In-tree Guix channel\""
"-DDISTRIBUTOR_DEBUGINFO_AVAILABLE=NO"
- "-DCRASH_HANDLER=OFF")
+ ;; Breakpad is not currently packaged for Guix.
+ "-DCRASH_REPORTER=OFF")
#:phases
#~(modify-phases %standard-phases
(replace 'build (lambda _ (invoke "cmake" "--build" ".")))
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 0c05419..c95ecf7 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -11,9 +11,8 @@ add_subdirectory(window)
add_subdirectory(io)
add_subdirectory(widgets)
add_subdirectory(ui)
-add_subdirectory(windowmanager)
-if (CRASH_HANDLER)
+if (CRASH_REPORTER)
add_subdirectory(crash)
endif()
diff --git a/src/build/CMakeLists.txt b/src/build/CMakeLists.txt
index c1ffa59..bb35da9 100644
--- a/src/build/CMakeLists.txt
+++ b/src/build/CMakeLists.txt
@@ -9,10 +9,16 @@ if (NOT DEFINED GIT_REVISION)
)
endif()
-if (CRASH_HANDLER)
- set(CRASH_HANDLER_DEF 1)
+if (CRASH_REPORTER)
+ set(CRASH_REPORTER_DEF 1)
else()
- set(CRASH_HANDLER_DEF 0)
+ set(CRASH_REPORTER_DEF 0)
+endif()
+
+if (DISTRIBUTOR_DEBUGINFO_AVAILABLE)
+ set(DEBUGINFO_AVAILABLE 1)
+else()
+ set(DEBUGINFO_AVAILABLE 0)
endif()
configure_file(build.hpp.in build.hpp @ONLY ESCAPE_QUOTES)
diff --git a/src/build/build.hpp.in b/src/build/build.hpp.in
index acc3c58..66fb664 100644
--- a/src/build/build.hpp.in
+++ b/src/build/build.hpp.in
@@ -8,10 +8,10 @@
#define QS_UNRELEASED_FEATURES "@UNRELEASED_FEATURES@"
#define GIT_REVISION "@GIT_REVISION@"
#define DISTRIBUTOR "@DISTRIBUTOR@"
-#define CRASH_HANDLER @CRASH_HANDLER_DEF@
+#define DISTRIBUTOR_DEBUGINFO_AVAILABLE @DEBUGINFO_AVAILABLE@
+#define CRASH_REPORTER @CRASH_REPORTER_DEF@
#define BUILD_TYPE "@CMAKE_BUILD_TYPE@"
#define COMPILER "@CMAKE_CXX_COMPILER_ID@ (@CMAKE_CXX_COMPILER_VERSION@)"
#define COMPILE_FLAGS "@CMAKE_CXX_FLAGS@"
#define BUILD_CONFIGURATION "@QS_BUILD_OPTIONS@"
-#define CRASHREPORT_URL "@CRASHREPORT_URL@"
// NOLINTEND
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 4824965..fb63f40 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -1,4 +1,3 @@
-pkg_check_modules(libdrm REQUIRED IMPORTED_TARGET libdrm)
qt_add_library(quickshell-core STATIC
plugin.cpp
shell.cpp
@@ -41,8 +40,6 @@ qt_add_library(quickshell-core STATIC
scriptmodel.cpp
colorquantizer.cpp
toolsupport.cpp
- streamreader.cpp
- debuginfo.cpp
)
qt_add_qml_module(quickshell-core
@@ -55,7 +52,7 @@ qt_add_qml_module(quickshell-core
install_qml_module(quickshell-core)
-target_link_libraries(quickshell-core PRIVATE Qt::Quick Qt::QuickPrivate Qt::Widgets quickshell-build PkgConfig::libdrm)
+target_link_libraries(quickshell-core PRIVATE Qt::Quick Qt::QuickPrivate Qt::Widgets quickshell-build)
qs_module_pch(quickshell-core SET large)
diff --git a/src/core/debuginfo.cpp b/src/core/debuginfo.cpp
deleted file mode 100644
index ae227f8..0000000
--- a/src/core/debuginfo.cpp
+++ /dev/null
@@ -1,175 +0,0 @@
-#include "debuginfo.hpp"
-#include
-#include
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#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 "";
- auto fdGuard = qScopeGuard([&] { close(fd); });
- auto* ver = drmGetVersion(fd);
- if (!ver) return "";
- 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 {
- "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
diff --git a/src/core/debuginfo.hpp b/src/core/debuginfo.hpp
deleted file mode 100644
index fc766fc..0000000
--- a/src/core/debuginfo.hpp
+++ /dev/null
@@ -1,14 +0,0 @@
-#pragma once
-
-#include
-
-namespace qs::debuginfo {
-
-QString qsVersion();
-QString qtVersion();
-QString gpuInfo();
-QString systemInfo();
-QString envInfo();
-QString combinedInfo();
-
-} // namespace qs::debuginfo
diff --git a/src/core/desktopentry.cpp b/src/core/desktopentry.cpp
index 637f758..2dbafea 100644
--- a/src/core/desktopentry.cpp
+++ b/src/core/desktopentry.cpp
@@ -107,10 +107,7 @@ ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString&
auto groupName = QString();
auto entries = QHash>();
- auto actionOrder = QStringList();
- auto pendingActions = QHash();
-
- auto finishCategory = [&data, &groupName, &entries, &actionOrder, &pendingActions]() {
+ auto finishCategory = [&data, &groupName, &entries]() {
if (groupName == "Desktop Entry") {
if (entries.value("Type").second != "Application") return;
@@ -132,10 +129,9 @@ ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString&
else if (key == "Terminal") data.terminal = value == "true";
else if (key == "Categories") data.categories = value.split(u';', Qt::SkipEmptyParts);
else if (key == "Keywords") data.keywords = value.split(u';', Qt::SkipEmptyParts);
- else if (key == "Actions") actionOrder = value.split(u';', Qt::SkipEmptyParts);
}
} else if (groupName.startsWith("Desktop Action ")) {
- auto actionName = groupName.sliced(15);
+ auto actionName = groupName.sliced(16);
DesktopActionData action;
action.id = actionName;
@@ -151,7 +147,7 @@ ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString&
}
}
- pendingActions.insert(actionName, action);
+ data.actions.insert(actionName, action);
}
entries.clear();
@@ -197,13 +193,6 @@ ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString&
}
finishCategory();
-
- for (const auto& actionId: actionOrder) {
- if (pendingActions.contains(actionId)) {
- data.actions.append(pendingActions.value(actionId));
- }
- }
-
return data;
}
@@ -227,18 +216,17 @@ void DesktopEntry::updateState(const ParsedDesktopEntryData& newState) {
this->updateActions(newState.actions);
}
-void DesktopEntry::updateActions(const QVector& newActions) {
+void DesktopEntry::updateActions(const QHash& newActions) {
auto old = this->mActions;
- this->mActions.clear();
- for (const auto& d: newActions) {
+ for (const auto& [key, d]: newActions.asKeyValueRange()) {
DesktopAction* act = nullptr;
- auto found = std::ranges::find(old, d.id, &DesktopAction::mId);
- if (found != old.end()) {
- act = *found;
+ if (auto found = old.find(key); found != old.end()) {
+ act = found.value();
old.erase(found);
} else {
act = new DesktopAction(d.id, this);
+ this->mActions.insert(key, act);
}
Qt::beginPropertyUpdateGroup();
@@ -249,7 +237,6 @@ void DesktopEntry::updateActions(const QVector& newActions) {
Qt::endPropertyUpdateGroup();
act->mEntries = d.entries;
- this->mActions.append(act);
}
for (auto* leftover: old) {
@@ -263,7 +250,7 @@ void DesktopEntry::execute() const {
bool DesktopEntry::isValid() const { return !this->bName.value().isEmpty(); }
-QVector DesktopEntry::actions() const { return this->mActions; }
+QVector DesktopEntry::actions() const { return this->mActions.values(); }
QVector DesktopEntry::parseExecString(const QString& execString) {
QVector arguments;
diff --git a/src/core/desktopentry.hpp b/src/core/desktopentry.hpp
index 0d1eff2..623019d 100644
--- a/src/core/desktopentry.hpp
+++ b/src/core/desktopentry.hpp
@@ -43,7 +43,7 @@ struct ParsedDesktopEntryData {
QVector categories;
QVector keywords;
QHash entries;
- QVector actions;
+ QHash actions;
};
/// A desktop entry. See @@DesktopEntries for details.
@@ -164,10 +164,10 @@ public:
// clang-format on
private:
- void updateActions(const QVector& newActions);
+ void updateActions(const QHash& newActions);
ParsedDesktopEntryData state;
- QVector mActions;
+ QHash mActions;
friend class DesktopAction;
};
diff --git a/src/core/generation.cpp b/src/core/generation.cpp
index 21febc3..c68af71 100644
--- a/src/core/generation.cpp
+++ b/src/core/generation.cpp
@@ -209,8 +209,6 @@ bool EngineGeneration::setExtraWatchedFiles(const QVector& files) {
for (const auto& file: files) {
if (!this->scanner.scannedFiles.contains(file)) {
this->extraWatchedFiles.append(file);
- QByteArray data;
- this->scanner.readAndHashFile(file, data);
}
}
@@ -231,11 +229,6 @@ void EngineGeneration::onFileChanged(const QString& name) {
auto fileInfo = QFileInfo(name);
if (fileInfo.isFile() && fileInfo.size() == 0) return;
- if (!this->scanner.hasFileContentChanged(name)) {
- qCDebug(logQmlScanner) << "Ignoring file change with unchanged content:" << name;
- return;
- }
-
emit this->filesChanged();
}
}
@@ -244,11 +237,6 @@ void EngineGeneration::onDirectoryChanged() {
// try to find any files that were just deleted from a replace operation
for (auto& file: this->deletedWatchedFiles) {
if (QFileInfo(file).exists()) {
- if (!this->scanner.hasFileContentChanged(file)) {
- qCDebug(logQmlScanner) << "Ignoring restored file with unchanged content:" << file;
- continue;
- }
-
emit this->filesChanged();
break;
}
diff --git a/src/core/instanceinfo.hpp b/src/core/instanceinfo.hpp
index 977e4c2..d462f6e 100644
--- a/src/core/instanceinfo.hpp
+++ b/src/core/instanceinfo.hpp
@@ -35,8 +35,6 @@ namespace qs::crash {
struct CrashInfo {
int logFd = -1;
- int traceFd = -1;
- int infoFd = -1;
static CrashInfo INSTANCE; // NOLINT
};
diff --git a/src/core/logging.cpp b/src/core/logging.cpp
index 893c56e..d24225b 100644
--- a/src/core/logging.cpp
+++ b/src/core/logging.cpp
@@ -31,9 +31,6 @@
#include
#include
#endif
-#ifdef __FreeBSD__
-#include
-#endif
#include "instanceinfo.hpp"
#include "logcat.hpp"
@@ -70,7 +67,7 @@ bool copyFileData(int sourceFd, int destFd, qint64 size) {
return true;
#else
std::array buffer = {};
- auto remaining = usize;
+ auto remaining = totalTarget;
while (remaining > 0) {
auto chunk = std::min(remaining, buffer.size());
diff --git a/src/core/qmlglobal.cpp b/src/core/qmlglobal.cpp
index 35504f6..6c26609 100644
--- a/src/core/qmlglobal.cpp
+++ b/src/core/qmlglobal.cpp
@@ -60,9 +60,7 @@ void QuickshellSettings::setWorkingDirectory(QString workingDirectory) { // NOLI
emit this->workingDirectoryChanged();
}
-bool QuickshellSettings::watchFiles() const {
- return this->mWatchFiles && qEnvironmentVariableIsEmpty("QS_DISABLE_FILE_WATCHER");
-}
+bool QuickshellSettings::watchFiles() const { return this->mWatchFiles; }
void QuickshellSettings::setWatchFiles(bool watchFiles) {
if (watchFiles == this->mWatchFiles) return;
diff --git a/src/core/scan.cpp b/src/core/scan.cpp
index 58da38c..37b0fac 100644
--- a/src/core/scan.cpp
+++ b/src/core/scan.cpp
@@ -3,7 +3,6 @@
#include
#include
-#include
#include
#include
#include
@@ -22,25 +21,6 @@
QS_LOGGING_CATEGORY(logQmlScanner, "quickshell.qmlscanner", QtWarningMsg);
-bool QmlScanner::readAndHashFile(const QString& path, QByteArray& data) {
- auto file = QFile(path);
- if (!file.open(QFile::ReadOnly)) return false;
- data = file.readAll();
- this->fileHashes.insert(path, QCryptographicHash::hash(data, QCryptographicHash::Md5));
- return true;
-}
-
-bool QmlScanner::hasFileContentChanged(const QString& path) const {
- auto it = this->fileHashes.constFind(path);
- if (it == this->fileHashes.constEnd()) return true;
-
- auto file = QFile(path);
- if (!file.open(QFile::ReadOnly)) return true;
-
- auto newHash = QCryptographicHash::hash(file.readAll(), QCryptographicHash::Md5);
- return newHash != it.value();
-}
-
void QmlScanner::scanDir(const QDir& dir) {
if (this->scannedDirs.contains(dir)) return;
this->scannedDirs.push_back(dir);
@@ -129,13 +109,13 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
qCDebug(logQmlScanner) << "Scanning qml file" << path;
- QByteArray fileData;
- if (!this->readAndHashFile(path, fileData)) {
+ auto file = QFile(path);
+ if (!file.open(QFile::ReadOnly | QFile::Text)) {
qCWarning(logQmlScanner) << "Failed to open file" << path;
return false;
}
- auto stream = QTextStream(&fileData);
+ auto stream = QTextStream(&file);
auto imports = QVector();
bool inHeader = true;
@@ -239,6 +219,8 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
postError("unclosed preprocessor if block");
}
+ file.close();
+
if (isOverridden) {
this->fileIntercepts.insert(path, overrideText);
}
@@ -275,11 +257,8 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna
continue;
}
- if (import.endsWith(".js")) {
- this->scannedFiles.push_back(cpath);
- QByteArray jsData;
- this->readAndHashFile(cpath, jsData);
- } else this->scanDir(cpath);
+ if (import.endsWith(".js")) this->scannedFiles.push_back(cpath);
+ else this->scanDir(cpath);
}
return true;
@@ -294,12 +273,14 @@ void QmlScanner::scanQmlRoot(const QString& path) {
bool QmlScanner::scanQmlJson(const QString& path) {
qCDebug(logQmlScanner) << "Scanning qml.json file" << path;
- QByteArray data;
- if (!this->readAndHashFile(path, data)) {
+ auto file = QFile(path);
+ if (!file.open(QFile::ReadOnly | QFile::Text)) {
qCWarning(logQmlScanner) << "Failed to open file" << path;
return false;
}
+ auto data = file.readAll();
+
// Importing this makes CI builds fail for some reason.
QJsonParseError error; // NOLINT (misc-include-cleaner)
auto json = QJsonDocument::fromJson(data, &error);
diff --git a/src/core/scan.hpp b/src/core/scan.hpp
index 26034e1..29f8f6a 100644
--- a/src/core/scan.hpp
+++ b/src/core/scan.hpp
@@ -1,6 +1,5 @@
#pragma once
-#include
#include
#include
#include
@@ -22,7 +21,6 @@ public:
QVector scannedDirs;
QVector scannedFiles;
- QHash fileHashes;
QHash fileIntercepts;
struct ScanError {
@@ -33,9 +31,6 @@ public:
QVector scanErrors;
- bool readAndHashFile(const QString& path, QByteArray& data);
- [[nodiscard]] bool hasFileContentChanged(const QString& path) const;
-
private:
QDir rootPath;
diff --git a/src/core/streamreader.cpp b/src/core/streamreader.cpp
deleted file mode 100644
index 1f66e29..0000000
--- a/src/core/streamreader.cpp
+++ /dev/null
@@ -1,98 +0,0 @@
-#include "streamreader.hpp"
-#include
-
-#include
-#include
-#include
-
-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(&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;
-}
diff --git a/src/core/streamreader.hpp b/src/core/streamreader.hpp
deleted file mode 100644
index abf14ef..0000000
--- a/src/core/streamreader.hpp
+++ /dev/null
@@ -1,26 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-
-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;
-};
diff --git a/src/core/util.hpp b/src/core/util.hpp
index bb8dd85..3b86d28 100644
--- a/src/core/util.hpp
+++ b/src/core/util.hpp
@@ -251,6 +251,37 @@ public:
GuardedEmitBlocker block() { return GuardedEmitBlocker(&this->blocked); }
};
+template
+class SimpleObjectHandleOps {
+ using Traits = MemberPointerTraits;
+
+public:
+ static bool setObject(Traits::Class* parent, Traits::Type value) {
+ if (value == parent->*member) return false;
+
+ if (parent->*member != nullptr) {
+ QObject::disconnect(parent->*member, &QObject::destroyed, parent, destroyedSlot);
+ }
+
+ parent->*member = value;
+
+ if (value != nullptr) {
+ QObject::connect(parent->*member, &QObject::destroyed, parent, destroyedSlot);
+ }
+
+ if constexpr (changedSignal != nullptr) {
+ emit(parent->*changedSignal)();
+ }
+
+ return true;
+ }
+};
+
+template
+bool setSimpleObjectHandle(auto* parent, auto* value) {
+ return SimpleObjectHandleOps::setObject(parent, value);
+}
+
template
class MethodFunctor {
using PtrMeta = MemberPointerTraits;
diff --git a/src/crash/CMakeLists.txt b/src/crash/CMakeLists.txt
index a891ee9..7fdd830 100644
--- a/src/crash/CMakeLists.txt
+++ b/src/crash/CMakeLists.txt
@@ -6,51 +6,12 @@ qt_add_library(quickshell-crash STATIC
qs_pch(quickshell-crash SET large)
-if (VENDOR_CPPTRACE)
- message(STATUS "Vendoring cpptrace...")
- include(FetchContent)
-
- # For use without internet access see: https://cmake.org/cmake/help/latest/module/FetchContent.html#variable:FETCHCONTENT_SOURCE_DIR_%3CuppercaseName%3E
- FetchContent_Declare(
- cpptrace
- GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git
- GIT_TAG v1.0.4
- )
-
- set(CPPTRACE_UNWIND_WITH_LIBUNWIND TRUE)
- FetchContent_MakeAvailable(cpptrace)
-else ()
- find_package(cpptrace REQUIRED)
-
- # useful for cross after you have already checked cpptrace is built correctly
- if (NOT DO_NOT_CHECK_CPPTRACE_USABILITY)
- try_run(CPPTRACE_SIGNAL_SAFE_UNWIND CPPTRACE_SIGNAL_SAFE_UNWIND_COMP
- SOURCE_FROM_CONTENT check.cxx "
- #include
- int main() {
- return cpptrace::can_signal_safe_unwind() ? 0 : 1;
- }
- "
- LOG_DESCRIPTION "Checking ${CPPTRACE_SIGNAL_SAFE_UNWIND}"
- LINK_LIBRARIES cpptrace::cpptrace
- COMPILE_OUTPUT_VARIABLE CPPTRACE_SIGNAL_SAFE_UNWIND_LOG
- CXX_STANDARD 20
- CXX_STANDARD_REQUIRED ON
- )
-
- if (NOT CPPTRACE_SIGNAL_SAFE_UNWIND_COMP)
- message(STATUS "${CPPTRACE_SIGNAL_SAFE_UNWIND_LOG}")
- message(FATAL_ERROR "Failed to compile cpptrace signal safe unwind tester.")
- endif()
-
- if (NOT CPPTRACE_SIGNAL_SAFE_UNWIND EQUAL 0)
- message(STATUS "Cpptrace signal safe unwind test exited with: ${CPPTRACE_SIGNAL_SAFE_UNWIND}")
- message(FATAL_ERROR "Cpptrace was built without CPPTRACE_UNWIND_WITH_LIBUNWIND set to true. Enable libunwind support in the package or set VENDOR_CPPTRACE to true when building Quickshell.")
- endif()
- endif ()
-endif ()
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(breakpad REQUIRED IMPORTED_TARGET breakpad)
+# only need client?? take only includes from pkg config todo
+target_link_libraries(quickshell-crash PRIVATE PkgConfig::breakpad -lbreakpad_client)
# quick linked for pch compat
-target_link_libraries(quickshell-crash PRIVATE quickshell-build Qt::Quick Qt::Widgets cpptrace::cpptrace)
+target_link_libraries(quickshell-crash PRIVATE quickshell-build Qt::Quick Qt::Widgets)
target_link_libraries(quickshell PRIVATE quickshell-crash)
diff --git a/src/crash/handler.cpp b/src/crash/handler.cpp
index 8f37085..0baa8e6 100644
--- a/src/crash/handler.cpp
+++ b/src/crash/handler.cpp
@@ -1,13 +1,12 @@
#include "handler.hpp"
-#include
#include
-#include
-#include
#include
#include
-#include
-#include
+#include
+#include
+#include
+#include
#include
#include
#include
@@ -20,76 +19,99 @@
extern char** environ; // NOLINT
+using namespace google_breakpad;
+
namespace qs::crash {
namespace {
-
QS_LOGGING_CATEGORY(logCrashHandler, "quickshell.crashhandler", QtWarningMsg);
+}
-void writeEnvInt(char* buf, const char* name, int value) {
- // NOLINTBEGIN (cppcoreguidelines-pro-bounds-pointer-arithmetic)
- while (*name != '\0') *buf++ = *name++;
- *buf++ = '=';
+struct CrashHandlerPrivate {
+ ExceptionHandler* exceptionHandler = nullptr;
+ int minidumpFd = -1;
+ int infoFd = -1;
- if (value < 0) {
- *buf++ = '-';
- value = -value;
+ static bool minidumpCallback(const MinidumpDescriptor& descriptor, void* context, bool succeeded);
+};
+
+CrashHandler::CrashHandler(): d(new CrashHandlerPrivate()) {}
+
+void CrashHandler::init() {
+ // MinidumpDescriptor has no move constructor and the copy constructor breaks fds.
+ auto createHandler = [this](const MinidumpDescriptor& desc) {
+ this->d->exceptionHandler = new ExceptionHandler(
+ desc,
+ nullptr,
+ &CrashHandlerPrivate::minidumpCallback,
+ this->d,
+ true,
+ -1
+ );
+ };
+
+ qCDebug(logCrashHandler) << "Starting crash handler...";
+
+ this->d->minidumpFd = memfd_create("quickshell:minidump", MFD_CLOEXEC);
+
+ if (this->d->minidumpFd == -1) {
+ qCCritical(
+ logCrashHandler
+ ) << "Failed to allocate minidump memfd, minidumps will be saved in the working directory.";
+ createHandler(MinidumpDescriptor("."));
+ } else {
+ qCDebug(logCrashHandler) << "Created memfd" << this->d->minidumpFd
+ << "for holding possible minidumps.";
+ createHandler(MinidumpDescriptor(this->d->minidumpFd));
}
- if (value == 0) {
- *buf++ = '0';
- *buf = '\0';
+ qCInfo(logCrashHandler) << "Crash handler initialized.";
+}
+
+void CrashHandler::setRelaunchInfo(const RelaunchInfo& info) {
+ this->d->infoFd = memfd_create("quickshell:instance_info", MFD_CLOEXEC);
+
+ if (this->d->infoFd == -1) {
+ qCCritical(
+ logCrashHandler
+ ) << "Failed to allocate instance info memfd, crash recovery will not work.";
return;
}
- auto* start = buf;
- while (value > 0) {
- *buf++ = static_cast('0' + (value % 10));
- value /= 10;
+ QFile file;
+
+ if (!file.open(this->d->infoFd, QFile::ReadWrite)) {
+ qCCritical(
+ logCrashHandler
+ ) << "Failed to open instance info memfd, crash recovery will not work.";
}
- *buf = '\0';
- std::reverse(start, buf);
- // NOLINTEND
+ QDataStream ds(&file);
+ ds << info;
+ file.flush();
+
+ qCDebug(logCrashHandler) << "Stored instance info in memfd" << this->d->infoFd;
}
-void signalHandler(
- int sig,
- siginfo_t* /*info*/, // NOLINT (misc-include-cleaner)
- void* /*context*/
+CrashHandler::~CrashHandler() {
+ delete this->d->exceptionHandler;
+ delete this->d;
+}
+
+bool CrashHandlerPrivate::minidumpCallback(
+ const MinidumpDescriptor& /*descriptor*/,
+ void* context,
+ bool /*success*/
) {
- if (CrashInfo::INSTANCE.traceFd != -1) {
- auto traceBuffer = std::array();
- auto frameCount = cpptrace::safe_generate_raw_trace(traceBuffer.data(), traceBuffer.size(), 1);
-
- for (size_t i = 0; i < static_cast(frameCount); i++) {
- auto frame = cpptrace::safe_object_frame();
- cpptrace::get_safe_object_frame(traceBuffer[i], &frame);
-
- auto* wptr = reinterpret_cast(&frame);
- auto* end = wptr + sizeof(cpptrace::safe_object_frame); // NOLINT
- while (wptr != end) {
- auto r = write(CrashInfo::INSTANCE.traceFd, &frame, sizeof(cpptrace::safe_object_frame));
- if (r < 0 && errno == EINTR) continue;
- if (r <= 0) goto fail;
- wptr += r; // NOLINT
- }
- }
-
- fail:;
- }
-
+ // A fork that just dies to ensure the coredump is caught by the system.
auto coredumpPid = fork();
+
if (coredumpPid == 0) {
- // NOLINTBEGIN (misc-include-cleaner)
- sigset_t set;
- sigfillset(&set);
- sigprocmask(SIG_UNBLOCK, &set, nullptr);
- // NOLINTEND
- raise(sig);
- _exit(-1);
+ return false;
}
+ auto* self = static_cast(context);
+
auto exe = std::array();
if (readlink("/proc/self/exe", exe.data(), exe.size() - 1) == -1) {
perror("Failed to find crash reporter executable.\n");
@@ -101,19 +123,17 @@ void signalHandler(
auto env = std::array();
auto envi = 0;
- // dup to remove CLOEXEC
- auto infoFdStr = std::array();
- writeEnvInt(infoFdStr.data(), "__QUICKSHELL_CRASH_INFO_FD", dup(CrashInfo::INSTANCE.infoFd));
+ auto infoFd = dup(self->infoFd);
+ auto infoFdStr = std::array();
+ memcpy(infoFdStr.data(), "__QUICKSHELL_CRASH_INFO_FD=-1" /*\0*/, 30);
+ if (infoFd != -1) my_uitos(&infoFdStr[27], infoFd, 10);
env[envi++] = infoFdStr.data();
- auto corePidStr = std::array();
- writeEnvInt(corePidStr.data(), "__QUICKSHELL_CRASH_DUMP_PID", coredumpPid);
+ auto corePidStr = std::array();
+ memcpy(corePidStr.data(), "__QUICKSHELL_CRASH_DUMP_PID=-1" /*\0*/, 31);
+ if (coredumpPid != -1) my_uitos(&corePidStr[28], coredumpPid, 10);
env[envi++] = corePidStr.data();
- auto sigStr = std::array();
- writeEnvInt(sigStr.data(), "__QUICKSHELL_CRASH_SIGNAL", sig);
- env[envi++] = sigStr.data();
-
auto populateEnv = [&]() {
auto senvi = 0;
while (envi != 4095) {
@@ -125,18 +145,30 @@ void signalHandler(
env[envi] = nullptr;
};
+ sigset_t sigset;
+ sigemptyset(&sigset); // NOLINT (include)
+ sigprocmask(SIG_SETMASK, &sigset, nullptr); // NOLINT
+
auto pid = fork();
if (pid == -1) {
perror("Failed to fork and launch crash reporter.\n");
- _exit(-1);
+ return false;
} else if (pid == 0) {
-
// dup to remove CLOEXEC
- auto dumpFdStr = std::array();
- auto logFdStr = std::array();
- writeEnvInt(dumpFdStr.data(), "__QUICKSHELL_CRASH_DUMP_FD", dup(CrashInfo::INSTANCE.traceFd));
- writeEnvInt(logFdStr.data(), "__QUICKSHELL_CRASH_LOG_FD", dup(CrashInfo::INSTANCE.logFd));
+ // if already -1 will return -1
+ auto dumpFd = dup(self->minidumpFd);
+ auto logFd = dup(CrashInfo::INSTANCE.logFd);
+
+ // allow up to 10 digits, which should never happen
+ auto dumpFdStr = std::array();
+ auto logFdStr = std::array();
+
+ memcpy(dumpFdStr.data(), "__QUICKSHELL_CRASH_DUMP_FD=-1" /*\0*/, 30);
+ memcpy(logFdStr.data(), "__QUICKSHELL_CRASH_LOG_FD=-1" /*\0*/, 29);
+
+ if (dumpFd != -1) my_uitos(&dumpFdStr[27], dumpFd, 10);
+ if (logFd != -1) my_uitos(&logFdStr[26], logFd, 10);
env[envi++] = dumpFdStr.data();
env[envi++] = logFdStr.data();
@@ -153,82 +185,8 @@ void signalHandler(
perror("Failed to relaunch quickshell.\n");
_exit(-1);
}
-}
-} // namespace
-
-void CrashHandler::init() {
- qCDebug(logCrashHandler) << "Starting crash handler...";
-
- CrashInfo::INSTANCE.traceFd = memfd_create("quickshell:trace", MFD_CLOEXEC);
-
- if (CrashInfo::INSTANCE.traceFd == -1) {
- qCCritical(logCrashHandler) << "Failed to allocate trace memfd, stack traces will not be "
- "available in crash reports.";
- } else {
- qCDebug(logCrashHandler) << "Created memfd" << CrashInfo::INSTANCE.traceFd
- << "for holding possible stack traces.";
- }
-
- {
- // Preload anything dynamically linked to avoid malloc etc in the dynamic loader.
- // See cpptrace documentation for more information.
- auto buffer = std::array();
- cpptrace::safe_generate_raw_trace(buffer.data(), buffer.size());
- auto frame = cpptrace::safe_object_frame();
- cpptrace::get_safe_object_frame(buffer[0], &frame);
- }
-
- // NOLINTBEGIN (misc-include-cleaner)
-
- // Set up alternate signal stack for stack overflow handling
- auto ss = stack_t();
- ss.ss_sp = new char[SIGSTKSZ];
- ss.ss_size = SIGSTKSZ;
- ss.ss_flags = 0;
- sigaltstack(&ss, nullptr);
-
- // Install signal handlers
- struct sigaction sa {};
- sa.sa_sigaction = &signalHandler;
- sa.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESETHAND;
- sigemptyset(&sa.sa_mask);
-
- sigaction(SIGSEGV, &sa, nullptr);
- sigaction(SIGABRT, &sa, nullptr);
- sigaction(SIGFPE, &sa, nullptr);
- sigaction(SIGILL, &sa, nullptr);
- sigaction(SIGBUS, &sa, nullptr);
- sigaction(SIGTRAP, &sa, nullptr);
-
- // NOLINTEND (misc-include-cleaner)
-
- qCInfo(logCrashHandler) << "Crash handler initialized.";
-}
-
-void CrashHandler::setRelaunchInfo(const RelaunchInfo& info) {
- CrashInfo::INSTANCE.infoFd = memfd_create("quickshell:instance_info", MFD_CLOEXEC);
-
- if (CrashInfo::INSTANCE.infoFd == -1) {
- qCCritical(
- logCrashHandler
- ) << "Failed to allocate instance info memfd, crash recovery will not work.";
- return;
- }
-
- QFile file;
-
- if (!file.open(CrashInfo::INSTANCE.infoFd, QFile::ReadWrite)) {
- qCCritical(
- logCrashHandler
- ) << "Failed to open instance info memfd, crash recovery will not work.";
- }
-
- QDataStream ds(&file);
- ds << info;
- file.flush();
-
- qCDebug(logCrashHandler) << "Stored instance info in memfd" << CrashInfo::INSTANCE.infoFd;
+ return false; // should make sure it hits the system coredump handler
}
} // namespace qs::crash
diff --git a/src/crash/handler.hpp b/src/crash/handler.hpp
index 9488d71..2a1d86f 100644
--- a/src/crash/handler.hpp
+++ b/src/crash/handler.hpp
@@ -5,10 +5,19 @@
#include "../core/instanceinfo.hpp"
namespace qs::crash {
+struct CrashHandlerPrivate;
+
class CrashHandler {
public:
- static void init();
- static void setRelaunchInfo(const RelaunchInfo& info);
+ explicit CrashHandler();
+ ~CrashHandler();
+ Q_DISABLE_COPY_MOVE(CrashHandler);
+
+ void init();
+ void setRelaunchInfo(const RelaunchInfo& info);
+
+private:
+ CrashHandlerPrivate* d;
};
} // namespace qs::crash
diff --git a/src/crash/interface.cpp b/src/crash/interface.cpp
index 6a370ce..326216a 100644
--- a/src/crash/interface.cpp
+++ b/src/crash/interface.cpp
@@ -5,7 +5,6 @@
#include
#include
#include
-#include
#include
#include
#include
@@ -13,22 +12,11 @@
#include
#include
#include
-#include
#include
#include
#include "build.hpp"
-namespace {
-QString crashreportUrl() {
- if (auto url = qEnvironmentVariable("QS_CRASHREPORT_URL"); !url.isEmpty()) {
- return url;
- }
-
- return CRASHREPORT_URL;
-}
-} // namespace
-
class ReportLabel: public QWidget {
public:
ReportLabel(const QString& label, const QString& content, QWidget* parent): QWidget(parent) {
@@ -79,16 +67,22 @@ CrashReporterGui::CrashReporterGui(QString reportFolder, int pid)
if (qtVersionMatches) {
mainLayout->addWidget(
- new QLabel("Please open a bug report for this issue on the issue tracker.")
+ new QLabel("Please open a bug report for this issue via github or email.")
);
} else {
mainLayout->addWidget(new QLabel(
"Please rebuild Quickshell against the current Qt version.\n"
- "If this does not solve the problem, please open a bug report on the issue tracker."
+ "If this does not solve the problem, please open a bug report via github or email."
));
}
- mainLayout->addWidget(new ReportLabel("Tracker:", crashreportUrl(), this));
+ mainLayout->addWidget(new ReportLabel(
+ "Github:",
+ "https://github.com/quickshell-mirror/quickshell/issues/new?template=crash.yml",
+ this
+ ));
+
+ mainLayout->addWidget(new ReportLabel("Email:", "quickshell-bugs@outfoxxed.me", this));
auto* buttons = new QWidget(this);
buttons->setMinimumWidth(900);
@@ -118,5 +112,10 @@ void CrashReporterGui::openFolder() {
QDesktopServices::openUrl(QUrl::fromLocalFile(this->reportFolder));
}
-void CrashReporterGui::openReportUrl() { QDesktopServices::openUrl(QUrl(crashreportUrl())); }
+void CrashReporterGui::openReportUrl() {
+ QDesktopServices::openUrl(
+ QUrl("https://github.com/outfoxxed/quickshell/issues/new?template=crash.yml")
+ );
+}
+
void CrashReporterGui::cancel() { QApplication::quit(); }
diff --git a/src/crash/main.cpp b/src/crash/main.cpp
index 05927f2..6571660 100644
--- a/src/crash/main.cpp
+++ b/src/crash/main.cpp
@@ -1,11 +1,9 @@
#include "main.hpp"
#include
#include
-#include
-#include
-#include
#include
+#include
#include
#include
#include
@@ -14,18 +12,15 @@
#include
#include
#include
-#include
+#include
#include
#include
-#include
-#include "../core/debuginfo.hpp"
#include "../core/instanceinfo.hpp"
#include "../core/logcat.hpp"
#include "../core/logging.hpp"
-#include "../core/logging_p.hpp"
#include "../core/paths.hpp"
-#include "../core/ringbuf.hpp"
+#include "build.hpp"
#include "interface.hpp"
namespace {
@@ -66,76 +61,6 @@ int tryDup(int fd, const QString& path) {
return 0;
}
-QString readRecentLogs(int logFd, int maxLines, qint64 maxAgeSecs) {
- QFile file;
- if (!file.open(logFd, QFile::ReadOnly, QFile::AutoCloseHandle)) {
- return QStringLiteral("(failed to open log fd)\n");
- }
-
- file.seek(0);
-
- qs::log::EncodedLogReader reader;
- reader.setDevice(&file);
-
- bool readable = false;
- quint8 logVersion = 0;
- quint8 readerVersion = 0;
- if (!reader.readHeader(&readable, &logVersion, &readerVersion) || !readable) {
- return QStringLiteral("(failed to read log header)\n");
- }
-
- // Read all messages, keeping last maxLines in a ring buffer
- auto tail = RingBuffer(maxLines);
- qs::log::LogMessage message;
- while (reader.read(&message)) {
- tail.emplace(message);
- }
-
- if (tail.size() == 0) {
- return QStringLiteral("(no logs)\n");
- }
-
- // Filter to only messages within maxAgeSecs of the newest message
- auto cutoff = tail.at(0).time.addSecs(-maxAgeSecs);
-
- QString result;
- auto stream = QTextStream(&result);
- for (auto i = tail.size() - 1; i != -1; i--) {
- if (tail.at(i).time < cutoff) continue;
- qs::log::LogMessage::formatMessage(stream, tail.at(i), false, true);
- stream << '\n';
- }
-
- if (result.isEmpty()) {
- return QStringLiteral("(no recent logs)\n");
- }
-
- return result;
-}
-
-cpptrace::stacktrace resolveStacktrace(int dumpFd) {
- QFile sourceFile;
- if (!sourceFile.open(dumpFd, QFile::ReadOnly, QFile::AutoCloseHandle)) {
- qCCritical(logCrashReporter) << "Failed to open trace memfd.";
- return {};
- }
-
- sourceFile.seek(0);
- auto data = sourceFile.readAll();
-
- auto frameCount = static_cast(data.size()) / sizeof(cpptrace::safe_object_frame);
- if (frameCount == 0) return {};
-
- const auto* frames = reinterpret_cast(data.constData());
-
- cpptrace::object_trace objectTrace;
- for (size_t i = 0; i < frameCount; i++) {
- objectTrace.frames.push_back(frames[i].resolve()); // NOLINT
- }
-
- return objectTrace.resolve();
-}
-
void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instance) {
qCDebug(logCrashReporter) << "Recording crash information at" << crashDir.path();
@@ -146,49 +71,74 @@ void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instance) {
}
auto crashProc = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_PID").toInt();
- auto crashSignal = qEnvironmentVariable("__QUICKSHELL_CRASH_SIGNAL").toInt();
auto dumpFd = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_FD").toInt();
auto logFd = qEnvironmentVariable("__QUICKSHELL_CRASH_LOG_FD").toInt();
- qCDebug(logCrashReporter) << "Resolving stacktrace from fd" << dumpFd;
- auto stacktrace = resolveStacktrace(dumpFd);
+ qCDebug(logCrashReporter) << "Saving minidump from fd" << dumpFd;
+ auto dumpDupStatus = tryDup(dumpFd, crashDir.filePath("minidump.dmp.log"));
+ if (dumpDupStatus != 0) {
+ qCCritical(logCrashReporter) << "Failed to write minidump:" << dumpDupStatus;
+ }
- qCDebug(logCrashReporter) << "Reading recent log lines from fd" << logFd;
- auto logDupFd = dup(logFd);
- auto recentLogs = readRecentLogs(logFd, 100, 10);
-
- qCDebug(logCrashReporter) << "Saving log from fd" << logDupFd;
- auto logDupStatus = tryDup(logDupFd, crashDir.filePath("log.qslog.log"));
+ qCDebug(logCrashReporter) << "Saving log from fd" << logFd;
+ auto logDupStatus = tryDup(logFd, crashDir.filePath("log.qslog.log"));
if (logDupStatus != 0) {
qCCritical(logCrashReporter) << "Failed to save log:" << logDupStatus;
}
+ auto copyBinStatus = 0;
+ if (!DISTRIBUTOR_DEBUGINFO_AVAILABLE) {
+ qCDebug(logCrashReporter) << "Copying binary to crash folder";
+ if (!QFile(QCoreApplication::applicationFilePath()).copy(crashDir.filePath("executable.txt"))) {
+ copyBinStatus = 1;
+ qCCritical(logCrashReporter) << "Failed to copy binary.";
+ }
+ }
+
{
- auto extraInfoFile = QFile(crashDir.filePath("report.txt"));
+ auto extraInfoFile = QFile(crashDir.filePath("info.txt"));
if (!extraInfoFile.open(QFile::WriteOnly)) {
qCCritical(logCrashReporter) << "Failed to open crash info file for writing.";
} else {
auto stream = QTextStream(&extraInfoFile);
- stream << qs::debuginfo::combinedInfo();
+ stream << "===== Build Information =====\n";
+ stream << "Git Revision: " << GIT_REVISION << '\n';
+ stream << "Buildtime Qt Version: " << QT_VERSION_STR << "\n";
+ stream << "Build Type: " << BUILD_TYPE << '\n';
+ stream << "Compiler: " << COMPILER << '\n';
+ stream << "Complie Flags: " << COMPILE_FLAGS << "\n\n";
+ stream << "Build configuration:\n" << BUILD_CONFIGURATION << "\n";
- stream << "\n===== Instance Information =====\n";
- stream << "Signal: " << strsignal(crashSignal) << " (" << crashSignal << ")\n"; // NOLINT
+ stream << "\n===== Runtime Information =====\n";
+ stream << "Runtime Qt Version: " << qVersion() << '\n';
stream << "Crashed process ID: " << crashProc << '\n';
stream << "Run ID: " << instance.instanceId << '\n';
stream << "Shell ID: " << instance.shellId << '\n';
stream << "Config Path: " << instance.configPath << '\n';
- stream << "\n===== Stacktrace =====\n";
- if (stacktrace.empty()) {
- stream << "(no trace available)\n";
+ stream << "\n===== Report Integrity =====\n";
+ stream << "Minidump save status: " << dumpDupStatus << '\n';
+ stream << "Log save status: " << logDupStatus << '\n';
+ stream << "Binary copy status: " << copyBinStatus << '\n';
+
+ stream << "\n===== System Information =====\n\n";
+ stream << "/etc/os-release:";
+ auto osReleaseFile = QFile("/etc/os-release");
+ if (osReleaseFile.open(QFile::ReadOnly)) {
+ stream << '\n' << osReleaseFile.readAll() << '\n';
+ osReleaseFile.close();
} else {
- auto formatter = cpptrace::formatter().header(std::string());
- auto traceStr = formatter.format(stacktrace);
- stream << QString::fromStdString(traceStr) << '\n';
+ stream << "FAILED TO OPEN\n";
}
- stream << "\n===== Log Tail =====\n";
- stream << recentLogs;
+ stream << "/etc/lsb-release:";
+ auto lsbReleaseFile = QFile("/etc/lsb-release");
+ if (lsbReleaseFile.open(QFile::ReadOnly)) {
+ stream << '\n' << lsbReleaseFile.readAll();
+ lsbReleaseFile.close();
+ } else {
+ stream << "FAILED TO OPEN\n";
+ }
extraInfoFile.close();
}
diff --git a/src/ipc/ipc.cpp b/src/ipc/ipc.cpp
index 4bfea4c..40e8f0c 100644
--- a/src/ipc/ipc.cpp
+++ b/src/ipc/ipc.cpp
@@ -3,7 +3,6 @@
#include
#include
-#include
#include
#include
#include
@@ -128,9 +127,7 @@ int IpcClient::connect(const QString& id, const std::functionquit();
- else QCoreApplication::exit(0);
+ EngineGeneration::currentGeneration()->quit();
}
} // namespace qs::ipc
diff --git a/src/launch/command.cpp b/src/launch/command.cpp
index 807eb24..151fc24 100644
--- a/src/launch/command.cpp
+++ b/src/launch/command.cpp
@@ -25,12 +25,12 @@
#include
#include
-#include "../core/debuginfo.hpp"
#include "../core/instanceinfo.hpp"
#include "../core/logging.hpp"
#include "../core/paths.hpp"
#include "../io/ipccomm.hpp"
#include "../ipc/ipc.hpp"
+#include "build.hpp"
#include "launch_p.hpp"
namespace qs::launch {
@@ -519,10 +519,20 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
}
if (state.misc.printVersion) {
- if (state.log.verbosity == 0) {
- qCInfo(logBare).noquote() << "Quickshell" << qs::debuginfo::qsVersion();
- } else {
- qCInfo(logBare).noquote() << qs::debuginfo::combinedInfo();
+ qCInfo(logBare).noquote().nospace() << "quickshell " << QS_VERSION << ", revision "
+ << GIT_REVISION << ", distributed by: " << DISTRIBUTOR;
+
+ if (state.log.verbosity > 1) {
+ qCInfo(logBare).noquote() << "\nBuildtime Qt Version:" << QT_VERSION_STR;
+ qCInfo(logBare).noquote() << "Runtime Qt Version:" << qVersion();
+ qCInfo(logBare).noquote() << "Compiler:" << COMPILER;
+ qCInfo(logBare).noquote() << "Compile Flags:" << COMPILE_FLAGS;
+ }
+
+ if (state.log.verbosity > 0) {
+ qCInfo(logBare).noquote() << "\nBuild Type:" << BUILD_TYPE;
+ qCInfo(logBare).noquote() << "Build configuration:";
+ qCInfo(logBare).noquote().nospace() << BUILD_CONFIGURATION;
}
} else if (*state.subcommand.log) {
return readLogFile(state);
diff --git a/src/launch/launch.cpp b/src/launch/launch.cpp
index 3a9a2a5..f269f61 100644
--- a/src/launch/launch.cpp
+++ b/src/launch/launch.cpp
@@ -27,7 +27,7 @@
#include "build.hpp"
#include "launch_p.hpp"
-#if CRASH_HANDLER
+#if CRASH_REPORTER
#include "../crash/handler.hpp"
#endif
@@ -137,14 +137,13 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio
.display = getDisplayConnection(),
};
-#if CRASH_HANDLER
- if (qEnvironmentVariableIsSet("QS_DISABLE_CRASH_HANDLER")) {
- qInfo() << "Crash handling disabled.";
- } else {
- crash::CrashHandler::init();
+#if CRASH_REPORTER
+ auto crashHandler = crash::CrashHandler();
+ crashHandler.init();
+ {
auto* log = LogManager::instance();
- crash::CrashHandler::setRelaunchInfo({
+ crashHandler.setRelaunchInfo({
.instance = InstanceInfo::CURRENT,
.noColor = !log->colorLogs,
.timestamp = log->timestampLogs,
diff --git a/src/launch/main.cpp b/src/launch/main.cpp
index a324e09..7a801fc 100644
--- a/src/launch/main.cpp
+++ b/src/launch/main.cpp
@@ -16,7 +16,7 @@
#include "build.hpp"
#include "launch_p.hpp"
-#if CRASH_HANDLER
+#if CRASH_REPORTER
#include "../crash/main.hpp"
#endif
@@ -25,7 +25,7 @@ namespace qs::launch {
namespace {
void checkCrashRelaunch(char** argv, QCoreApplication* coreApplication) {
-#if CRASH_HANDLER
+#if CRASH_REPORTER
auto lastInfoFdStr = qEnvironmentVariable("__QUICKSHELL_CRASH_INFO_FD");
if (!lastInfoFdStr.isEmpty()) {
@@ -104,7 +104,7 @@ void exitDaemon(int code) {
int main(int argc, char** argv) {
QCoreApplication::setApplicationName("quickshell");
-#if CRASH_HANDLER
+#if CRASH_REPORTER
qsCheckCrash(argc, argv);
#endif
diff --git a/src/services/greetd/CMakeLists.txt b/src/services/greetd/CMakeLists.txt
index a103531..2252f8c 100644
--- a/src/services/greetd/CMakeLists.txt
+++ b/src/services/greetd/CMakeLists.txt
@@ -12,7 +12,7 @@ qt_add_qml_module(quickshell-service-greetd
install_qml_module(quickshell-service-greetd)
# can't be Qt::Qml because generation.hpp pulls in gui types
-target_link_libraries(quickshell-service-greetd PRIVATE Qt::Quick quickshell-core)
+target_link_libraries(quickshell-service-greetd PRIVATE Qt::Quick)
qs_module_pch(quickshell-service-greetd)
diff --git a/src/services/greetd/connection.cpp b/src/services/greetd/connection.cpp
index 3b8fa24..7130870 100644
--- a/src/services/greetd/connection.cpp
+++ b/src/services/greetd/connection.cpp
@@ -145,7 +145,6 @@ void GreetdConnection::setInactive() {
QString GreetdConnection::user() const { return this->mUser; }
void GreetdConnection::onSocketConnected() {
- this->reader.setDevice(&this->socket);
qCDebug(logGreetd) << "Connected to greetd socket.";
if (this->mTargetActive) {
@@ -161,84 +160,82 @@ void GreetdConnection::onSocketError(QLocalSocket::LocalSocketError error) {
}
void GreetdConnection::onSocketReady() {
- while (true) {
- this->reader.startTransaction();
- auto length = this->reader.readI32();
- auto text = this->reader.readBytes(length);
- if (!this->reader.commitTransaction()) return;
+ qint32 length = 0;
- auto json = QJsonDocument::fromJson(text).object();
- auto type = json.value("type").toString();
+ this->socket.read(reinterpret_cast(&length), sizeof(qint32));
- qCDebug(logGreetd).noquote() << "Received greetd response:" << text;
+ auto text = this->socket.read(length);
+ auto json = QJsonDocument::fromJson(text).object();
+ auto type = json.value("type").toString();
- if (type == "success") {
- switch (this->mState) {
- case GreetdState::Authenticating:
- qCDebug(logGreetd) << "Authentication complete.";
- this->mState = GreetdState::ReadyToLaunch;
- emit this->stateChanged();
- emit this->readyToLaunch();
- break;
- case GreetdState::Launching:
- qCDebug(logGreetd) << "Target session set successfully.";
- this->mState = GreetdState::Launched;
- emit this->stateChanged();
- emit this->launched();
+ qCDebug(logGreetd).noquote() << "Received greetd response:" << text;
- if (this->mExitAfterLaunch) {
- qCDebug(logGreetd) << "Quitting.";
- EngineGeneration::currentGeneration()->quit();
- }
+ if (type == "success") {
+ switch (this->mState) {
+ case GreetdState::Authenticating:
+ qCDebug(logGreetd) << "Authentication complete.";
+ this->mState = GreetdState::ReadyToLaunch;
+ emit this->stateChanged();
+ emit this->readyToLaunch();
+ break;
+ case GreetdState::Launching:
+ qCDebug(logGreetd) << "Target session set successfully.";
+ this->mState = GreetdState::Launched;
+ emit this->stateChanged();
+ emit this->launched();
- break;
- default: goto unexpected;
- }
- } else if (type == "error") {
- auto errorType = json.value("error_type").toString();
- auto desc = json.value("description").toString();
-
- // Special case this error in case a session was already running.
- // This cancels and restarts the session.
- if (errorType == "error" && desc == "a session is already being configured") {
- qCDebug(
- logGreetd
- ) << "A session was already in progress, cancelling it and starting a new one.";
- this->setActive(false);
- this->setActive(true);
- return;
+ if (this->mExitAfterLaunch) {
+ qCDebug(logGreetd) << "Quitting.";
+ EngineGeneration::currentGeneration()->quit();
}
- if (errorType == "auth_error") {
- emit this->authFailure(desc);
- this->setActive(false);
- } else if (errorType == "error") {
- qCWarning(logGreetd) << "Greetd error occurred" << desc;
- emit this->error(desc);
- } else goto unexpected;
+ break;
+ default: goto unexpected;
+ }
+ } else if (type == "error") {
+ auto errorType = json.value("error_type").toString();
+ auto desc = json.value("description").toString();
- // errors terminate the session
- this->setInactive();
- } else if (type == "auth_message") {
- auto message = json.value("auth_message").toString();
- auto type = json.value("auth_message_type").toString();
- auto error = type == "error";
- auto responseRequired = type == "visible" || type == "secret";
- auto echoResponse = type != "secret";
+ // Special case this error in case a session was already running.
+ // This cancels and restarts the session.
+ if (errorType == "error" && desc == "a session is already being configured") {
+ qCDebug(
+ logGreetd
+ ) << "A session was already in progress, cancelling it and starting a new one.";
+ this->setActive(false);
+ this->setActive(true);
+ return;
+ }
- this->mResponseRequired = responseRequired;
- emit this->authMessage(message, error, responseRequired, echoResponse);
-
- if (!responseRequired) {
- this->sendRequest({{"type", "post_auth_message_response"}});
- }
+ if (errorType == "auth_error") {
+ emit this->authFailure(desc);
+ this->setActive(false);
+ } else if (errorType == "error") {
+ qCWarning(logGreetd) << "Greetd error occurred" << desc;
+ emit this->error(desc);
} else goto unexpected;
- continue;
- unexpected:
- qCCritical(logGreetd) << "Received unexpected greetd response" << text;
- this->setActive(false);
- }
+ // errors terminate the session
+ this->setInactive();
+ } else if (type == "auth_message") {
+ auto message = json.value("auth_message").toString();
+ auto type = json.value("auth_message_type").toString();
+ auto error = type == "error";
+ auto responseRequired = type == "visible" || type == "secret";
+ auto echoResponse = type != "secret";
+
+ this->mResponseRequired = responseRequired;
+ emit this->authMessage(message, error, responseRequired, echoResponse);
+
+ if (!responseRequired) {
+ this->sendRequest({{"type", "post_auth_message_response"}});
+ }
+ } else goto unexpected;
+
+ return;
+unexpected:
+ qCCritical(logGreetd) << "Received unexpected greetd response" << text;
+ this->setActive(false);
}
void GreetdConnection::sendRequest(const QJsonObject& json) {
diff --git a/src/services/greetd/connection.hpp b/src/services/greetd/connection.hpp
index 89348dc..0c1d1eb 100644
--- a/src/services/greetd/connection.hpp
+++ b/src/services/greetd/connection.hpp
@@ -8,8 +8,6 @@
#include
#include
-#include "../../core/streamreader.hpp"
-
///! State of the Greetd connection.
/// See @@Greetd.state.
class GreetdState: public QObject {
@@ -76,5 +74,4 @@ private:
bool mResponseRequired = false;
QString mUser;
QLocalSocket socket;
- StreamReader reader;
};
diff --git a/src/services/pam/conversation.cpp b/src/services/pam/conversation.cpp
index 1fb4c04..f8f5a09 100644
--- a/src/services/pam/conversation.cpp
+++ b/src/services/pam/conversation.cpp
@@ -8,9 +8,6 @@
#include
#include
#include
-#ifdef __FreeBSD__
-#include
-#endif
#include "../../core/logcat.hpp"
#include "ipc.hpp"
diff --git a/src/services/pipewire/core.cpp b/src/services/pipewire/core.cpp
index 5077abe..e40bc54 100644
--- a/src/services/pipewire/core.cpp
+++ b/src/services/pipewire/core.cpp
@@ -143,17 +143,12 @@ void PwCore::onSync(void* data, quint32 id, qint32 seq) {
void PwCore::onError(void* data, quint32 id, qint32 /*seq*/, qint32 res, const char* message) {
auto* self = static_cast(data);
- // Pipewire's documentation describes the error event as being fatal, however it isn't.
- // We're not sure what causes these ENOENTs on device removal, presumably something in
- // the teardown sequence, but they're harmless. Attempting to handle them as a fatal
- // error causes unnecessary triggers for shells.
- if (res == -ENOENT) {
- qCDebug(logLoop) << "Pipewire ENOENT on object" << id << "with code" << res << message;
- return;
+ if (message != nullptr) {
+ qCWarning(logLoop) << "Fatal pipewire error on object" << id << "with code" << res << message;
+ } else {
+ qCWarning(logLoop) << "Fatal pipewire error on object" << id << "with code" << res;
}
- qCWarning(logLoop) << "Pipewire error on object" << id << "with code" << res << message;
-
emit self->fatalError();
}
diff --git a/src/services/pipewire/defaults.cpp b/src/services/pipewire/defaults.cpp
index 7a24a65..02463f4 100644
--- a/src/services/pipewire/defaults.cpp
+++ b/src/services/pipewire/defaults.cpp
@@ -12,6 +12,7 @@
#include
#include "../../core/logcat.hpp"
+#include "../../core/util.hpp"
#include "metadata.hpp"
#include "node.hpp"
#include "registry.hpp"
@@ -137,6 +138,32 @@ void PwDefaultTracker::onNodeAdded(PwNode* node) {
}
}
+void PwDefaultTracker::onNodeDestroyed(QObject* node) {
+ if (node == this->mDefaultSink) {
+ qCInfo(logDefaults) << "Default sink destroyed.";
+ this->mDefaultSink = nullptr;
+ emit this->defaultSinkChanged();
+ }
+
+ if (node == this->mDefaultSource) {
+ qCInfo(logDefaults) << "Default source destroyed.";
+ this->mDefaultSource = nullptr;
+ emit this->defaultSourceChanged();
+ }
+
+ if (node == this->mDefaultConfiguredSink) {
+ qCInfo(logDefaults) << "Default configured sink destroyed.";
+ this->mDefaultConfiguredSink = nullptr;
+ emit this->defaultConfiguredSinkChanged();
+ }
+
+ if (node == this->mDefaultConfiguredSource) {
+ qCInfo(logDefaults) << "Default configured source destroyed.";
+ this->mDefaultConfiguredSource = nullptr;
+ emit this->defaultConfiguredSourceChanged();
+ }
+}
+
void PwDefaultTracker::changeConfiguredSink(PwNode* node) {
if (node != nullptr) {
if (!node->type.testFlags(PwNodeType::AudioSink)) {
@@ -213,23 +240,10 @@ void PwDefaultTracker::setDefaultSink(PwNode* node) {
if (node == this->mDefaultSink) return;
qCInfo(logDefaults) << "Default sink changed to" << node;
- if (this->mDefaultSink != nullptr) {
- QObject::disconnect(this->mDefaultSink, nullptr, this, nullptr);
- }
-
- this->mDefaultSink = node;
-
- if (node != nullptr) {
- QObject::connect(node, &QObject::destroyed, this, &PwDefaultTracker::onDefaultSinkDestroyed);
- }
-
- emit this->defaultSinkChanged();
-}
-
-void PwDefaultTracker::onDefaultSinkDestroyed() {
- qCInfo(logDefaults) << "Default sink destroyed.";
- this->mDefaultSink = nullptr;
- emit this->defaultSinkChanged();
+ setSimpleObjectHandle<
+ &PwDefaultTracker::mDefaultSink,
+ &PwDefaultTracker::onNodeDestroyed,
+ &PwDefaultTracker::defaultSinkChanged>(this, node);
}
void PwDefaultTracker::setDefaultSinkName(const QString& name) {
@@ -243,23 +257,10 @@ void PwDefaultTracker::setDefaultSource(PwNode* node) {
if (node == this->mDefaultSource) return;
qCInfo(logDefaults) << "Default source changed to" << node;
- if (this->mDefaultSource != nullptr) {
- QObject::disconnect(this->mDefaultSource, nullptr, this, nullptr);
- }
-
- this->mDefaultSource = node;
-
- if (node != nullptr) {
- QObject::connect(node, &QObject::destroyed, this, &PwDefaultTracker::onDefaultSourceDestroyed);
- }
-
- emit this->defaultSourceChanged();
-}
-
-void PwDefaultTracker::onDefaultSourceDestroyed() {
- qCInfo(logDefaults) << "Default source destroyed.";
- this->mDefaultSource = nullptr;
- emit this->defaultSourceChanged();
+ setSimpleObjectHandle<
+ &PwDefaultTracker::mDefaultSource,
+ &PwDefaultTracker::onNodeDestroyed,
+ &PwDefaultTracker::defaultSourceChanged>(this, node);
}
void PwDefaultTracker::setDefaultSourceName(const QString& name) {
@@ -273,28 +274,10 @@ void PwDefaultTracker::setDefaultConfiguredSink(PwNode* node) {
if (node == this->mDefaultConfiguredSink) return;
qCInfo(logDefaults) << "Default configured sink changed to" << node;
- if (this->mDefaultConfiguredSink != nullptr) {
- QObject::disconnect(this->mDefaultConfiguredSink, nullptr, this, nullptr);
- }
-
- this->mDefaultConfiguredSink = node;
-
- if (node != nullptr) {
- QObject::connect(
- node,
- &QObject::destroyed,
- this,
- &PwDefaultTracker::onDefaultConfiguredSinkDestroyed
- );
- }
-
- emit this->defaultConfiguredSinkChanged();
-}
-
-void PwDefaultTracker::onDefaultConfiguredSinkDestroyed() {
- qCInfo(logDefaults) << "Default configured sink destroyed.";
- this->mDefaultConfiguredSink = nullptr;
- emit this->defaultConfiguredSinkChanged();
+ setSimpleObjectHandle<
+ &PwDefaultTracker::mDefaultConfiguredSink,
+ &PwDefaultTracker::onNodeDestroyed,
+ &PwDefaultTracker::defaultConfiguredSinkChanged>(this, node);
}
void PwDefaultTracker::setDefaultConfiguredSinkName(const QString& name) {
@@ -308,28 +291,10 @@ void PwDefaultTracker::setDefaultConfiguredSource(PwNode* node) {
if (node == this->mDefaultConfiguredSource) return;
qCInfo(logDefaults) << "Default configured source changed to" << node;
- if (this->mDefaultConfiguredSource != nullptr) {
- QObject::disconnect(this->mDefaultConfiguredSource, nullptr, this, nullptr);
- }
-
- this->mDefaultConfiguredSource = node;
-
- if (node != nullptr) {
- QObject::connect(
- node,
- &QObject::destroyed,
- this,
- &PwDefaultTracker::onDefaultConfiguredSourceDestroyed
- );
- }
-
- emit this->defaultConfiguredSourceChanged();
-}
-
-void PwDefaultTracker::onDefaultConfiguredSourceDestroyed() {
- qCInfo(logDefaults) << "Default configured source destroyed.";
- this->mDefaultConfiguredSource = nullptr;
- emit this->defaultConfiguredSourceChanged();
+ setSimpleObjectHandle<
+ &PwDefaultTracker::mDefaultConfiguredSource,
+ &PwDefaultTracker::onNodeDestroyed,
+ &PwDefaultTracker::defaultConfiguredSourceChanged>(this, node);
}
void PwDefaultTracker::setDefaultConfiguredSourceName(const QString& name) {
diff --git a/src/services/pipewire/defaults.hpp b/src/services/pipewire/defaults.hpp
index f31669e..591c4fd 100644
--- a/src/services/pipewire/defaults.hpp
+++ b/src/services/pipewire/defaults.hpp
@@ -44,10 +44,7 @@ private slots:
void onMetadataAdded(PwMetadata* metadata);
void onMetadataProperty(const char* key, const char* type, const char* value);
void onNodeAdded(PwNode* node);
- void onDefaultSinkDestroyed();
- void onDefaultSourceDestroyed();
- void onDefaultConfiguredSinkDestroyed();
- void onDefaultConfiguredSourceDestroyed();
+ void onNodeDestroyed(QObject* node);
private:
void setDefaultSink(PwNode* node);
diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt
index 13e648a..ca49c8f 100644
--- a/src/wayland/CMakeLists.txt
+++ b/src/wayland/CMakeLists.txt
@@ -73,7 +73,6 @@ endfunction()
# -----
qt_add_library(quickshell-wayland STATIC
- wl_proxy_safe_deref.cpp
platformmenu.cpp
popupanchor.cpp
xdgshell.cpp
@@ -81,13 +80,6 @@ qt_add_library(quickshell-wayland STATIC
output_tracking.cpp
)
-# required for wl_proxy_safe_deref
-target_link_libraries(quickshell-wayland PRIVATE ${CMAKE_DL_LIBS})
-target_link_options(quickshell PRIVATE
- "LINKER:--export-dynamic-symbol=wl_proxy_get_listener"
- "LINKER:--require-defined=wl_proxy_get_listener"
-)
-
# required to make sure the constructor is linked
add_library(quickshell-wayland-init OBJECT init.cpp)
@@ -131,8 +123,6 @@ list(APPEND WAYLAND_MODULES Quickshell.Wayland._IdleNotify)
add_subdirectory(shortcuts_inhibit)
list(APPEND WAYLAND_MODULES Quickshell.Wayland._ShortcutsInhibitor)
-add_subdirectory(windowmanager)
-
# widgets for qmenu
target_link_libraries(quickshell-wayland PRIVATE
Qt::Quick Qt::Widgets Qt::WaylandClient Qt::WaylandClientPrivate
diff --git a/src/wayland/hyprland/ipc/CMakeLists.txt b/src/wayland/hyprland/ipc/CMakeLists.txt
index 9e42520..fd01463 100644
--- a/src/wayland/hyprland/ipc/CMakeLists.txt
+++ b/src/wayland/hyprland/ipc/CMakeLists.txt
@@ -15,7 +15,7 @@ qs_add_module_deps_light(quickshell-hyprland-ipc Quickshell)
install_qml_module(quickshell-hyprland-ipc)
-target_link_libraries(quickshell-hyprland-ipc PRIVATE Qt::Quick quickshell-core)
+target_link_libraries(quickshell-hyprland-ipc PRIVATE Qt::Quick)
if (WAYLAND_TOPLEVEL_MANAGEMENT)
target_sources(quickshell-hyprland-ipc PRIVATE
diff --git a/src/wayland/hyprland/ipc/connection.cpp b/src/wayland/hyprland/ipc/connection.cpp
index d15701d..ad091a6 100644
--- a/src/wayland/hyprland/ipc/connection.cpp
+++ b/src/wayland/hyprland/ipc/connection.cpp
@@ -93,7 +93,6 @@ void HyprlandIpc::eventSocketError(QLocalSocket::LocalSocketError error) const {
void HyprlandIpc::eventSocketStateChanged(QLocalSocket::LocalSocketState state) {
if (state == QLocalSocket::ConnectedState) {
- this->eventReader.setDevice(&this->eventSocket);
qCInfo(logHyprlandIpc) << "Hyprland event socket connected.";
emit this->connected();
} else if (state == QLocalSocket::UnconnectedState && this->valid) {
@@ -105,11 +104,11 @@ void HyprlandIpc::eventSocketStateChanged(QLocalSocket::LocalSocketState state)
void HyprlandIpc::eventSocketReady() {
while (true) {
- this->eventReader.startTransaction();
- auto rawEvent = this->eventReader.readUntil('\n');
- if (!this->eventReader.commitTransaction()) return;
+ auto rawEvent = this->eventSocket.readLine();
+ if (rawEvent.isEmpty()) break;
- rawEvent.chop(1); // remove trailing \n
+ // remove trailing \n
+ rawEvent.truncate(rawEvent.length() - 1);
auto splitIdx = rawEvent.indexOf(">>");
auto event = QByteArrayView(rawEvent.data(), splitIdx);
auto data = QByteArrayView(
@@ -729,7 +728,7 @@ void HyprlandIpc::refreshToplevels() {
}
auto* workspace = toplevel->bindableWorkspace().value();
- if (workspace) workspace->insertToplevel(toplevel);
+ workspace->insertToplevel(toplevel);
}
});
}
diff --git a/src/wayland/hyprland/ipc/connection.hpp b/src/wayland/hyprland/ipc/connection.hpp
index ba1e7c9..e15d5cd 100644
--- a/src/wayland/hyprland/ipc/connection.hpp
+++ b/src/wayland/hyprland/ipc/connection.hpp
@@ -14,7 +14,6 @@
#include "../../../core/model.hpp"
#include "../../../core/qmlscreen.hpp"
-#include "../../../core/streamreader.hpp"
#include "../../../wayland/toplevel_management/handle.hpp"
namespace qs::hyprland::ipc {
@@ -140,7 +139,6 @@ private:
static bool compareWorkspaces(HyprlandWorkspace* a, HyprlandWorkspace* b);
QLocalSocket eventSocket;
- StreamReader eventReader;
QString mRequestSocketPath;
QString mEventSocketPath;
bool valid = false;
diff --git a/src/wayland/hyprland/ipc/hyprland_toplevel.cpp b/src/wayland/hyprland/ipc/hyprland_toplevel.cpp
index 43b9838..7b07bc8 100644
--- a/src/wayland/hyprland/ipc/hyprland_toplevel.cpp
+++ b/src/wayland/hyprland/ipc/hyprland_toplevel.cpp
@@ -72,16 +72,20 @@ void HyprlandToplevel::updateFromObject(const QVariantMap& object) {
Qt::beginPropertyUpdateGroup();
bool ok = false;
auto address = addressStr.toULongLong(&ok, 16);
- if (ok && address) this->setAddress(address);
+ if (!ok || !address) {
+ return;
+ }
+ this->setAddress(address);
this->bTitle = title;
auto workspaceMap = object.value("workspace").toMap();
auto workspaceName = workspaceMap.value("name").toString();
- auto* workspace = this->ipc->findWorkspaceByName(workspaceName, true);
- if (workspace) this->setWorkspace(workspace);
+ auto* workspace = this->ipc->findWorkspaceByName(workspaceName, false);
+ if (!workspace) return;
+ this->setWorkspace(workspace);
this->bLastIpcObject = object;
Qt::endPropertyUpdateGroup();
}
diff --git a/src/wayland/init.cpp b/src/wayland/init.cpp
index 790cebb..e56eee3 100644
--- a/src/wayland/init.cpp
+++ b/src/wayland/init.cpp
@@ -10,7 +10,6 @@
#include "wlr_layershell/wlr_layershell.hpp"
#endif
-void installWlProxySafeDeref(); // NOLINT(misc-use-internal-linkage)
void installPlatformMenuHook(); // NOLINT(misc-use-internal-linkage)
void installPopupPositioner(); // NOLINT(misc-use-internal-linkage)
@@ -34,7 +33,6 @@ class WaylandPlugin: public QsEnginePlugin {
}
void init() override {
- installWlProxySafeDeref();
installPlatformMenuHook();
installPopupPositioner();
}
diff --git a/src/wayland/toplevel_management/qml.cpp b/src/wayland/toplevel_management/qml.cpp
index 6a1d96b..0eae3de 100644
--- a/src/wayland/toplevel_management/qml.cpp
+++ b/src/wayland/toplevel_management/qml.cpp
@@ -161,11 +161,7 @@ void ToplevelManager::onToplevelReady(impl::ToplevelHandle* handle) {
void ToplevelManager::onToplevelActiveChanged() {
auto* toplevel = qobject_cast(this->sender());
- if (toplevel->activated()) {
- this->setActiveToplevel(toplevel);
- } else if (toplevel == this->mActiveToplevel) {
- this->setActiveToplevel(nullptr);
- }
+ if (toplevel->activated()) this->setActiveToplevel(toplevel);
}
void ToplevelManager::onToplevelClosed() {
diff --git a/src/wayland/windowmanager/CMakeLists.txt b/src/wayland/windowmanager/CMakeLists.txt
deleted file mode 100644
index 76d1d89..0000000
--- a/src/wayland/windowmanager/CMakeLists.txt
+++ /dev/null
@@ -1,19 +0,0 @@
-qt_add_library(quickshell-wayland-windowsystem STATIC
- windowmanager.cpp
- windowset.cpp
- ext_workspace.cpp
-)
-
-add_library(quickshell-wayland-windowsystem-init OBJECT init.cpp)
-target_link_libraries(quickshell-wayland-windowsystem-init PRIVATE Qt::Quick)
-
-wl_proto(wlp-ext-workspace ext-workspace-v1 "${WAYLAND_PROTOCOLS}/staging/ext-workspace")
-
-target_link_libraries(quickshell-wayland-windowsystem PRIVATE
- Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client
- wlp-ext-workspace
-)
-
-qs_pch(quickshell-wayland-windowsystem SET large)
-
-target_link_libraries(quickshell PRIVATE quickshell-wayland-windowsystem quickshell-wayland-windowsystem-init)
diff --git a/src/wayland/windowmanager/ext_workspace.cpp b/src/wayland/windowmanager/ext_workspace.cpp
deleted file mode 100644
index fcb9ffa..0000000
--- a/src/wayland/windowmanager/ext_workspace.cpp
+++ /dev/null
@@ -1,176 +0,0 @@
-#include "ext_workspace.hpp"
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "../../core/logcat.hpp"
-
-namespace qs::wayland::workspace {
-
-QS_LOGGING_CATEGORY(logWorkspace, "quickshell.wm.wayland.workspace", QtWarningMsg);
-
-WorkspaceManager::WorkspaceManager(): QWaylandClientExtensionTemplate(1) { this->initialize(); }
-
-WorkspaceManager* WorkspaceManager::instance() {
- static auto* instance = new WorkspaceManager();
- return instance;
-}
-
-void WorkspaceManager::ext_workspace_manager_v1_workspace_group(
- ::ext_workspace_group_handle_v1* handle
-) {
- auto* group = new WorkspaceGroup(handle);
- qCDebug(logWorkspace) << "Created group" << group;
- this->mGroups.insert(handle, group);
- emit this->groupCreated(group);
-}
-
-void WorkspaceManager::ext_workspace_manager_v1_workspace(::ext_workspace_handle_v1* handle) {
- auto* workspace = new Workspace(handle);
- qCDebug(logWorkspace) << "Created workspace" << workspace;
- this->mWorkspaces.insert(handle, workspace);
- emit this->workspaceCreated(workspace);
-};
-
-void WorkspaceManager::destroyWorkspace(Workspace* workspace) {
- this->mWorkspaces.remove(workspace->object());
- this->destroyedWorkspaces.append(workspace);
- emit this->workspaceDestroyed(workspace);
-}
-
-void WorkspaceManager::destroyGroup(WorkspaceGroup* group) {
- this->mGroups.remove(group->object());
- this->destroyedGroups.append(group);
- emit this->groupDestroyed(group);
-}
-
-void WorkspaceManager::ext_workspace_manager_v1_done() {
- qCDebug(logWorkspace) << "Workspace changes done";
- emit this->serverCommit();
-
- for (auto* workspace: this->destroyedWorkspaces) delete workspace;
- for (auto* group: this->destroyedGroups) delete group;
- this->destroyedWorkspaces.clear();
- this->destroyedGroups.clear();
-}
-
-void WorkspaceManager::ext_workspace_manager_v1_finished() {
- qCWarning(logWorkspace) << "ext_workspace_manager_v1.finished() was received";
-}
-
-Workspace::~Workspace() {
- if (this->isInitialized()) this->destroy();
-}
-
-void Workspace::ext_workspace_handle_v1_id(const QString& id) {
- qCDebug(logWorkspace) << "Updated id for workspace" << this << "to" << id;
- this->id = id;
-}
-
-void Workspace::ext_workspace_handle_v1_name(const QString& name) {
- qCDebug(logWorkspace) << "Updated name for workspace" << this << "to" << name;
- this->name = name;
-}
-
-void Workspace::ext_workspace_handle_v1_coordinates(wl_array* coordinates) {
- this->coordinates.clear();
-
- auto* data = static_cast(coordinates->data);
- auto size = static_cast(coordinates->size / sizeof(qint32));
-
- for (auto i = 0; i != size; ++i) {
- this->coordinates.append(data[i]); // NOLINT
- }
-
- qCDebug(logWorkspace) << "Updated coordinates for workspace" << this << "to" << this->coordinates;
-}
-
-void Workspace::ext_workspace_handle_v1_state(quint32 state) {
- this->active = state & ext_workspace_handle_v1::state_active;
- this->urgent = state & ext_workspace_handle_v1::state_urgent;
- this->hidden = state & ext_workspace_handle_v1::state_hidden;
-
- qCDebug(logWorkspace).nospace() << "Updated state for workspace " << this
- << " to [active: " << this->active << ", urgent: " << this->urgent
- << ", hidden: " << this->hidden << ']';
-}
-
-void Workspace::ext_workspace_handle_v1_capabilities(quint32 capabilities) {
- this->canActivate = capabilities & ext_workspace_handle_v1::workspace_capabilities_activate;
- this->canDeactivate = capabilities & ext_workspace_handle_v1::workspace_capabilities_deactivate;
- this->canRemove = capabilities & ext_workspace_handle_v1::workspace_capabilities_remove;
- this->canAssign = capabilities & ext_workspace_handle_v1::workspace_capabilities_assign;
-
- qCDebug(logWorkspace).nospace() << "Updated capabilities for workspace " << this
- << " to [activate: " << this->canActivate
- << ", deactivate: " << this->canDeactivate
- << ", remove: " << this->canRemove
- << ", assign: " << this->canAssign << ']';
-}
-
-void Workspace::ext_workspace_handle_v1_removed() {
- qCDebug(logWorkspace) << "Destroyed workspace" << this;
- WorkspaceManager::instance()->destroyWorkspace(this);
- this->destroy();
-}
-
-void Workspace::enterGroup(WorkspaceGroup* group) { this->group = group; }
-
-void Workspace::leaveGroup(WorkspaceGroup* group) {
- if (this->group == group) this->group = nullptr;
-}
-
-WorkspaceGroup::~WorkspaceGroup() {
- if (this->isInitialized()) this->destroy();
-}
-
-void WorkspaceGroup::ext_workspace_group_handle_v1_capabilities(quint32 capabilities) {
- this->canCreateWorkspace =
- capabilities & ext_workspace_group_handle_v1::group_capabilities_create_workspace;
-
- qCDebug(logWorkspace).nospace() << "Updated capabilities for group " << this
- << " to [create_workspace: " << this->canCreateWorkspace << ']';
-}
-
-void WorkspaceGroup::ext_workspace_group_handle_v1_output_enter(::wl_output* output) {
- qCDebug(logWorkspace) << "Output" << output << "added to group" << this;
- this->screens.addOutput(output);
-}
-
-void WorkspaceGroup::ext_workspace_group_handle_v1_output_leave(::wl_output* output) {
- qCDebug(logWorkspace) << "Output" << output << "removed from group" << this;
- this->screens.removeOutput(output);
-}
-
-void WorkspaceGroup::ext_workspace_group_handle_v1_workspace_enter(
- ::ext_workspace_handle_v1* handle
-) {
- auto* workspace = WorkspaceManager::instance()->mWorkspaces.value(handle);
- qCDebug(logWorkspace) << "Workspace" << workspace << "added to group" << this;
-
- if (workspace) workspace->enterGroup(this);
-}
-
-void WorkspaceGroup::ext_workspace_group_handle_v1_workspace_leave(
- ::ext_workspace_handle_v1* handle
-) {
- auto* workspace = WorkspaceManager::instance()->mWorkspaces.value(handle);
- qCDebug(logWorkspace) << "Workspace" << workspace << "removed from group" << this;
-
- if (workspace) workspace->leaveGroup(this);
-}
-
-void WorkspaceGroup::ext_workspace_group_handle_v1_removed() {
- qCDebug(logWorkspace) << "Destroyed group" << this;
- WorkspaceManager::instance()->destroyGroup(this);
- this->destroy();
-}
-
-} // namespace qs::wayland::workspace
diff --git a/src/wayland/windowmanager/ext_workspace.hpp b/src/wayland/windowmanager/ext_workspace.hpp
deleted file mode 100644
index 6aff209..0000000
--- a/src/wayland/windowmanager/ext_workspace.hpp
+++ /dev/null
@@ -1,117 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "../../core/logcat.hpp"
-#include "../output_tracking.hpp"
-
-namespace qs::wayland::workspace {
-
-QS_DECLARE_LOGGING_CATEGORY(logWorkspace);
-
-class WorkspaceGroup;
-class Workspace;
-
-class WorkspaceManager
- : public QWaylandClientExtensionTemplate
- , public QtWayland::ext_workspace_manager_v1 {
- Q_OBJECT;
-
-public:
- static WorkspaceManager* instance();
-
- [[nodiscard]] QList workspaces() { return this->mWorkspaces.values(); }
-
-signals:
- void serverCommit();
- void workspaceCreated(Workspace* workspace);
- void workspaceDestroyed(Workspace* workspace);
- void groupCreated(WorkspaceGroup* group);
- void groupDestroyed(WorkspaceGroup* group);
-
-protected:
- void ext_workspace_manager_v1_workspace_group(::ext_workspace_group_handle_v1* handle) override;
- void ext_workspace_manager_v1_workspace(::ext_workspace_handle_v1* handle) override;
- void ext_workspace_manager_v1_done() override;
- void ext_workspace_manager_v1_finished() override;
-
-private:
- WorkspaceManager();
-
- void destroyGroup(WorkspaceGroup* group);
- void destroyWorkspace(Workspace* workspace);
-
- QHash<::ext_workspace_handle_v1*, Workspace*> mWorkspaces;
- QHash<::ext_workspace_group_handle_v1*, WorkspaceGroup*> mGroups;
- QList destroyedGroups;
- QList destroyedWorkspaces;
-
- friend class Workspace;
- friend class WorkspaceGroup;
-};
-
-class Workspace: public QtWayland::ext_workspace_handle_v1 {
-public:
- Workspace(::ext_workspace_handle_v1* handle): QtWayland::ext_workspace_handle_v1(handle) {}
- ~Workspace() override;
- Q_DISABLE_COPY_MOVE(Workspace);
-
- QString id;
- QString name;
- QList coordinates;
- WorkspaceGroup* group = nullptr;
-
- bool active : 1 = false;
- bool urgent : 1 = false;
- bool hidden : 1 = false;
-
- bool canActivate : 1 = false;
- bool canDeactivate : 1 = false;
- bool canRemove : 1 = false;
- bool canAssign : 1 = false;
-
-protected:
- void ext_workspace_handle_v1_id(const QString& id) override;
- void ext_workspace_handle_v1_name(const QString& name) override;
- void ext_workspace_handle_v1_coordinates(wl_array* coordinates) override;
- void ext_workspace_handle_v1_state(quint32 state) override;
- void ext_workspace_handle_v1_capabilities(quint32 capabilities) override;
- void ext_workspace_handle_v1_removed() override;
-
-private:
- void enterGroup(WorkspaceGroup* group);
- void leaveGroup(WorkspaceGroup* group);
-
- friend class WorkspaceGroup;
-};
-
-class WorkspaceGroup: public QtWayland::ext_workspace_group_handle_v1 {
-public:
- WorkspaceGroup(::ext_workspace_group_handle_v1* handle)
- : QtWayland::ext_workspace_group_handle_v1(handle) {}
-
- ~WorkspaceGroup() override;
- Q_DISABLE_COPY_MOVE(WorkspaceGroup);
-
- WlOutputTracker screens;
- bool canCreateWorkspace : 1 = false;
-
-protected:
- void ext_workspace_group_handle_v1_capabilities(quint32 capabilities) override;
- void ext_workspace_group_handle_v1_output_enter(::wl_output* output) override;
- void ext_workspace_group_handle_v1_output_leave(::wl_output* output) override;
- void ext_workspace_group_handle_v1_workspace_enter(::ext_workspace_handle_v1* handle) override;
- void ext_workspace_group_handle_v1_workspace_leave(::ext_workspace_handle_v1* handle) override;
- void ext_workspace_group_handle_v1_removed() override;
-};
-
-} // namespace qs::wayland::workspace
diff --git a/src/wayland/windowmanager/init.cpp b/src/wayland/windowmanager/init.cpp
deleted file mode 100644
index 88be01a..0000000
--- a/src/wayland/windowmanager/init.cpp
+++ /dev/null
@@ -1,23 +0,0 @@
-#include
-#include
-#include
-
-#include "../../core/plugin.hpp"
-
-namespace qs::wm::wayland {
-void installWmProvider();
-}
-
-namespace {
-
-class WaylandWmPlugin: public QsEnginePlugin {
- QList dependencies() override { return {"window"}; }
-
- bool applies() override { return QGuiApplication::platformName() == "wayland"; }
-
- void init() override { qs::wm::wayland::installWmProvider(); }
-};
-
-QS_REGISTER_PLUGIN(WaylandWmPlugin);
-
-} // namespace
diff --git a/src/wayland/windowmanager/windowmanager.cpp b/src/wayland/windowmanager/windowmanager.cpp
deleted file mode 100644
index 16245d0..0000000
--- a/src/wayland/windowmanager/windowmanager.cpp
+++ /dev/null
@@ -1,21 +0,0 @@
-#include "windowmanager.hpp"
-
-#include "../../windowmanager/windowmanager.hpp"
-#include "windowset.hpp"
-
-namespace qs::wm::wayland {
-
-WaylandWindowManager* WaylandWindowManager::instance() {
- static auto* instance = []() {
- auto* wm = new WaylandWindowManager();
- WindowsetManager::instance();
- return wm;
- }();
- return instance;
-}
-
-void installWmProvider() { // NOLINT (misc-use-internal-linkage)
- qs::wm::WindowManager::setProvider([]() { return WaylandWindowManager::instance(); });
-}
-
-} // namespace qs::wm::wayland
diff --git a/src/wayland/windowmanager/windowmanager.hpp b/src/wayland/windowmanager/windowmanager.hpp
deleted file mode 100644
index 9d48efd..0000000
--- a/src/wayland/windowmanager/windowmanager.hpp
+++ /dev/null
@@ -1,17 +0,0 @@
-#pragma once
-
-#include
-
-#include "../../windowmanager/windowmanager.hpp"
-#include "windowset.hpp"
-
-namespace qs::wm::wayland {
-
-class WaylandWindowManager: public WindowManager {
- Q_OBJECT;
-
-public:
- static WaylandWindowManager* instance();
-};
-
-} // namespace qs::wm::wayland
diff --git a/src/wayland/windowmanager/windowset.cpp b/src/wayland/windowmanager/windowset.cpp
deleted file mode 100644
index 74e273d..0000000
--- a/src/wayland/windowmanager/windowset.cpp
+++ /dev/null
@@ -1,252 +0,0 @@
-#include "windowset.hpp"
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "../../windowmanager/screenprojection.hpp"
-#include "../../windowmanager/windowmanager.hpp"
-#include "../../windowmanager/windowset.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(ws)->commitImpl(); // NOLINT
- }
-
- for (auto* projection: projections) {
- static_cast(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(projection)) {
- wlProjection = p;
- } else if (auto* p = dynamic_cast(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
diff --git a/src/wayland/windowmanager/windowset.hpp b/src/wayland/windowmanager/windowset.hpp
deleted file mode 100644
index 52d1c63..0000000
--- a/src/wayland/windowmanager/windowset.hpp
+++ /dev/null
@@ -1,85 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-#include
-#include
-
-#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 pendingWindowsetCreations;
- QList pendingWindowsetDestructions;
- QHash windowsetByImpl;
-
- QList pendingProjectionCreations;
- QList pendingProjectionDestructions;
- QHash 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(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(this->parent()); // NOLINT
- }
-
-private:
- impl::WorkspaceGroup* impl = nullptr;
-
- friend class WlWindowset;
-};
-
-} // namespace qs::wm::wayland
diff --git a/src/wayland/wl_proxy_safe_deref.cpp b/src/wayland/wl_proxy_safe_deref.cpp
deleted file mode 100644
index 0ebc258..0000000
--- a/src/wayland/wl_proxy_safe_deref.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-
-#include
-#include
-#include
-#include
-#include
-
-#include "../core/logcat.hpp"
-
-namespace {
-QS_LOGGING_CATEGORY(logDeref, "quickshell.wayland.safederef", QtWarningMsg);
-using wl_proxy_get_listener_t = const void* (*) (wl_proxy*);
-wl_proxy_get_listener_t original_wl_proxy_get_listener = nullptr; // NOLINT
-} // namespace
-
-extern "C" {
-WL_EXPORT const void* wl_proxy_get_listener(struct wl_proxy* proxy) {
- // Avoid null derefs of protocol objects in qtbase.
- // https://qt-project.atlassian.net/browse/QTBUG-145022
- if (!proxy) [[unlikely]] {
- qCCritical(logDeref) << "wl_proxy_get_listener called with a null proxy!";
- return nullptr;
- }
-
- return original_wl_proxy_get_listener(proxy);
-}
-}
-
-// NOLINTBEGIN (concurrency-mt-unsafe)
-void installWlProxySafeDeref() {
- dlerror(); // clear old errors
-
- original_wl_proxy_get_listener =
- reinterpret_cast(dlsym(RTLD_NEXT, "wl_proxy_get_listener"));
-
- if (auto* error = dlerror()) {
- qCCritical(logDeref) << "Failed to find wl_proxy_get_listener for hooking:" << error;
- } else {
- qCInfo(logDeref) << "Installed wl_proxy_get_listener hook.";
- }
-}
-// NOLINTEND
diff --git a/src/wayland/wlr_layershell/surface.cpp b/src/wayland/wlr_layershell/surface.cpp
index 0b0e7d7..4a5015e 100644
--- a/src/wayland/wlr_layershell/surface.cpp
+++ b/src/wayland/wlr_layershell/surface.cpp
@@ -17,7 +17,6 @@
#include