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 #include #include -#include #include "../../window/panelinterface.hpp" #include "shell_integration.hpp" @@ -248,19 +247,9 @@ void LayerSurface::commit() { } void LayerSurface::attachPopup(QtWaylandClient::QWaylandShellSurface* popup) { -#ifdef __FreeBSD__ - // FreeBSD uses an alternate RTTI matching strategy by default which does - // not work across modules, preventing std::any from downcasting. On - // FreeBSD, Qt is built with a patch to expose the surface role through a - // pointer instead of an any, which does not have this problem. - // See https://bugs.kde.org/show_bug.cgi?id=479679 - if (auto* xdgPopup = static_cast<::xdg_popup*>(popup->nativeResource("xdg_popup"))) { - this->get_popup(xdgPopup); - return; - } -#endif - auto role = popup->surfaceRole(); // NOLINT - if (auto* popupRole = std::any_cast<::xdg_popup*>(&role)) { + std::any role = popup->surfaceRole(); + + if (auto* popupRole = std::any_cast<::xdg_popup*>(&role)) { // NOLINT this->get_popup(*popupRole); } else { qWarning() << "Cannot attach popup" << popup << "to shell surface" << this diff --git a/src/windowmanager/CMakeLists.txt b/src/windowmanager/CMakeLists.txt deleted file mode 100644 index 3c032f4..0000000 --- a/src/windowmanager/CMakeLists.txt +++ /dev/null @@ -1,20 +0,0 @@ -qt_add_library(quickshell-windowmanager STATIC - screenprojection.cpp - windowmanager.cpp - windowset.cpp -) - -qt_add_qml_module(quickshell-windowmanager - URI Quickshell.WindowManager - VERSION 0.1 - DEPENDENCIES QtQuick -) - -qs_add_module_deps_light(quickshell-windowmanager Quickshell) - -install_qml_module(quickshell-windowmanager) - -qs_module_pch(quickshell-windowmanager SET large) - -target_link_libraries(quickshell-windowmanager PRIVATE Qt::Quick) -target_link_libraries(quickshell PRIVATE quickshell-windowmanagerplugin) diff --git a/src/windowmanager/module.md b/src/windowmanager/module.md deleted file mode 100644 index 3480d60..0000000 --- a/src/windowmanager/module.md +++ /dev/null @@ -1,10 +0,0 @@ -name = "Quickshell.WindowManager" -description = "Window manager interface" -headers = [ - "windowmanager.hpp", - "windowset.hpp", - "screenprojection.hpp", -] ------ -Currently only supports the [ext-workspace-v1](https://wayland.app/protocols/ext-workspace-v1) wayland protocol. -Support will be expanded in future releases. diff --git a/src/windowmanager/screenprojection.cpp b/src/windowmanager/screenprojection.cpp deleted file mode 100644 index c09e6f0..0000000 --- a/src/windowmanager/screenprojection.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "screenprojection.hpp" - -#include -#include -#include - -#include "windowmanager.hpp" -#include "windowset.hpp" - -namespace qs::wm { - -ScreenProjection::ScreenProjection(QScreen* screen, QObject* parent) - : WindowsetProjection(parent) - , mScreen(screen) { - this->bQScreens = {screen}; - this->bWindowsets.setBinding([this]() { - QList result; - for (auto* ws: WindowManager::instance()->bindableWindowsets().value()) { - auto* proj = ws->bindableProjection().value(); - if (proj && proj->bindableQScreens().value().contains(this->mScreen)) { - result.append(ws); - } - } - return result; - }); -} - -QScreen* ScreenProjection::screen() const { return this->mScreen; } - -} // namespace qs::wm diff --git a/src/windowmanager/screenprojection.hpp b/src/windowmanager/screenprojection.hpp deleted file mode 100644 index 6b0f31e..0000000 --- a/src/windowmanager/screenprojection.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "windowset.hpp" - -namespace qs::wm { - -///! WindowsetProjection covering one specific screen. -/// A ScreenProjection is a special type of @@WindowsetProjection which aggregates -/// all windowsets across all projections covering a specific screen. -/// -/// When used with @@Windowset.setProjection(), an arbitrary projection on the screen -/// will be picked. Usually there is only one. -/// -/// Use @@WindowManager.screenProjection() to get a ScreenProjection for a given screen. -class ScreenProjection: public WindowsetProjection { - Q_OBJECT; - QML_ELEMENT; - QML_UNCREATABLE(""); - -public: - ScreenProjection(QScreen* screen, QObject* parent); - - [[nodiscard]] QScreen* screen() const; - -private: - QScreen* mScreen; -}; - -} // namespace qs::wm diff --git a/src/windowmanager/test/manual/WorkspaceDelegate.qml b/src/windowmanager/test/manual/WorkspaceDelegate.qml deleted file mode 100644 index 4ebd7f2..0000000 --- a/src/windowmanager/test/manual/WorkspaceDelegate.qml +++ /dev/null @@ -1,86 +0,0 @@ -import QtQuick -import QtQuick.Controls.Fusion -import QtQuick.Layouts -import Quickshell -import Quickshell.Widgets -import Quickshell.WindowManager - -WrapperRectangle { - id: delegate - required property Windowset modelData; - color: modelData.active ? "green" : "gray" - - ColumnLayout { - Label { text: delegate.modelData.toString() } - Label { text: `Id: ${delegate.modelData.id} Name: ${delegate.modelData.name}` } - Label { text: `Coordinates: ${delegate.modelData.coordinates.toString()}`} - - RowLayout { - Label { text: "Group:" } - ComboBox { - Layout.fillWidth: true - implicitContentWidthPolicy: ComboBox.WidestText - enabled: delegate.modelData.canSetProjection - model: [...WindowManager.windowsetProjections].map(w => w.toString()) - currentIndex: WindowManager.windowsetProjections.indexOf(delegate.modelData.projection) - onActivated: i => delegate.modelData.setProjection(WindowManager.windowsetProjections[i]) - } - } - - RowLayout { - Label { text: "Screen:" } - ComboBox { - Layout.fillWidth: true - implicitContentWidthPolicy: ComboBox.WidestText - enabled: delegate.modelData.canSetProjection - model: [...Quickshell.screens].map(w => w.name) - currentIndex: Quickshell.screens.indexOf(delegate.modelData.projection.screens[0]) - onActivated: i => delegate.modelData.setProjection(WindowManager.screenProjection(Quickshell.screens[i])) - } - } - - - RowLayout { - DisplayCheckBox { - text: "Active" - checked: delegate.modelData.active - } - - DisplayCheckBox { - text: "Urgent" - checked: delegate.modelData.urgent - } - - DisplayCheckBox { - text: "Should Display" - checked: delegate.modelData.shouldDisplay - } - } - - RowLayout { - Button { - text: "Activate" - enabled: delegate.modelData.canActivate - onClicked: delegate.modelData.activate() - } - - Button { - text: "Deactivate" - enabled: delegate.modelData.canDeactivate - onClicked: delegate.modelData.deactivate() - } - - Button { - text: "Remove" - enabled: delegate.modelData.canRemove - onClicked: delegate.modelData.remove() - } - } - } - - component DisplayCheckBox: CheckBox { - enabled: false - palette.disabled.text: parent.palette.active.text - palette.disabled.windowText: parent.palette.active.windowText - } -} diff --git a/src/windowmanager/test/manual/screenproj.qml b/src/windowmanager/test/manual/screenproj.qml deleted file mode 100644 index d06036c..0000000 --- a/src/windowmanager/test/manual/screenproj.qml +++ /dev/null @@ -1,45 +0,0 @@ -import QtQuick -import QtQuick.Controls.Fusion -import QtQuick.Layouts -import Quickshell -import Quickshell.Widgets -import Quickshell.WindowManager - -FloatingWindow { - ScrollView { - anchors.fill: parent - - ColumnLayout { - Repeater { - model: Quickshell.screens - - WrapperRectangle { - id: delegate - required property ShellScreen modelData - color: "slategray" - margin: 5 - - ColumnLayout { - Label { text: `Screen: ${delegate.modelData.name}` } - - Repeater { - model: ScriptModel { - values: WindowManager.screenProjection(delegate.modelData).windowsets - } - - WorkspaceDelegate {} - } - } - } - } - - Repeater { - model: ScriptModel { - values: WindowManager.windowsets.filter(w => w.projection == null) - } - - WorkspaceDelegate {} - } - } - } -} diff --git a/src/windowmanager/test/manual/workspaces.qml b/src/windowmanager/test/manual/workspaces.qml deleted file mode 100644 index d6fdf05..0000000 --- a/src/windowmanager/test/manual/workspaces.qml +++ /dev/null @@ -1,46 +0,0 @@ -import QtQuick -import QtQuick.Controls.Fusion -import QtQuick.Layouts -import Quickshell -import Quickshell.Widgets -import Quickshell.WindowManager - -FloatingWindow { - ScrollView { - anchors.fill: parent - - ColumnLayout { - Repeater { - model: WindowManager.windowsetProjections - - WrapperRectangle { - id: delegate - required property WindowsetProjection modelData - color: "slategray" - margin: 5 - - ColumnLayout { - Label { text: delegate.modelData.toString() } - Label { text: `Screens: ${delegate.modelData.screens.map(s => s.name)}` } - - Repeater { - model: ScriptModel { - values: delegate.modelData.windowsets - } - - WorkspaceDelegate {} - } - } - } - } - - Repeater { - model: ScriptModel { - values: WindowManager.windowsets.filter(w => w.projection == null) - } - - WorkspaceDelegate {} - } - } - } -} diff --git a/src/windowmanager/windowmanager.cpp b/src/windowmanager/windowmanager.cpp deleted file mode 100644 index 6b51db1..0000000 --- a/src/windowmanager/windowmanager.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "windowmanager.hpp" -#include -#include - -#include - -#include "../core/qmlscreen.hpp" -#include "screenprojection.hpp" - -namespace qs::wm { - -std::function WindowManager::provider; - -void WindowManager::setProvider(std::function provider) { - WindowManager::provider = std::move(provider); -} - -WindowManager* WindowManager::instance() { - static auto* instance = WindowManager::provider(); - return instance; -} - -ScreenProjection* WindowManager::screenProjection(QuickshellScreenInfo* screen) { - auto* qscreen = screen->screen; - auto it = this->mScreenProjections.find(qscreen); - if (it != this->mScreenProjections.end()) { - return *it; - } - - auto* projection = new ScreenProjection(qscreen, this); - this->mScreenProjections.insert(qscreen, projection); - - QObject::connect(qscreen, &QObject::destroyed, this, [this, projection, qscreen]() { - this->mScreenProjections.remove(qscreen); - delete projection; - }); - - return projection; -} - -} // namespace qs::wm diff --git a/src/windowmanager/windowmanager.hpp b/src/windowmanager/windowmanager.hpp deleted file mode 100644 index 054e485..0000000 --- a/src/windowmanager/windowmanager.hpp +++ /dev/null @@ -1,91 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "../core/qmlscreen.hpp" -#include "screenprojection.hpp" -#include "windowset.hpp" - -namespace qs::wm { - -class WindowManager: public QObject { - Q_OBJECT; - -public: - static void setProvider(std::function provider); - static WindowManager* instance(); - - Q_INVOKABLE ScreenProjection* screenProjection(QuickshellScreenInfo* screen); - - [[nodiscard]] QBindable> bindableWindowsets() const { - return &this->bWindowsets; - } - - [[nodiscard]] QBindable> bindableWindowsetProjections() const { - return &this->bWindowsetProjections; - } - -signals: - void windowsetsChanged(); - void windowsetProjectionsChanged(); - -public: - Q_OBJECT_BINDABLE_PROPERTY( - WindowManager, - QList, - bWindowsets, - &WindowManager::windowsetsChanged - ); - - Q_OBJECT_BINDABLE_PROPERTY( - WindowManager, - QList, - bWindowsetProjections, - &WindowManager::windowsetProjectionsChanged - ); - -private: - static std::function provider; - QHash mScreenProjections; -}; - -///! Window management interfaces exposed by the window manager. -class WindowManagerQml: public QObject { - Q_OBJECT; - QML_NAMED_ELEMENT(WindowManager); - QML_SINGLETON; - // clang-format off - /// All windowsets tracked by the WM across all projections. - Q_PROPERTY(QList windowsets READ default BINDABLE bindableWindowsets); - /// All windowset projections tracked by the WM. Does not include - /// internal projections from @@screenProjection(). - Q_PROPERTY(QList windowsetProjections READ default BINDABLE bindableWindowsetProjections); - // clang-format on - -public: - /// Returns an internal WindowsetProjection that covers a single screen and contains all - /// windowsets on that screen, regardless of the WM-specified projection. Depending on - /// how the WM lays out its actual projections, multiple ScreenProjections may contain - /// the same Windowsets. - Q_INVOKABLE static ScreenProjection* screenProjection(QuickshellScreenInfo* screen) { - return WindowManager::instance()->screenProjection(screen); - } - - [[nodiscard]] static QBindable> bindableWindowsets() { - return WindowManager::instance()->bindableWindowsets(); - } - - [[nodiscard]] static QBindable> bindableWindowsetProjections() { - return WindowManager::instance()->bindableWindowsetProjections(); - } -}; - -} // namespace qs::wm diff --git a/src/windowmanager/windowset.cpp b/src/windowmanager/windowset.cpp deleted file mode 100644 index 6231c40..0000000 --- a/src/windowmanager/windowset.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "windowset.hpp" - -#include -#include -#include -#include - -#include "../core/qmlglobal.hpp" -#include "windowmanager.hpp" - -namespace qs::wm { - -Q_LOGGING_CATEGORY(logWorkspace, "quickshell.wm.workspace", QtWarningMsg); - -void Windowset::activate() { qCCritical(logWorkspace) << this << "cannot be activated"; } -void Windowset::deactivate() { qCCritical(logWorkspace) << this << "cannot be deactivated"; } -void Windowset::remove() { qCCritical(logWorkspace) << this << "cannot be removed"; } - -void Windowset::setProjection(WindowsetProjection* /*projection*/) { - qCCritical(logWorkspace) << this << "cannot be assigned to a projection"; -} - -WindowsetProjection::WindowsetProjection(QObject* parent): QObject(parent) { - this->bWindowsets.setBinding([this] { - QList result; - for (auto* ws: WindowManager::instance()->bindableWindowsets().value()) { - if (ws->bindableProjection().value() == this) { - result.append(ws); - } - } - return result; - }); - - this->bScreens.setBinding([this] { - QList screens; - - for (auto* screen: this->bQScreens.value()) { - screens.append(QuickshellTracked::instance()->screenInfo(screen)); - } - - return screens; - }); -} - -} // namespace qs::wm diff --git a/src/windowmanager/windowset.hpp b/src/windowmanager/windowset.hpp deleted file mode 100644 index 51cbd9b..0000000 --- a/src/windowmanager/windowset.hpp +++ /dev/null @@ -1,175 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -class QuickshellScreenInfo; - -namespace qs::wm { - -Q_DECLARE_LOGGING_CATEGORY(logWorkspace); - -class WindowsetProjection; - -///! A group of windows worked with by a user, usually known as a Workspace or Tag. -/// A Windowset is a generic type that encompasses both "Workspaces" and "Tags" in window managers. -/// Because the definition encompasses both you may not necessarily need all features. -class Windowset: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_UNCREATABLE(""); - // clang-format off - /// A persistent internal identifier for the windowset. This property should be identical - /// across restarts and destruction/recreation of a windowset. - Q_PROPERTY(QString id READ default NOTIFY idChanged BINDABLE bindableId); - /// Human readable name of the windowset. - Q_PROPERTY(QString name READ default NOTIFY nameChanged BINDABLE bindableName); - /// Coordinates of the workspace, represented as an N-dimensional array. Most WMs - /// will only expose one coordinate. If more than one is exposed, the first is - /// conventionally X, the second Y, and the third Z. - Q_PROPERTY(QList coordinates READ default NOTIFY coordinatesChanged BINDABLE bindableCoordinates); - /// True if the windowset is currently active. In a workspace based WM, this means the - /// represented workspace is current. In a tag based WM, this means the represented tag - /// is active. - Q_PROPERTY(bool active READ default NOTIFY activeChanged BINDABLE bindableActive); - /// The projection this windowset is a member of. A projection is the set of screens covered by - /// a windowset. - Q_PROPERTY(WindowsetProjection* projection READ default NOTIFY projectionChanged BINDABLE bindableProjection); - /// If false, this windowset should generally be hidden from workspace pickers. - Q_PROPERTY(bool shouldDisplay READ default NOTIFY shouldDisplayChanged BINDABLE bindableShouldDisplay); - /// If true, a window in this windowset has been marked as urgent. - Q_PROPERTY(bool urgent READ default NOTIFY urgentChanged BINDABLE bindableUrgent); - /// If true, the windowset can be activated. In a workspace based WM, this will make the workspace - /// current, in a tag based wm, the tag will be activated. - Q_PROPERTY(bool canActivate READ default NOTIFY canActivateChanged BINDABLE bindableCanActivate); - /// If true, the windowset can be deactivated. In a workspace based WM, deactivation is usually implicit - /// and based on activation of another workspace. - Q_PROPERTY(bool canDeactivate READ default NOTIFY canDeactivateChanged BINDABLE bindableCanDeactivate); - /// If true, the windowset can be removed. This may be done implicitly by the WM as well. - Q_PROPERTY(bool canRemove READ default NOTIFY canRemoveChanged BINDABLE bindableCanRemove); - /// If true, the windowset can be moved to a different projection. - Q_PROPERTY(bool canSetProjection READ default NOTIFY canSetProjectionChanged BINDABLE bindableCanSetProjection); - // clang-format on - -public: - explicit Windowset(QObject* parent): QObject(parent) {} - - /// Activate the windowset, making it the current workspace on a workspace based WM, or activating - /// the tag on a tag based WM. Requires @@canActivate. - Q_INVOKABLE virtual void activate(); - /// Deactivate the windowset, hiding it. Requires @@canDeactivate. - Q_INVOKABLE virtual void deactivate(); - /// Remove or destroy the windowset. Requires @@canRemove. - Q_INVOKABLE virtual void remove(); - /// Move the windowset to a different projection. A projection represents the set of screens - /// a workspace spans. Requires @@canSetProjection. - Q_INVOKABLE virtual void setProjection(WindowsetProjection* projection); - - [[nodiscard]] QBindable bindableId() const { return &this->bId; } - [[nodiscard]] QBindable bindableName() const { return &this->bName; } - [[nodiscard]] QBindable> bindableCoordinates() const { return &this->bCoordinates; } - [[nodiscard]] QBindable bindableActive() const { return &this->bActive; } - - [[nodiscard]] QBindable bindableProjection() const { - return &this->bProjection; - } - - [[nodiscard]] QBindable bindableShouldDisplay() const { return &this->bShouldDisplay; } - [[nodiscard]] QBindable bindableUrgent() const { return &this->bUrgent; } - [[nodiscard]] QBindable bindableCanActivate() const { return &this->bCanActivate; } - [[nodiscard]] QBindable bindableCanDeactivate() const { return &this->bCanDeactivate; } - [[nodiscard]] QBindable bindableCanRemove() const { return &this->bCanRemove; } - - [[nodiscard]] QBindable bindableCanSetProjection() const { - return &this->bCanSetProjection; - } - -signals: - void idChanged(); - void nameChanged(); - void coordinatesChanged(); - void activeChanged(); - void projectionChanged(); - void shouldDisplayChanged(); - void urgentChanged(); - void canActivateChanged(); - void canDeactivateChanged(); - void canRemoveChanged(); - void canSetProjectionChanged(); - -protected: - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(Windowset, QString, bId, &Windowset::idChanged); - Q_OBJECT_BINDABLE_PROPERTY(Windowset, QString, bName, &Windowset::nameChanged); - Q_OBJECT_BINDABLE_PROPERTY(Windowset, QList, bCoordinates); - Q_OBJECT_BINDABLE_PROPERTY(Windowset, bool, bActive, &Windowset::activeChanged); - Q_OBJECT_BINDABLE_PROPERTY(Windowset, WindowsetProjection*, bProjection, &Windowset::projectionChanged); - Q_OBJECT_BINDABLE_PROPERTY(Windowset, bool, bShouldDisplay, &Windowset::shouldDisplayChanged); - Q_OBJECT_BINDABLE_PROPERTY(Windowset, bool, bUrgent, &Windowset::urgentChanged); - Q_OBJECT_BINDABLE_PROPERTY(Windowset, bool, bCanActivate, &Windowset::canActivateChanged); - Q_OBJECT_BINDABLE_PROPERTY(Windowset, bool, bCanDeactivate, &Windowset::canDeactivateChanged); - Q_OBJECT_BINDABLE_PROPERTY(Windowset, bool, bCanRemove, &Windowset::canRemoveChanged); - Q_OBJECT_BINDABLE_PROPERTY(Windowset, bool, bCanSetProjection, &Windowset::canSetProjectionChanged); - // clang-format on -}; - -///! A space occupiable by a Windowset. -/// A WindowsetProjection represents a space that can be occupied by one or more @@Windowset$s. -/// The space is one or more screens. Multiple projections may occupy the same screens. -/// -/// @@WindowManager.screenProjection() can be used to get a projection representing all -/// @@Windowset$s on a given screen regardless of the WM's actual projection layout. -class WindowsetProjection: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_UNCREATABLE(""); - // clang-format off - /// Screens the windowset projection spans, often a single screen or all screens. - Q_PROPERTY(QList screens READ default NOTIFY screensChanged BINDABLE bindableScreens); - /// Windowsets that are currently present on the projection. - Q_PROPERTY(QList windowsets READ default NOTIFY windowsetsChanged BINDABLE bindableWindowsets); - // clang-format on - -public: - explicit WindowsetProjection(QObject* parent); - - [[nodiscard]] QBindable> bindableScreens() const { - return &this->bScreens; - } - - [[nodiscard]] QBindable> bindableQScreens() const { return &this->bQScreens; } - - [[nodiscard]] QBindable> bindableWindowsets() const { - return &this->bWindowsets; - } - -signals: - void screensChanged(); - void windowsetsChanged(); - -protected: - Q_OBJECT_BINDABLE_PROPERTY(WindowsetProjection, QList, bQScreens); - - Q_OBJECT_BINDABLE_PROPERTY( - WindowsetProjection, - QList, - bScreens, - &WindowsetProjection::screensChanged - ); - - Q_OBJECT_BINDABLE_PROPERTY( - WindowsetProjection, - QList, - bWindowsets, - &WindowsetProjection::windowsetsChanged - ); -}; - -} // namespace qs::wm diff --git a/src/x11/i3/ipc/CMakeLists.txt b/src/x11/i3/ipc/CMakeLists.txt index a073459..c228ae3 100644 --- a/src/x11/i3/ipc/CMakeLists.txt +++ b/src/x11/i3/ipc/CMakeLists.txt @@ -17,7 +17,7 @@ qs_add_module_deps_light(quickshell-i3-ipc Quickshell) install_qml_module(quickshell-i3-ipc) -target_link_libraries(quickshell-i3-ipc PRIVATE Qt::Quick quickshell-core) +target_link_libraries(quickshell-i3-ipc PRIVATE Qt::Quick) qs_module_pch(quickshell-i3-ipc SET large) diff --git a/src/x11/i3/ipc/connection.cpp b/src/x11/i3/ipc/connection.cpp index 976167b..b765ebc 100644 --- a/src/x11/i3/ipc/connection.cpp +++ b/src/x11/i3/ipc/connection.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -14,7 +15,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -86,6 +89,9 @@ I3Ipc::I3Ipc(const QList& events): mEvents(events) { QObject::connect(&this->liveEventSocket, &QLocalSocket::readyRead, this, &I3Ipc::eventSocketReady); QObject::connect(&this->liveEventSocket, &QLocalSocket::connected, this, &I3Ipc::subscribe); // clang-format on + + this->liveEventSocketDs.setDevice(&this->liveEventSocket); + this->liveEventSocketDs.setByteOrder(static_cast(QSysInfo::ByteOrder)); } void I3Ipc::makeRequest(const QByteArray& request) { @@ -139,21 +145,34 @@ void I3Ipc::reconnectIPC() { } QVector I3Ipc::parseResponse() { - QVector events; + QVector> events; + const int magicLen = 6; - while (true) { - this->eventReader.startTransaction(); - auto magic = this->eventReader.readBytes(6); - auto size = this->eventReader.readI32(); - auto type = this->eventReader.readI32(); - auto payload = this->eventReader.readBytes(size); - if (!this->eventReader.commitTransaction()) return events; + while (!this->liveEventSocketDs.atEnd()) { + this->liveEventSocketDs.startTransaction(); + this->liveEventSocketDs.startTransaction(); - if (magic.size() < 6 || strncmp(magic.data(), MAGIC.data(), 6) != 0) { + std::array buffer = {}; + qint32 size = 0; + qint32 type = EventCode::Unknown; + + this->liveEventSocketDs.readRawData(buffer.data(), magicLen); + this->liveEventSocketDs >> size; + this->liveEventSocketDs >> type; + + if (!this->liveEventSocketDs.commitTransaction()) break; + + QByteArray payload(size, Qt::Uninitialized); + + this->liveEventSocketDs.readRawData(payload.data(), size); + + if (!this->liveEventSocketDs.commitTransaction()) break; + + if (strncmp(buffer.data(), MAGIC.data(), 6) != 0) { qCWarning(logI3Ipc) << "No magic sequence found in string."; this->reconnectIPC(); break; - } + }; if (I3IpcEvent::intToEvent(type) == EventCode::Unknown) { qCWarning(logI3Ipc) << "Received unknown event"; @@ -185,7 +204,6 @@ void I3Ipc::eventSocketError(QLocalSocket::LocalSocketError error) const { void I3Ipc::eventSocketStateChanged(QLocalSocket::LocalSocketState state) { if (state == QLocalSocket::ConnectedState) { - this->eventReader.setDevice(&this->liveEventSocket); qCInfo(logI3Ipc) << "I3 event socket connected."; emit this->connected(); } else if (state == QLocalSocket::UnconnectedState && this->valid) { diff --git a/src/x11/i3/ipc/connection.hpp b/src/x11/i3/ipc/connection.hpp index 7d03ecd..6100f7e 100644 --- a/src/x11/i3/ipc/connection.hpp +++ b/src/x11/i3/ipc/connection.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -8,8 +9,6 @@ #include #include -#include "../../../core/streamreader.hpp" - namespace qs::i3::ipc { constexpr std::string MAGIC = "i3-ipc"; @@ -93,7 +92,7 @@ protected: QVector> parseResponse(); QLocalSocket liveEventSocket; - StreamReader eventReader; + QDataStream liveEventSocketDs; QString mSocketPath; bool valid = false;