diff --git a/.clang-format b/.clang-format index 8ec602a..610ee65 100644 --- a/.clang-format +++ b/.clang-format @@ -1,6 +1,6 @@ AlignArrayOfStructures: None AlignAfterOpenBracket: BlockIndent -AllowShortBlocksOnASingleLine: Empty +AllowShortBlocksOnASingleLine: Always AllowShortCaseLabelsOnASingleLine: true AllowShortEnumsOnASingleLine: true AllowShortFunctionsOnASingleLine: All diff --git a/.clang-tidy b/.clang-tidy index da14682..002c444 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -20,8 +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, google-runtime-int, @@ -65,8 +63,6 @@ CheckOptions: readability-identifier-naming.ParameterCase: camelBack readability-identifier-naming.VariableCase: camelBack - misc-const-correctness.WarnPointersAsPointers: false - # does not appear to work readability-operators-representation.BinaryOperators: '&&;&=;&;|;~;!;!=;||;|=;^;^=' readability-operators-representation.OverloadedOperators: '&&;&=;&;|;~;!;!=;||;|=;^;^=' diff --git a/.github/ISSUE_TEMPLATE/crash.yml b/.github/ISSUE_TEMPLATE/crash.yml index 958c884..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) -labels: ["unactionable"] +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 submit this report. - - 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: Read the text above. Do not submit the report. - options: - - label: Yes I want this report to be deleted. - 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 86f490c..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 `qs log -r '*=true'`. - 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..93b8458 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,23 +6,17 @@ jobs: name: Nix strategy: matrix: - qtver: [qt6.10.1, qt6.10.0, qt6.9.2, qt6.9.1, qt6.9.0, qt6.8.3, qt6.8.2, qt6.8.1, qt6.8.0, qt6.7.3, qt6.7.2, qt6.7.1, qt6.7.0, qt6.6.3, qt6.6.2, qt6.6.1, qt6.6.0] + qtver: [qt6.9.0, qt6.8.3, qt6.8.2, qt6.8.1, qt6.8.0, qt6.7.3, qt6.7.2, qt6.7.1, qt6.7.0, qt6.6.3, qt6.6.2, qt6.6.1, qt6.6.0] compiler: [clang, gcc] runs-on: ubuntu-latest - permissions: - contents: read - id-token: write steps: - uses: actions/checkout@v4 # Use cachix action over detsys for testing with act. # - uses: cachix/install-nix-action@v27 - uses: DeterminateSystems/nix-installer-action@main - - uses: DeterminateSystems/magic-nix-cache-action@main - with: - use-flakehub: false - name: Download Dependencies - run: nix-build --no-out-link --expr '((import ./ci/matrix.nix) { qtver = "${{ matrix.qtver }}"; compiler = "${{ matrix.compiler }}"; }).unwrapped.inputDerivation' + run: nix-build --no-out-link --expr '((import ./ci/matrix.nix) { qtver = "${{ matrix.qtver }}"; compiler = "${{ matrix.compiler }}"; }).inputDerivation' - name: Build run: nix-build --no-out-link --expr '(import ./ci/matrix.nix) { qtver = "${{ matrix.qtver }}"; compiler = "${{ matrix.compiler }}"; }' @@ -50,16 +44,13 @@ jobs: wayland-protocols \ wayland \ libdrm \ - vulkan-headers \ libxcb \ 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/.github/workflows/lint.yml b/.github/workflows/lint.yml index de0c304..da329cc 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -5,17 +5,11 @@ jobs: lint: name: Lint runs-on: ubuntu-latest - permissions: - contents: read - id-token: write steps: - uses: actions/checkout@v4 # Use cachix action over detsys for testing with act. # - uses: cachix/install-nix-action@v27 - uses: DeterminateSystems/nix-installer-action@main - - uses: DeterminateSystems/magic-nix-cache-action@main - with: - use-flakehub: false - uses: nicknovitski/nix-develop@v1 - name: Check formatting diff --git a/BUILD.md b/BUILD.md index d624a06..aa7c98a 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) @@ -48,7 +55,7 @@ On some distros, private Qt headers are in separate packages which you may have We currently require private headers for the following libraries: - `qt6declarative` -- `qt6wayland` (for Qt versions prior to 6.10) +- `qt6wayland` We recommend an implicit dependency on `qt6svg`. If it is not installed, svg images and svg icons will not work, including system ones. @@ -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 @@ -107,7 +104,7 @@ Currently supported Qt versions: `6.6`, `6.7`. To disable: `-DWAYLAND=OFF` Dependencies: - - `qt6wayland` (for Qt versions prior to 6.10) + - `qt6wayland` - `wayland` (libwayland-client) - `wayland-scanner` (build time) - `wayland-protocols` (static library) @@ -147,8 +144,8 @@ Enables streaming video from monitors and toplevel windows through various proto To disable: `-DSCREENCOPY=OFF` Dependencies: +- `libdrm` - `libgbm` -- `vulkan-headers` (build-time) Specific protocols can also be disabled: - `DSCREENCOPY_ICC=OFF` - Disable screencopy via [ext-image-copy-capture-v1] @@ -195,13 +192,6 @@ To disable: `-DSERVICE_PAM=OFF` Dependencies: `pam` -### Polkit -This feature enables creating Polkit agents that can prompt user for authentication. - -To disable: `-DSERVICE_POLKIT=OFF` - -Dependencies: `polkit`, `glib` - ### Hyprland This feature enables hyprland specific integrations. It requires wayland support but has no extra dependencies. @@ -242,7 +232,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 8293f23..55b5e5d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,5 @@ cmake_minimum_required(VERSION 3.20) -project(quickshell VERSION "0.2.1" LANGUAGES CXX C) - -set(UNRELEASED_FEATURES - "network.2" - "colorquant-imagerect" - "window-parent" -) +project(quickshell VERSION "0.2.0" LANGUAGES CXX C) set(QT_MIN_VERSION "6.6.0") set(CMAKE_CXX_STANDARD 20) @@ -13,9 +7,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" "") @@ -47,17 +38,14 @@ 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(USE_JEMALLOC "Use jemalloc" OFF) -else() - boption(USE_JEMALLOC "Use jemalloc" ON) -endif() -boption(CRASH_HANDLER "Crash Handling" ON) +boption(CRASH_REPORTER "Crash Handling" ON) +boption(USE_JEMALLOC "Use jemalloc" ON) boption(SOCKETS "Unix Sockets" ON) boption(WAYLAND "Wayland" ON) boption(WAYLAND_WLR_LAYERSHELL " Wlroots Layer-Shell" ON REQUIRES WAYLAND) @@ -79,21 +67,18 @@ boption(SERVICE_STATUS_NOTIFIER "System Tray" ON) boption(SERVICE_PIPEWIRE "PipeWire" ON) boption(SERVICE_MPRIS "Mpris" ON) boption(SERVICE_PAM "Pam" ON) -boption(SERVICE_POLKIT "Polkit" ON) boption(SERVICE_GREETD "Greetd" ON) boption(SERVICE_UPOWER "UPower" ON) boption(SERVICE_NOTIFICATIONS "Notifications" ON) boption(BLUETOOTH "Bluetooth" ON) -boption(NETWORK "Network" ON) include(cmake/install-qml-module.cmake) include(cmake/util.cmake) add_compile_options(-Wall -Wextra -Wno-vla-cxx-extension) -# pipewire defines these, breaking PCH +# pipewire defines this, breaking PCH add_compile_definitions(_REENTRANT) -add_compile_options(-fno-strict-overflow) if (FRAME_POINTERS) add_compile_options(-fno-omit-frame-pointer) @@ -115,7 +100,6 @@ if (NOT CMAKE_BUILD_TYPE) endif() set(QT_FPDEPS Gui Qml Quick QuickControls2 Widgets ShaderTools) -set(QT_PRIVDEPS QuickPrivate) include(cmake/pch.cmake) @@ -131,10 +115,9 @@ endif() if (WAYLAND) list(APPEND QT_FPDEPS WaylandClient) - list(APPEND QT_PRIVDEPS WaylandClientPrivate) endif() -if (SERVICE_STATUS_NOTIFIER OR SERVICE_MPRIS OR SERVICE_UPOWER OR SERVICE_NOTIFICATIONS OR BLUETOOTH OR NETWORK) +if (SERVICE_STATUS_NOTIFIER OR SERVICE_MPRIS OR SERVICE_UPOWER OR SERVICE_NOTIFICATIONS OR BLUETOOTH) set(DBUS ON) endif() @@ -144,13 +127,6 @@ endif() find_package(Qt6 REQUIRED COMPONENTS ${QT_FPDEPS}) -# In Qt 6.10, private dependencies are required to be explicit, -# but they could not be explicitly depended on prior to 6.9. -if (Qt6_VERSION VERSION_GREATER_EQUAL "6.9.0") - set(QT_NO_PRIVATE_MODULE_WARNING ON) - find_package(Qt6 REQUIRED COMPONENTS ${QT_PRIVDEPS}) -endif() - set(CMAKE_AUTOUIC OFF) qt_standard_project_setup(REQUIRES 6.6) set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/qml_modules) 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..f60771a 100644 --- a/Justfile +++ b/Justfile @@ -12,9 +12,6 @@ lint-ci: 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") }} - configure target='debug' *FLAGS='': cmake -GNinja -B {{builddir}} \ -DCMAKE_BUILD_TYPE={{ if target == "debug" { "Debug" } else { "RelWithDebInfo" } }} \ 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 deleted file mode 100644 index 761161d..0000000 --- a/changelog/next.md +++ /dev/null @@ -1,84 +0,0 @@ -## Breaking Changes - -### Config paths are no longer canonicalized - -This fixes nix configs changing shell-ids on rebuild as the shell id is now derived from -the symlink path. Configs with a symlink in their path will have a different shell id. - -Shell ids are used to derive the default config / state / cache folders, so those files -will need to be manually moved if using a config behind a symlinked path without an explicitly -set shell id. - -## New Features - -- Added support for creating Polkit agents. -- Added support for creating wayland idle inhibitors. -- Added support for wayland idle timeouts. -- Added support for inhibiting wayland compositor shortcuts for focused windows. -- Added the ability to override Quickshell.cacheDir with a custom path. -- Added minimized, maximized, and fullscreen properties to FloatingWindow. -- Added the ability to handle move and resize events to FloatingWindow. -- Pipewire service now reconnects if pipewire dies or a protocol error occurs. -- Added pipewire audio peak detection. -- Added network management support. -- Added support for grabbing focus from popup windows. -- Added support for IPC signal listeners. -- 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. -- Added ext-background-effect window blur support. -- Added per-corner radius support to Region. -- Added ColorQuantizer region selection. -- Added dialog window support to FloatingWindow. - -## 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. -- Added `AppId` pragma and `QS_APP_ID` environment variable to allow overriding the desktop application ID. -- Added `DropExpensiveFonts` pragma and `QS_DROP_EXPENSIVE_FONTS` environment variable which avoids loading fonts which may cause lag and excessive memory usage if many variants are used. -- Unrecognized pragmas are no longer a hard error for future backward compatibility. - -## Bug Fixes - -- Fixed volume control breaking with pipewire pro audio mode. -- Fixed volume control breaking with bluez streams and potentially others. -- Fixed volume control breaking for devices without route definitions. -- Fixed escape sequence handling in desktop entries. -- 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. -- Fixed HyprlandFocusGrab crashing if windows were destroyed after being passed to it. -- Fixed ScreencopyView pixelation when scaled. -- Fixed JsonAdapter crashing and providing bad data on read when using JsonObject. -- Fixed JsonAdapter sending unnecessary property changes for primitive values. -- Fixed JsonAdapter serialization for lists. -- Fixed pipewire crashes after hotplugging devices and changing default outputs. -- Fixed launches failing for `--daemonize` on some systems. - -## 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. diff --git a/changelog/v0.2.1.md b/changelog/v0.2.1.md deleted file mode 100644 index 596b82f..0000000 --- a/changelog/v0.2.1.md +++ /dev/null @@ -1,17 +0,0 @@ -## New Features - -- Changes to desktop entries are now tracked in real time. - -## Other Changes - -- Added support for Qt 6.10 - -## Bug Fixes - -- Fixed volumes getting stuck on change for pipewire devices with few volume steps. -- Fixed a crash when running out of disk space to write log files. -- Fixed a rare crash when disconnecting a monitor. -- Fixed build issues preventing cross compilation from working. -- Fixed dekstop entries with lower priority than a hidden entry not being hidden. -- Fixed desktop entry keys with mismatched modifier or country not being discarded. -- Fixed greetd hanging when authenticating with a fingerprint. diff --git a/ci/matrix.nix b/ci/matrix.nix index dd20fa5..be2da61 100644 --- a/ci/matrix.nix +++ b/ci/matrix.nix @@ -2,10 +2,7 @@ qtver, compiler, }: let - checkouts = import ./nix-checkouts.nix; - nixpkgs = checkouts.${builtins.replaceStrings ["."] ["_"] qtver}; + nixpkgs = (import ./nix-checkouts.nix).${builtins.replaceStrings ["."] ["_"] qtver}; compilerOverride = (nixpkgs.callPackage ./variations.nix {}).${compiler}; - pkg = (nixpkgs.callPackage ../default.nix {}).override (compilerOverride // { - wayland-protocols = checkouts.latest.wayland-protocols; - }); + pkg = (nixpkgs.callPackage ../default.nix {}).override compilerOverride; in pkg diff --git a/ci/nix-checkouts.nix b/ci/nix-checkouts.nix index 945973c..73c2415 100644 --- a/ci/nix-checkouts.nix +++ b/ci/nix-checkouts.nix @@ -7,28 +7,9 @@ let url = "https://github.com/nixos/nixpkgs/archive/${commit}.tar.gz"; inherit sha256; }) {}; -in rec { - latest = qt6_10_0; - - qt6_10_1 = byCommit { - commit = "ffbc9f8cbaacfb331b6017d5a5abb21a492c9a38"; - sha256 = "0fvbizl7j5rv2rf8j76yw0xb3d9l06hahkjys2a7k1yraznvnafm"; - }; - - qt6_10_0 = byCommit { - commit = "c5ae371f1a6a7fd27823bc500d9390b38c05fa55"; - sha256 = "18g0f8cb9m8mxnz9cf48sks0hib79b282iajl2nysyszph993yp0"; - }; - - qt6_9_2 = byCommit { - commit = "e9f00bd893984bc8ce46c895c3bf7cac95331127"; - sha256 = "0s2mhbrgzxlgkg2yxb0q0hpk8lby1a7w67dxvfmaz4gsmc0bnvfj"; - }; - - qt6_9_1 = byCommit { - commit = "4c202d26483c5ccf3cb95e0053163facde9f047e"; - sha256 = "06l2w4bcgfw7dfanpzpjcf25ydf84in240yplqsss82qx405y9di"; - }; +in { + # For old qt versions, grab the commit before the version bump that has all the patches + # instead of the bumped version. qt6_9_0 = byCommit { commit = "546c545bd0594809a28ab7e869b5f80dd7243ef6"; diff --git a/ci/variations.nix b/ci/variations.nix index b1d2947..b0889be 100644 --- a/ci/variations.nix +++ b/ci/variations.nix @@ -2,6 +2,6 @@ clangStdenv, gccStdenv, }: { - clang = { stdenv = clangStdenv; }; - gcc = { stdenv = gccStdenv; }; + clang = { buildStdenv = clangStdenv; }; + gcc = { buildStdenv = gccStdenv; }; } diff --git a/default.nix b/default.nix index 749ef49..71c949e 100644 --- a/default.nix +++ b/default.nix @@ -2,31 +2,25 @@ lib, nix-gitignore, pkgs, - stdenv, keepDebugInfo, + buildStdenv ? pkgs.clangStdenv, pkg-config, cmake, 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, pipewire, pam, - polkit, - glib, gitRev ? (let headExists = builtins.pathExists ./.git/HEAD; @@ -49,14 +43,10 @@ withPam ? true, withHyprland ? true, withI3 ? true, - withPolkit ? true, - withNetworkManager ? true, }: let - withCrashHandler = withCrashReporter && cpptrace != null && lib.strings.compareVersions cpptrace.version "0.7.2" >= 0; - - unwrapped = stdenv.mkDerivation { + unwrapped = buildStdenv.mkDerivation { pname = "quickshell${lib.optionalString debug "-debug"}"; - version = "0.2.1"; + version = "0.2.0"; src = nix-gitignore.gitignoreSource "/default.nix\n" ./.; dontWrapQtApps = true; # see wrappers @@ -64,36 +54,25 @@ nativeBuildInputs = [ cmake ninja + qt6.qtshadertools spirv-tools pkg-config ] - ++ lib.optional (withWayland && lib.strings.compareVersions qt6.qtbase.version "6.10.0" == -1) qt6.qtwayland - ++ lib.optionals withWayland [ - qt6.qtwayland # qtwaylandscanner required at build time - wayland-scanner - ]; + ++ lib.optional withWayland wayland-scanner; 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 [ qt6.qtwayland wayland wayland-protocols ] + ++ lib.optionals (withWayland && libgbm != null) [ libdrm libgbm ] + ++ lib.optional withX11 xorg.libxcb ++ lib.optional withPam pam - ++ lib.optional withPipewire pipewire - ++ lib.optionals withPolkit [ polkit glib ]; + ++ lib.optional withPipewire pipewire; cmakeBuildType = if debug then "Debug" else "RelWithDebInfo"; @@ -102,14 +81,12 @@ (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)) (lib.cmakeBool "SERVICE_PIPEWIRE" withPipewire) (lib.cmakeBool "SERVICE_PAM" withPam) - (lib.cmakeBool "SERVICE_NETWORKMANAGER" withNetworkManager) - (lib.cmakeBool "SERVICE_POLKIT" withPolkit) (lib.cmakeBool "HYPRLAND" withHyprland) (lib.cmakeBool "I3" withI3) ]; diff --git a/flake.lock b/flake.lock index 2f95a44..7c25aa2 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1768127708, - "narHash": "sha256-1Sm77VfZh3mU0F5OqKABNLWxOuDeHIlcFjsXeeiPazs=", + "lastModified": 1749285348, + "narHash": "sha256-frdhQvPbmDYaScPFiCnfdh3B/Vh81Uuoo0w5TkWmmjU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ffbc9f8cbaacfb331b6017d5a5abb21a492c9a38", + "rev": "3e3afe5174c561dee0df6f2c2b2236990146329f", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 8edda2c..5de9c96 100644 --- a/flake.nix +++ b/flake.nix @@ -4,28 +4,23 @@ }; outputs = { self, nixpkgs }: let - overlayPkgs = p: p.appendOverlays [ self.overlays.default ]; - forEachSystem = fn: nixpkgs.lib.genAttrs nixpkgs.lib.platforms.linux - (system: fn system (overlayPkgs nixpkgs.legacyPackages.${system})); + (system: fn system nixpkgs.legacyPackages.${system}); in { - overlays.default = import ./overlay.nix { - rev = self.rev or self.dirtyRev; - }; - packages = forEachSystem (system: pkgs: rec { - quickshell = pkgs.quickshell; + quickshell = pkgs.callPackage ./default.nix { + gitRev = self.rev or self.dirtyRev; + }; + default = quickshell; }); devShells = forEachSystem (system: pkgs: rec { default = import ./shell.nix { inherit pkgs; - quickshell = self.packages.${system}.quickshell.override { - stdenv = pkgs.clangStdenv; - }; + inherit (self.packages.${system}) quickshell; }; }); }; diff --git a/overlay.nix b/overlay.nix deleted file mode 100644 index d8ea137..0000000 --- a/overlay.nix +++ /dev/null @@ -1,5 +0,0 @@ -{ rev ? null }: (final: prev: { - quickshell = final.callPackage ./default.nix { - gitRev = rev; - }; -}) diff --git a/quickshell.scm b/quickshell.scm index 780bb96..26abdc0 100644 --- a/quickshell.scm +++ b/quickshell.scm @@ -42,7 +42,6 @@ libxcb libxkbcommon linux-pam - polkit mesa pipewire qtbase @@ -56,7 +55,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/shell.nix b/shell.nix index 03a446d..82382f9 100644 --- a/shell.nix +++ b/shell.nix @@ -1,15 +1,14 @@ { pkgs ? import {}, - stdenv ? pkgs.clangStdenv, # faster compiles than gcc - quickshell ? pkgs.callPackage ./default.nix { inherit stdenv; }, + quickshell ? pkgs.callPackage ./default.nix {}, ... }: let tidyfox = import (pkgs.fetchFromGitea { domain = "git.outfoxxed.me"; owner = "outfoxxed"; repo = "tidyfox"; - rev = "9d85d7e7dea2602aa74ec3168955fee69967e92f"; - hash = "sha256-77ERiweF6lumonp2c/124rAoVG6/o9J+Aajhttwtu0w="; + rev = "1f062cc198d1112d13e5128fa1f2ee3dbffe613b"; + sha256 = "kbt0Zc1qHE5fhqBkKz8iue+B+ZANjF1AR/RdgmX1r0I="; }) { inherit pkgs; }; in pkgs.mkShell.override { stdenv = quickshell.stdenv; } { inputsFrom = [ quickshell ]; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0c05419..52db00a 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() @@ -34,7 +33,3 @@ add_subdirectory(services) if (BLUETOOTH) add_subdirectory(bluetooth) endif() - -if (NETWORK) - add_subdirectory(network) -endif() diff --git a/src/bluetooth/adapter.cpp b/src/bluetooth/adapter.cpp index 7f70a27..0d8a319 100644 --- a/src/bluetooth/adapter.cpp +++ b/src/bluetooth/adapter.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "../core/logcat.hpp" diff --git a/src/bluetooth/device.cpp b/src/bluetooth/device.cpp index b140aa0..7265b24 100644 --- a/src/bluetooth/device.cpp +++ b/src/bluetooth/device.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include 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..075abd1 100644 --- a/src/build/build.hpp.in +++ b/src/build/build.hpp.in @@ -1,17 +1,12 @@ #pragma once // NOLINTBEGIN -#define QS_VERSION "@quickshell_VERSION@" -#define QS_VERSION_MAJOR @quickshell_VERSION_MAJOR@ -#define QS_VERSION_MINOR @quickshell_VERSION_MINOR@ -#define QS_VERSION_PATCH @quickshell_VERSION_PATCH@ -#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..7cef987 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 @@ -13,7 +12,6 @@ qt_add_library(quickshell-core STATIC singleton.cpp generation.cpp scan.cpp - scanenv.cpp qsintercept.cpp incubator.cpp lazyloader.cpp @@ -25,7 +23,7 @@ qt_add_library(quickshell-core STATIC model.cpp elapsedtimer.cpp desktopentry.cpp - desktopentrymonitor.cpp + objectrepeater.cpp platformmenu.cpp qsmenu.cpp retainable.cpp @@ -41,8 +39,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 +51,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::Widgets) qs_module_pch(quickshell-core SET large) diff --git a/src/core/colorquantizer.cpp b/src/core/colorquantizer.cpp index d983f76..6cfb05d 100644 --- a/src/core/colorquantizer.cpp +++ b/src/core/colorquantizer.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -25,43 +24,30 @@ namespace { QS_LOGGING_CATEGORY(logColorQuantizer, "quickshell.colorquantizer", QtWarningMsg); } -ColorQuantizerOperation::ColorQuantizerOperation( - QUrl* source, - qreal depth, - QRect imageRect, - qreal rescaleSize -) +ColorQuantizerOperation::ColorQuantizerOperation(QUrl* source, qreal depth, qreal rescaleSize) : source(source) , maxDepth(depth) - , imageRect(imageRect) , rescaleSize(rescaleSize) { - this->setAutoDelete(false); + setAutoDelete(false); } void ColorQuantizerOperation::quantizeImage(const QAtomicInteger& shouldCancel) { - if (shouldCancel.loadAcquire() || this->source->isEmpty()) return; + if (shouldCancel.loadAcquire() || source->isEmpty()) return; - this->colors.clear(); + colors.clear(); - auto image = QImage(this->source->toLocalFile()); - - if (this->imageRect.isValid()) { - image = image.copy(this->imageRect); - } - - if ((image.width() > this->rescaleSize || image.height() > this->rescaleSize) - && this->rescaleSize > 0) - { + auto image = QImage(source->toLocalFile()); + if ((image.width() > rescaleSize || image.height() > rescaleSize) && rescaleSize > 0) { image = image.scaled( - static_cast(this->rescaleSize), - static_cast(this->rescaleSize), + static_cast(rescaleSize), + static_cast(rescaleSize), Qt::KeepAspectRatio, Qt::SmoothTransformation ); } if (image.isNull()) { - qCWarning(logColorQuantizer) << "Failed to load image from" << this->source->toString(); + qCWarning(logColorQuantizer) << "Failed to load image from" << source->toString(); return; } @@ -77,7 +63,7 @@ void ColorQuantizerOperation::quantizeImage(const QAtomicInteger& shouldCa auto startTime = QDateTime::currentDateTime(); - this->colors = this->quantization(pixels, 0); + colors = quantization(pixels, 0); auto endTime = QDateTime::currentDateTime(); auto milliseconds = startTime.msecsTo(endTime); @@ -91,7 +77,7 @@ QList ColorQuantizerOperation::quantization( ) { if (shouldCancel.loadAcquire()) return QList(); - if (depth >= this->maxDepth || rgbValues.isEmpty()) { + if (depth >= maxDepth || rgbValues.isEmpty()) { if (rgbValues.isEmpty()) return QList(); auto totalR = 0; @@ -128,8 +114,8 @@ QList ColorQuantizerOperation::quantization( auto rightHalf = rgbValues.mid(mid); QList result; - result.append(this->quantization(leftHalf, depth + 1)); - result.append(this->quantization(rightHalf, depth + 1)); + result.append(quantization(leftHalf, depth + 1)); + result.append(quantization(rightHalf, depth + 1)); return result; } @@ -173,7 +159,7 @@ void ColorQuantizerOperation::finishRun() { } void ColorQuantizerOperation::finished() { - emit this->done(this->colors); + emit this->done(colors); delete this; } @@ -192,50 +178,39 @@ void ColorQuantizerOperation::run() { void ColorQuantizerOperation::tryCancel() { this->shouldCancel.storeRelease(true); } void ColorQuantizer::componentComplete() { - this->componentCompleted = true; - if (!this->mSource.isEmpty()) this->quantizeAsync(); + componentCompleted = true; + if (!mSource.isEmpty()) quantizeAsync(); } void ColorQuantizer::setSource(const QUrl& source) { - if (this->mSource != source) { - this->mSource = source; + if (mSource != source) { + mSource = source; emit this->sourceChanged(); - if (this->componentCompleted && !this->mSource.isEmpty()) this->quantizeAsync(); + if (this->componentCompleted && !mSource.isEmpty()) quantizeAsync(); } } void ColorQuantizer::setDepth(qreal depth) { - if (this->mDepth != depth) { - this->mDepth = depth; + if (mDepth != depth) { + mDepth = depth; emit this->depthChanged(); - if (this->componentCompleted && !this->mSource.isEmpty()) this->quantizeAsync(); + if (this->componentCompleted) quantizeAsync(); } } -void ColorQuantizer::setImageRect(QRect imageRect) { - if (this->mImageRect != imageRect) { - this->mImageRect = imageRect; - emit this->imageRectChanged(); - - if (this->componentCompleted && !this->mSource.isEmpty()) this->quantizeAsync(); - } -} - -void ColorQuantizer::resetImageRect() { this->setImageRect(QRect()); } - void ColorQuantizer::setRescaleSize(int rescaleSize) { - if (this->mRescaleSize != rescaleSize) { - this->mRescaleSize = rescaleSize; + if (mRescaleSize != rescaleSize) { + mRescaleSize = rescaleSize; emit this->rescaleSizeChanged(); - if (this->componentCompleted && !this->mSource.isEmpty()) this->quantizeAsync(); + if (this->componentCompleted) quantizeAsync(); } } void ColorQuantizer::operationFinished(const QList& result) { - this->bColors = result; + bColors = result; this->liveOperation = nullptr; emit this->colorsChanged(); } @@ -244,13 +219,7 @@ void ColorQuantizer::quantizeAsync() { if (this->liveOperation) this->cancelAsync(); qCDebug(logColorQuantizer) << "Starting color quantization asynchronously"; - - this->liveOperation = new ColorQuantizerOperation( - &this->mSource, - this->mDepth, - this->mImageRect, - this->mRescaleSize - ); + this->liveOperation = new ColorQuantizerOperation(&mSource, mDepth, mRescaleSize); QObject::connect( this->liveOperation, diff --git a/src/core/colorquantizer.hpp b/src/core/colorquantizer.hpp index 0159181..d35a15a 100644 --- a/src/core/colorquantizer.hpp +++ b/src/core/colorquantizer.hpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -17,7 +16,7 @@ class ColorQuantizerOperation Q_OBJECT; public: - explicit ColorQuantizerOperation(QUrl* source, qreal depth, QRect imageRect, qreal rescaleSize); + explicit ColorQuantizerOperation(QUrl* source, qreal depth, qreal rescaleSize); void run() override; void tryCancel(); @@ -45,7 +44,6 @@ private: QList colors; QUrl* source; qreal maxDepth; - QRect imageRect; qreal rescaleSize; }; @@ -80,13 +78,6 @@ class ColorQuantizer /// binary split of the color space Q_PROPERTY(qreal depth READ depth WRITE setDepth NOTIFY depthChanged); - // clang-format off - /// Rectangle that the source image is cropped to. - /// - /// Can be set to `undefined` to reset. - Q_PROPERTY(QRect imageRect READ imageRect WRITE setImageRect RESET resetImageRect NOTIFY imageRectChanged); - // clang-format on - /// The size to rescale the image to, when rescaleSize is 0 then no scaling will be done. /// > [!NOTE] Results from color quantization doesn't suffer much when rescaling, it's /// > reccommended to rescale, otherwise the quantization process will take much longer. @@ -100,24 +91,19 @@ public: [[nodiscard]] QBindable> bindableColors() { return &this->bColors; } - [[nodiscard]] QUrl source() const { return this->mSource; } + [[nodiscard]] QUrl source() const { return mSource; } void setSource(const QUrl& source); - [[nodiscard]] qreal depth() const { return this->mDepth; } + [[nodiscard]] qreal depth() const { return mDepth; } void setDepth(qreal depth); - [[nodiscard]] QRect imageRect() const { return this->mImageRect; } - void setImageRect(QRect imageRect); - void resetImageRect(); - - [[nodiscard]] qreal rescaleSize() const { return this->mRescaleSize; } + [[nodiscard]] qreal rescaleSize() const { return mRescaleSize; } void setRescaleSize(int rescaleSize); signals: void colorsChanged(); void sourceChanged(); void depthChanged(); - void imageRectChanged(); void rescaleSizeChanged(); public slots: @@ -131,7 +117,6 @@ private: ColorQuantizerOperation* liveOperation = nullptr; QUrl mSource; qreal mDepth = 0; - QRect mImageRect; qreal mRescaleSize = 0; Q_OBJECT_BINDABLE_PROPERTY( diff --git a/src/core/debuginfo.cpp b/src/core/debuginfo.cpp deleted file mode 100644 index abc467d..0000000 --- a/src/core/debuginfo.cpp +++ /dev/null @@ -1,176 +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_", - "XDG_CURRENT_DESKTOP=", - }; - - 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..95fcb89 100644 --- a/src/core/desktopentry.cpp +++ b/src/core/desktopentry.cpp @@ -1,27 +1,22 @@ #include "desktopentry.hpp" #include -#include #include #include #include -#include #include +#include +#include #include #include #include #include -#include #include -#include -#include +#include #include -#include -#include #include #include "../io/processcore.hpp" -#include "desktopentrymonitor.hpp" #include "logcat.hpp" #include "model.hpp" #include "qmlglobal.hpp" @@ -61,14 +56,12 @@ struct Locale { [[nodiscard]] int matchScore(const Locale& other) const { if (this->language != other.language) return 0; - - if (!other.modifier.isEmpty() && this->modifier != other.modifier) return 0; - if (!other.territory.isEmpty() && this->territory != other.territory) return 0; + auto territoryMatches = !this->territory.isEmpty() && this->territory == other.territory; + auto modifierMatches = !this->modifier.isEmpty() && this->modifier == other.modifier; auto score = 1; - - if (!other.territory.isEmpty()) score += 2; - if (!other.modifier.isEmpty()) score += 1; + if (territoryMatches) score += 2; + if (modifierMatches) score += 1; return score; } @@ -94,64 +87,57 @@ struct Locale { QDebug operator<<(QDebug debug, const Locale& locale) { auto saver = QDebugStateSaver(debug); debug.nospace() << "Locale(language=" << locale.language << ", territory=" << locale.territory - << ", modifier=" << locale.modifier << ')'; + << ", modifier" << locale.modifier << ')'; return debug; } -ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString& text) { - ParsedDesktopEntryData data; - data.id = id; +void DesktopEntry::parseEntry(const QString& text) { const auto& system = Locale::system(); auto groupName = QString(); auto entries = QHash>(); - auto actionOrder = QStringList(); - auto pendingActions = QHash(); - - auto finishCategory = [&data, &groupName, &entries, &actionOrder, &pendingActions]() { + auto finishCategory = [this, &groupName, &entries]() { if (groupName == "Desktop Entry") { - if (entries.value("Type").second != "Application") return; + if (entries["Type"].second != "Application") return; + if (entries.contains("Hidden") && entries["Hidden"].second == "true") return; for (const auto& [key, pair]: entries.asKeyValueRange()) { auto& [_, value] = pair; - data.entries.insert(key, value); + this->mEntries.insert(key, value); - if (key == "Name") data.name = value; - else if (key == "GenericName") data.genericName = value; - else if (key == "StartupWMClass") data.startupClass = value; - else if (key == "NoDisplay") data.noDisplay = value == "true"; - else if (key == "Hidden") data.hidden = value == "true"; - else if (key == "Comment") data.comment = value; - else if (key == "Icon") data.icon = value; + if (key == "Name") this->mName = value; + else if (key == "GenericName") this->mGenericName = value; + else if (key == "StartupWMClass") this->mStartupClass = value; + else if (key == "NoDisplay") this->mNoDisplay = value == "true"; + else if (key == "Comment") this->mComment = value; + else if (key == "Icon") this->mIcon = value; else if (key == "Exec") { - data.execString = value; - data.command = DesktopEntry::parseExecString(value); - } else if (key == "Path") data.workingDirectory = value; - 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); + this->mExecString = value; + this->mCommand = DesktopEntry::parseExecString(value); + } else if (key == "Path") this->mWorkingDirectory = value; + else if (key == "Terminal") this->mTerminal = value == "true"; + else if (key == "Categories") this->mCategories = value.split(u';', Qt::SkipEmptyParts); + else if (key == "Keywords") this->mKeywords = value.split(u';', Qt::SkipEmptyParts); } } else if (groupName.startsWith("Desktop Action ")) { - auto actionName = groupName.sliced(15); - DesktopActionData action; - action.id = actionName; + auto actionName = groupName.sliced(16); + auto* action = new DesktopAction(actionName, this); for (const auto& [key, pair]: entries.asKeyValueRange()) { const auto& [_, value] = pair; - action.entries.insert(key, value); + action->mEntries.insert(key, value); - if (key == "Name") action.name = value; - else if (key == "Icon") action.icon = value; + if (key == "Name") action->mName = value; + else if (key == "Icon") action->mIcon = value; else if (key == "Exec") { - action.execString = value; - action.command = DesktopEntry::parseExecString(value); + action->mExecString = value; + action->mCommand = DesktopEntry::parseExecString(value); } } - pendingActions.insert(actionName, action); + this->mActions.insert(actionName, action); } entries.clear(); @@ -197,73 +183,16 @@ 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; -} - -void DesktopEntry::updateState(const ParsedDesktopEntryData& newState) { - Qt::beginPropertyUpdateGroup(); - this->bName = newState.name; - this->bGenericName = newState.genericName; - this->bStartupClass = newState.startupClass; - this->bNoDisplay = newState.noDisplay; - this->bComment = newState.comment; - this->bIcon = newState.icon; - this->bExecString = newState.execString; - this->bCommand = newState.command; - this->bWorkingDirectory = newState.workingDirectory; - this->bRunInTerminal = newState.terminal; - this->bCategories = newState.categories; - this->bKeywords = newState.keywords; - Qt::endPropertyUpdateGroup(); - - this->state = newState; - this->updateActions(newState.actions); -} - -void DesktopEntry::updateActions(const QVector& newActions) { - auto old = this->mActions; - this->mActions.clear(); - - for (const auto& d: newActions) { - DesktopAction* act = nullptr; - auto found = std::ranges::find(old, d.id, &DesktopAction::mId); - if (found != old.end()) { - act = *found; - old.erase(found); - } else { - act = new DesktopAction(d.id, this); - } - - Qt::beginPropertyUpdateGroup(); - act->bName = d.name; - act->bIcon = d.icon; - act->bExecString = d.execString; - act->bCommand = d.command; - Qt::endPropertyUpdateGroup(); - - act->mEntries = d.entries; - this->mActions.append(act); - } - - for (auto* leftover: old) { - leftover->deleteLater(); - } } void DesktopEntry::execute() const { - DesktopEntry::doExec(this->bCommand.value(), this->bWorkingDirectory.value()); + DesktopEntry::doExec(this->mCommand, this->mWorkingDirectory); } -bool DesktopEntry::isValid() const { return !this->bName.value().isEmpty(); } +bool DesktopEntry::isValid() const { return !this->mName.isEmpty(); } +bool DesktopEntry::noDisplay() const { return this->mNoDisplay; } -QVector DesktopEntry::actions() const { return this->mActions; } +QVector DesktopEntry::actions() const { return this->mActions.values(); } QVector DesktopEntry::parseExecString(const QString& execString) { QVector arguments; @@ -282,22 +211,16 @@ QVector DesktopEntry::parseExecString(const QString& execString) { currentArgument += '\\'; escape = 0; } - } else if (escape == 2) { - currentArgument += c; - escape = 0; } else if (escape != 0) { - switch (c.unicode()) { - case 's': currentArgument += u' '; break; - case 'n': currentArgument += u'\n'; break; - case 't': currentArgument += u'\t'; break; - case 'r': currentArgument += u'\r'; break; - case '\\': currentArgument += u'\\'; break; - default: + if (escape != 2) { + // Technically this is an illegal state, but the spec has a terrible double escape + // rule in strings for no discernable reason. Assuming someone might understandably + // misunderstand it, treat it as a normal escape and log it. qCWarning(logDesktopEntry).noquote() << "Illegal escape sequence in desktop entry exec string:" << execString; - currentArgument += c; - break; } + + currentArgument += c; escape = 0; } else if (c == u'"' || c == u'\'') { parsingString = false; @@ -343,44 +266,59 @@ void DesktopEntry::doExec(const QList& execString, const QString& worki } void DesktopAction::execute() const { - DesktopEntry::doExec(this->bCommand.value(), this->entry->bWorkingDirectory.value()); + DesktopEntry::doExec(this->mCommand, this->entry->mWorkingDirectory); } -DesktopEntryScanner::DesktopEntryScanner(DesktopEntryManager* manager): manager(manager) { - this->setAutoDelete(true); +DesktopEntryManager::DesktopEntryManager() { + this->scanDesktopEntries(); + this->populateApplications(); } -void DesktopEntryScanner::run() { - const auto& desktopPaths = DesktopEntryManager::desktopPaths(); - auto scanResults = QList(); +void DesktopEntryManager::scanDesktopEntries() { + QList dataPaths; - for (const auto& path: desktopPaths | std::views::reverse) { - auto file = QFileInfo(path); - if (!file.isDir()) continue; - - this->scanDirectory(QDir(path), QString(), scanResults); + if (qEnvironmentVariableIsSet("XDG_DATA_HOME")) { + dataPaths.push_back(qEnvironmentVariable("XDG_DATA_HOME")); + } else if (qEnvironmentVariableIsSet("HOME")) { + dataPaths.push_back(qEnvironmentVariable("HOME") + "/.local/share"); } - QMetaObject::invokeMethod( - this->manager, - "onScanCompleted", - Qt::QueuedConnection, - Q_ARG(QList, scanResults) - ); + if (qEnvironmentVariableIsSet("XDG_DATA_DIRS")) { + auto var = qEnvironmentVariable("XDG_DATA_DIRS"); + dataPaths += var.split(u':', Qt::SkipEmptyParts); + } else { + dataPaths.push_back("/usr/local/share"); + dataPaths.push_back("/usr/share"); + } + + qCDebug(logDesktopEntry) << "Creating desktop entry scanners"; + + for (auto& path: std::ranges::reverse_view(dataPaths)) { + auto p = QDir(path).filePath("applications"); + auto file = QFileInfo(p); + + if (!file.isDir()) { + qCDebug(logDesktopEntry) << "Not scanning path" << p << "as it is not a directory"; + continue; + } + + qCDebug(logDesktopEntry) << "Scanning path" << p; + this->scanPath(p); + } } -void DesktopEntryScanner::scanDirectory( - const QDir& dir, - const QString& idPrefix, - QList& entries -) { - auto dirEntries = dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot); +void DesktopEntryManager::populateApplications() { + for (auto& entry: this->desktopEntries.values()) { + if (!entry->noDisplay()) this->mApplications.insertObject(entry); + } +} - for (auto& entry: dirEntries) { - if (entry.isDir()) { - auto subdirPrefix = idPrefix.isEmpty() ? entry.fileName() : idPrefix + '-' + entry.fileName(); - this->scanDirectory(QDir(entry.absoluteFilePath()), subdirPrefix, entries); - } else if (entry.isFile()) { +void DesktopEntryManager::scanPath(const QDir& dir, const QString& prefix) { + auto entries = dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot); + + for (auto& entry: entries) { + if (entry.isDir()) this->scanPath(entry.absoluteFilePath(), prefix + dir.dirName() + "-"); + else if (entry.isFile()) { auto path = entry.filePath(); if (!path.endsWith(".desktop")) { qCDebug(logDesktopEntry) << "Skipping file" << path << "as it has no .desktop extension"; @@ -393,42 +331,46 @@ void DesktopEntryScanner::scanDirectory( continue; } - auto basename = QFileInfo(entry.fileName()).completeBaseName(); - auto id = idPrefix.isEmpty() ? basename : idPrefix + '-' + basename; - auto content = QString::fromUtf8(file.readAll()); + auto id = prefix + entry.fileName().sliced(0, entry.fileName().length() - 8); + auto lowerId = id.toLower(); - auto data = DesktopEntry::parseText(id, content); - entries.append(std::move(data)); + auto text = QString::fromUtf8(file.readAll()); + auto* dentry = new DesktopEntry(id, this); + dentry->parseEntry(text); + + if (!dentry->isValid()) { + qCDebug(logDesktopEntry) << "Skipping desktop entry" << path; + delete dentry; + continue; + } + + qCDebug(logDesktopEntry) << "Found desktop entry" << id << "at" << path; + + auto conflictingId = this->desktopEntries.contains(id); + + if (conflictingId) { + qCDebug(logDesktopEntry) << "Replacing old entry for" << id; + delete this->desktopEntries.value(id); + this->desktopEntries.remove(id); + this->lowercaseDesktopEntries.remove(lowerId); + } + + this->desktopEntries.insert(id, dentry); + + if (this->lowercaseDesktopEntries.contains(lowerId)) { + qCInfo(logDesktopEntry).nospace() + << "Multiple desktop entries have the same lowercased id " << lowerId + << ". This can cause ambiguity when byId requests are not made with the correct case " + "already."; + + this->lowercaseDesktopEntries.remove(lowerId); + } + + this->lowercaseDesktopEntries.insert(lowerId, dentry); } } } -DesktopEntryManager::DesktopEntryManager(): monitor(new DesktopEntryMonitor(this)) { - QObject::connect( - this->monitor, - &DesktopEntryMonitor::desktopEntriesChanged, - this, - &DesktopEntryManager::handleFileChanges - ); - - DesktopEntryScanner(this).run(); -} - -void DesktopEntryManager::scanDesktopEntries() { - qCDebug(logDesktopEntry) << "Starting desktop entry scan"; - - if (this->scanInProgress) { - qCDebug(logDesktopEntry) << "Scan already in progress, queuing another scan"; - this->scanQueued = true; - return; - } - - this->scanInProgress = true; - this->scanQueued = false; - auto* scanner = new DesktopEntryScanner(this); - QThreadPool::globalInstance()->start(scanner); -} - DesktopEntryManager* DesktopEntryManager::instance() { static auto* instance = new DesktopEntryManager(); // NOLINT return instance; @@ -449,14 +391,14 @@ DesktopEntry* DesktopEntryManager::heuristicLookup(const QString& name) { auto list = this->desktopEntries.values(); - auto iter = std::ranges::find_if(list, [&](DesktopEntry* entry) { - return name == entry->bStartupClass.value(); + auto iter = std::ranges::find_if(list, [&](const DesktopEntry* entry) { + return name == entry->mStartupClass; }); if (iter != list.end()) return *iter; - iter = std::ranges::find_if(list, [&](DesktopEntry* entry) { - return name.toLower() == entry->bStartupClass.value().toLower(); + iter = std::ranges::find_if(list, [&](const DesktopEntry* entry) { + return name.toLower() == entry->mStartupClass.toLower(); }); if (iter != list.end()) return *iter; @@ -465,137 +407,7 @@ DesktopEntry* DesktopEntryManager::heuristicLookup(const QString& name) { ObjectModel* DesktopEntryManager::applications() { return &this->mApplications; } -void DesktopEntryManager::handleFileChanges() { - qCDebug(logDesktopEntry) << "Directory change detected, performing full rescan"; - - if (this->scanInProgress) { - qCDebug(logDesktopEntry) << "Scan already in progress, queuing another scan"; - this->scanQueued = true; - return; - } - - this->scanInProgress = true; - this->scanQueued = false; - auto* scanner = new DesktopEntryScanner(this); - QThreadPool::globalInstance()->start(scanner); -} - -const QStringList& DesktopEntryManager::desktopPaths() { - static const auto paths = []() { - auto dataPaths = QStringList(); - - auto dataHome = qEnvironmentVariable("XDG_DATA_HOME"); - if (dataHome.isEmpty() && qEnvironmentVariableIsSet("HOME")) - dataHome = qEnvironmentVariable("HOME") + "/.local/share"; - if (!dataHome.isEmpty()) dataPaths.append(dataHome + "/applications"); - - auto dataDirs = qEnvironmentVariable("XDG_DATA_DIRS"); - if (dataDirs.isEmpty()) dataDirs = "/usr/local/share:/usr/share"; - - for (const auto& dir: dataDirs.split(':', Qt::SkipEmptyParts)) { - dataPaths.append(dir + "/applications"); - } - - return dataPaths; - }(); - - return paths; -} - -void DesktopEntryManager::onScanCompleted(const QList& scanResults) { - auto guard = qScopeGuard([this] { - this->scanInProgress = false; - if (this->scanQueued) { - this->scanQueued = false; - this->scanDesktopEntries(); - } - }); - - auto oldEntries = this->desktopEntries; - auto newEntries = QHash(); - auto newLowercaseEntries = QHash(); - - for (const auto& data: scanResults) { - auto lowerId = data.id.toLower(); - - if (data.hidden) { - if (auto* victim = newEntries.take(data.id)) victim->deleteLater(); - newLowercaseEntries.remove(lowerId); - - if (auto it = oldEntries.find(data.id); it != oldEntries.end()) { - it.value()->deleteLater(); - oldEntries.erase(it); - } - - qCDebug(logDesktopEntry) << "Masking hidden desktop entry" << data.id; - continue; - } - - DesktopEntry* dentry = nullptr; - - if (auto it = oldEntries.find(data.id); it != oldEntries.end()) { - dentry = it.value(); - oldEntries.erase(it); - dentry->updateState(data); - } else { - dentry = new DesktopEntry(data.id, this); - dentry->updateState(data); - } - - if (!dentry->isValid()) { - qCDebug(logDesktopEntry) << "Skipping desktop entry" << data.id; - if (!oldEntries.contains(data.id)) { - dentry->deleteLater(); - } - continue; - } - - qCDebug(logDesktopEntry) << "Found desktop entry" << data.id; - - auto conflictingId = newEntries.contains(data.id); - - if (conflictingId) { - qCDebug(logDesktopEntry) << "Replacing old entry for" << data.id; - if (auto* victim = newEntries.take(data.id)) victim->deleteLater(); - newLowercaseEntries.remove(lowerId); - } - - newEntries.insert(data.id, dentry); - - if (newLowercaseEntries.contains(lowerId)) { - qCInfo(logDesktopEntry).nospace() - << "Multiple desktop entries have the same lowercased id " << lowerId - << ". This can cause ambiguity when byId requests are not made with the correct case " - "already."; - - newLowercaseEntries.remove(lowerId); - } - - newLowercaseEntries.insert(lowerId, dentry); - } - - this->desktopEntries = newEntries; - this->lowercaseDesktopEntries = newLowercaseEntries; - - auto newApplications = QVector(); - for (auto* entry: this->desktopEntries.values()) - if (!entry->bNoDisplay) newApplications.append(entry); - - this->mApplications.diffUpdate(newApplications); - - emit this->applicationsChanged(); - - for (auto* e: oldEntries) e->deleteLater(); -} - -DesktopEntries::DesktopEntries() { - QObject::connect( - DesktopEntryManager::instance(), - &DesktopEntryManager::applicationsChanged, - this, - &DesktopEntries::applicationsChanged - ); -} +DesktopEntries::DesktopEntries() { DesktopEntryManager::instance(); } DesktopEntry* DesktopEntries::byId(const QString& id) { return DesktopEntryManager::instance()->byId(id); diff --git a/src/core/desktopentry.hpp b/src/core/desktopentry.hpp index 0d1eff2..827a618 100644 --- a/src/core/desktopentry.hpp +++ b/src/core/desktopentry.hpp @@ -6,68 +6,35 @@ #include #include #include -#include #include -#include #include -#include "desktopentrymonitor.hpp" #include "doc.hpp" #include "model.hpp" class DesktopAction; -class DesktopEntryMonitor; - -struct DesktopActionData { - QString id; - QString name; - QString icon; - QString execString; - QVector command; - QHash entries; -}; - -struct ParsedDesktopEntryData { - QString id; - QString name; - QString genericName; - QString startupClass; - bool noDisplay = false; - bool hidden = false; - QString comment; - QString icon; - QString execString; - QVector command; - QString workingDirectory; - bool terminal = false; - QVector categories; - QVector keywords; - QHash entries; - QVector actions; -}; /// A desktop entry. See @@DesktopEntries for details. class DesktopEntry: public QObject { Q_OBJECT; Q_PROPERTY(QString id MEMBER mId CONSTANT); /// Name of the specific application, such as "Firefox". - // clang-format off - Q_PROPERTY(QString name READ default WRITE default NOTIFY nameChanged BINDABLE bindableName); + Q_PROPERTY(QString name MEMBER mName CONSTANT); /// Short description of the application, such as "Web Browser". May be empty. - Q_PROPERTY(QString genericName READ default WRITE default NOTIFY genericNameChanged BINDABLE bindableGenericName); + Q_PROPERTY(QString genericName MEMBER mGenericName CONSTANT); /// Initial class or app id the app intends to use. May be useful for matching running apps /// to desktop entries. - Q_PROPERTY(QString startupClass READ default WRITE default NOTIFY startupClassChanged BINDABLE bindableStartupClass); + Q_PROPERTY(QString startupClass MEMBER mStartupClass CONSTANT); /// If true, this application should not be displayed in menus and launchers. - Q_PROPERTY(bool noDisplay READ default WRITE default NOTIFY noDisplayChanged BINDABLE bindableNoDisplay); + Q_PROPERTY(bool noDisplay MEMBER mNoDisplay CONSTANT); /// Long description of the application, such as "View websites on the internet". May be empty. - Q_PROPERTY(QString comment READ default WRITE default NOTIFY commentChanged BINDABLE bindableComment); + Q_PROPERTY(QString comment MEMBER mComment CONSTANT); /// Name of the icon associated with this application. May be empty. - Q_PROPERTY(QString icon READ default WRITE default NOTIFY iconChanged BINDABLE bindableIcon); + Q_PROPERTY(QString icon MEMBER mIcon CONSTANT); /// The raw `Exec` string from the desktop entry. /// /// > [!WARNING] This cannot be reliably run as a command. See @@command for one you can run. - Q_PROPERTY(QString execString READ default WRITE default NOTIFY execStringChanged BINDABLE bindableExecString); + Q_PROPERTY(QString execString MEMBER mExecString CONSTANT); /// The parsed `Exec` command in the desktop entry. /// /// The entry can be run with @@execute(), or by using this command in @@ -76,14 +43,13 @@ class DesktopEntry: public QObject { /// the invoked process. See @@execute() for details. /// /// > [!NOTE] The provided command does not invoke a terminal even if @@runInTerminal is true. - Q_PROPERTY(QVector command READ default WRITE default NOTIFY commandChanged BINDABLE bindableCommand); + Q_PROPERTY(QVector command MEMBER mCommand CONSTANT); /// The working directory to execute from. - Q_PROPERTY(QString workingDirectory READ default WRITE default NOTIFY workingDirectoryChanged BINDABLE bindableWorkingDirectory); + Q_PROPERTY(QString workingDirectory MEMBER mWorkingDirectory CONSTANT); /// If the application should run in a terminal. - Q_PROPERTY(bool runInTerminal READ default WRITE default NOTIFY runInTerminalChanged BINDABLE bindableRunInTerminal); - Q_PROPERTY(QVector categories READ default WRITE default NOTIFY categoriesChanged BINDABLE bindableCategories); - Q_PROPERTY(QVector keywords READ default WRITE default NOTIFY keywordsChanged BINDABLE bindableKeywords); - // clang-format on + Q_PROPERTY(bool runInTerminal MEMBER mTerminal CONSTANT); + Q_PROPERTY(QVector categories MEMBER mCategories CONSTANT); + Q_PROPERTY(QVector keywords MEMBER mKeywords CONSTANT); Q_PROPERTY(QVector actions READ actions CONSTANT); QML_ELEMENT; QML_UNCREATABLE("DesktopEntry instances must be retrieved from DesktopEntries"); @@ -91,8 +57,7 @@ class DesktopEntry: public QObject { public: explicit DesktopEntry(QString id, QObject* parent): QObject(parent), mId(std::move(id)) {} - static ParsedDesktopEntryData parseText(const QString& id, const QString& text); - void updateState(const ParsedDesktopEntryData& newState); + void parseEntry(const QString& text); /// Run the application. Currently ignores @@runInTerminal and field codes. /// @@ -108,66 +73,31 @@ public: Q_INVOKABLE void execute() const; [[nodiscard]] bool isValid() const; + [[nodiscard]] bool noDisplay() const; [[nodiscard]] QVector actions() const; - [[nodiscard]] QBindable bindableName() const { return &this->bName; } - [[nodiscard]] QBindable bindableGenericName() const { return &this->bGenericName; } - [[nodiscard]] QBindable bindableStartupClass() const { return &this->bStartupClass; } - [[nodiscard]] QBindable bindableNoDisplay() const { return &this->bNoDisplay; } - [[nodiscard]] QBindable bindableComment() const { return &this->bComment; } - [[nodiscard]] QBindable bindableIcon() const { return &this->bIcon; } - [[nodiscard]] QBindable bindableExecString() const { return &this->bExecString; } - [[nodiscard]] QBindable> bindableCommand() const { return &this->bCommand; } - [[nodiscard]] QBindable bindableWorkingDirectory() const { - return &this->bWorkingDirectory; - } - [[nodiscard]] QBindable bindableRunInTerminal() const { return &this->bRunInTerminal; } - [[nodiscard]] QBindable> bindableCategories() const { - return &this->bCategories; - } - [[nodiscard]] QBindable> bindableKeywords() const { return &this->bKeywords; } - // currently ignores all field codes. static QVector parseExecString(const QString& execString); static void doExec(const QList& execString, const QString& workingDirectory); -signals: - void nameChanged(); - void genericNameChanged(); - void startupClassChanged(); - void noDisplayChanged(); - void commentChanged(); - void iconChanged(); - void execStringChanged(); - void commandChanged(); - void workingDirectoryChanged(); - void runInTerminalChanged(); - void categoriesChanged(); - void keywordsChanged(); - public: QString mId; - - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bName, &DesktopEntry::nameChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bGenericName, &DesktopEntry::genericNameChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bStartupClass, &DesktopEntry::startupClassChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, bool, bNoDisplay, &DesktopEntry::noDisplayChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bComment, &DesktopEntry::commentChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bIcon, &DesktopEntry::iconChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bExecString, &DesktopEntry::execStringChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QVector, bCommand, &DesktopEntry::commandChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bWorkingDirectory, &DesktopEntry::workingDirectoryChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, bool, bRunInTerminal, &DesktopEntry::runInTerminalChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QVector, bCategories, &DesktopEntry::categoriesChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QVector, bKeywords, &DesktopEntry::keywordsChanged); - // clang-format on + QString mName; + QString mGenericName; + QString mStartupClass; + bool mNoDisplay = false; + QString mComment; + QString mIcon; + QString mExecString; + QVector mCommand; + QString mWorkingDirectory; + bool mTerminal = false; + QVector mCategories; + QVector mKeywords; private: - void updateActions(const QVector& newActions); - - ParsedDesktopEntryData state; - QVector mActions; + QHash mEntries; + QHash mActions; friend class DesktopAction; }; @@ -176,13 +106,12 @@ private: class DesktopAction: public QObject { Q_OBJECT; Q_PROPERTY(QString id MEMBER mId CONSTANT); - // clang-format off - Q_PROPERTY(QString name READ default WRITE default NOTIFY nameChanged BINDABLE bindableName); - Q_PROPERTY(QString icon READ default WRITE default NOTIFY iconChanged BINDABLE bindableIcon); + Q_PROPERTY(QString name MEMBER mName CONSTANT); + Q_PROPERTY(QString icon MEMBER mIcon CONSTANT); /// The raw `Exec` string from the action. /// /// > [!WARNING] This cannot be reliably run as a command. See @@command for one you can run. - Q_PROPERTY(QString execString READ default WRITE default NOTIFY execStringChanged BINDABLE bindableExecString); + Q_PROPERTY(QString execString MEMBER mExecString CONSTANT); /// The parsed `Exec` command in the action. /// /// The entry can be run with @@execute(), or by using this command in @@ -191,8 +120,7 @@ class DesktopAction: public QObject { /// the invoked process. /// /// > [!NOTE] The provided command does not invoke a terminal even if @@runInTerminal is true. - Q_PROPERTY(QVector command READ default WRITE default NOTIFY commandChanged BINDABLE bindableCommand); - // clang-format on + Q_PROPERTY(QVector command MEMBER mCommand CONSTANT); QML_ELEMENT; QML_UNCREATABLE("DesktopAction instances must be retrieved from a DesktopEntry"); @@ -208,47 +136,18 @@ public: /// and @@DesktopEntry.workingDirectory. Q_INVOKABLE void execute() const; - [[nodiscard]] QBindable bindableName() const { return &this->bName; } - [[nodiscard]] QBindable bindableIcon() const { return &this->bIcon; } - [[nodiscard]] QBindable bindableExecString() const { return &this->bExecString; } - [[nodiscard]] QBindable> bindableCommand() const { return &this->bCommand; } - -signals: - void nameChanged(); - void iconChanged(); - void execStringChanged(); - void commandChanged(); - private: DesktopEntry* entry; QString mId; + QString mName; + QString mIcon; + QString mExecString; + QVector mCommand; QHash mEntries; - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(DesktopAction, QString, bName, &DesktopAction::nameChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopAction, QString, bIcon, &DesktopAction::iconChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopAction, QString, bExecString, &DesktopAction::execStringChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopAction, QVector, bCommand, &DesktopAction::commandChanged); - // clang-format on - friend class DesktopEntry; }; -class DesktopEntryManager; - -class DesktopEntryScanner: public QRunnable { -public: - explicit DesktopEntryScanner(DesktopEntryManager* manager); - - void run() override; - // clang-format off - void scanDirectory(const QDir& dir, const QString& idPrefix, QList& entries); - // clang-format on - -private: - DesktopEntryManager* manager; -}; - class DesktopEntryManager: public QObject { Q_OBJECT; @@ -262,26 +161,15 @@ public: static DesktopEntryManager* instance(); - static const QStringList& desktopPaths(); - -signals: - void applicationsChanged(); - -private slots: - void handleFileChanges(); - void onScanCompleted(const QList& scanResults); - private: explicit DesktopEntryManager(); + void populateApplications(); + void scanPath(const QDir& dir, const QString& prefix = QString()); + QHash desktopEntries; QHash lowercaseDesktopEntries; ObjectModel mApplications {this}; - DesktopEntryMonitor* monitor = nullptr; - bool scanInProgress = false; - bool scanQueued = false; - - friend class DesktopEntryScanner; }; ///! Desktop entry index. @@ -313,7 +201,4 @@ public: Q_INVOKABLE [[nodiscard]] static DesktopEntry* heuristicLookup(const QString& name); [[nodiscard]] static ObjectModel* applications(); - -signals: - void applicationsChanged(); }; diff --git a/src/core/desktopentrymonitor.cpp b/src/core/desktopentrymonitor.cpp deleted file mode 100644 index bed6ef1..0000000 --- a/src/core/desktopentrymonitor.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include "desktopentrymonitor.hpp" - -#include -#include -#include -#include -#include -#include - -#include "desktopentry.hpp" - -namespace { -void addPathAndParents(QFileSystemWatcher& watcher, const QString& path) { - watcher.addPath(path); - - auto p = QFileInfo(path).absolutePath(); - while (!p.isEmpty()) { - watcher.addPath(p); - const auto parent = QFileInfo(p).dir().absolutePath(); - if (parent == p) break; - p = parent; - } -} -} // namespace - -DesktopEntryMonitor::DesktopEntryMonitor(QObject* parent): QObject(parent) { - this->debounceTimer.setSingleShot(true); - this->debounceTimer.setInterval(100); - - QObject::connect( - &this->watcher, - &QFileSystemWatcher::directoryChanged, - this, - &DesktopEntryMonitor::onDirectoryChanged - ); - QObject::connect( - &this->debounceTimer, - &QTimer::timeout, - this, - &DesktopEntryMonitor::processChanges - ); - - this->startMonitoring(); -} - -void DesktopEntryMonitor::startMonitoring() { - for (const auto& path: DesktopEntryManager::desktopPaths()) { - if (!QDir(path).exists()) continue; - addPathAndParents(this->watcher, path); - this->scanAndWatch(path); - } -} - -void DesktopEntryMonitor::scanAndWatch(const QString& dirPath) { - auto dir = QDir(dirPath); - if (!dir.exists()) return; - - this->watcher.addPath(dirPath); - - auto subdirs = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks); - for (const auto& subdir: subdirs) this->watcher.addPath(subdir.absoluteFilePath()); -} - -void DesktopEntryMonitor::onDirectoryChanged(const QString& /*path*/) { - this->debounceTimer.start(); -} - -void DesktopEntryMonitor::processChanges() { emit this->desktopEntriesChanged(); } \ No newline at end of file diff --git a/src/core/desktopentrymonitor.hpp b/src/core/desktopentrymonitor.hpp deleted file mode 100644 index eb3251d..0000000 --- a/src/core/desktopentrymonitor.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -class DesktopEntryMonitor: public QObject { - Q_OBJECT - -public: - explicit DesktopEntryMonitor(QObject* parent = nullptr); - ~DesktopEntryMonitor() override = default; - DesktopEntryMonitor(const DesktopEntryMonitor&) = delete; - DesktopEntryMonitor& operator=(const DesktopEntryMonitor&) = delete; - DesktopEntryMonitor(DesktopEntryMonitor&&) = delete; - DesktopEntryMonitor& operator=(DesktopEntryMonitor&&) = delete; - -signals: - void desktopEntriesChanged(); - -private slots: - void onDirectoryChanged(const QString& path); - void processChanges(); - -private: - void startMonitoring(); - void scanAndWatch(const QString& dirPath); - - QFileSystemWatcher watcher; - QTimer debounceTimer; -}; diff --git a/src/core/generation.cpp b/src/core/generation.cpp index 21febc3..fee9441 100644 --- a/src/core/generation.cpp +++ b/src/core/generation.cpp @@ -11,12 +11,12 @@ #include #include #include +#include #include #include #include #include #include -#include #include #include "iconimageprovider.hpp" @@ -49,8 +49,7 @@ EngineGeneration::EngineGeneration(const QDir& rootPath, QmlScanner scanner) this->engine->addImportPath("qs:@/"); this->engine->setNetworkAccessManagerFactory(&this->interceptNetFactory); - this->incubationController.initLoop(); - this->engine->setIncubationController(&this->incubationController); + this->engine->setIncubationController(&this->delayedIncubationController); this->engine->addImageProvider("icon", new IconImageProvider()); this->engine->addImageProvider("qsimage", new QsImageProvider()); @@ -135,7 +134,7 @@ void EngineGeneration::onReload(EngineGeneration* old) { // new generation acquires it then incubators will hang intermittently qCDebug(logIncubator) << "Locking incubation controllers of old generation" << old; old->incubationControllersLocked = true; - old->updateIncubationMode(); + old->assignIncubationController(); } QObject::connect(this->engine, &QQmlEngine::quit, this, &EngineGeneration::quit); @@ -209,8 +208,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 +228,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,17 +236,96 @@ 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; } } } +void EngineGeneration::registerIncubationController(QQmlIncubationController* controller) { + // We only want controllers that we can swap out if destroyed. + // This happens if the window owning the active controller dies. + auto* obj = dynamic_cast(controller); + if (!obj) { + qCWarning(logIncubator) << "Could not register incubation controller as it is not a QObject" + << controller; + + return; + } + + QObject::connect( + obj, + &QObject::destroyed, + this, + &EngineGeneration::incubationControllerDestroyed, + Qt::UniqueConnection + ); + + this->incubationControllers.push_back(obj); + qCDebug(logIncubator) << "Registered incubation controller" << obj << "to generation" << this; + + // This function can run during destruction. + if (this->engine == nullptr) return; + + if (this->engine->incubationController() == &this->delayedIncubationController) { + this->assignIncubationController(); + } +} + +// Multiple controllers may be destroyed at once. Dynamic casts must be performed before working +// with any controllers. The QQmlIncubationController destructor will already have run by the +// point QObject::destroyed is called, so we can't cast to that. +void EngineGeneration::deregisterIncubationController(QQmlIncubationController* controller) { + auto* obj = dynamic_cast(controller); + if (!obj) { + qCCritical(logIncubator) << "Deregistering incubation controller which is not a QObject, " + "however only QObject controllers should be registered."; + } + + QObject::disconnect(obj, nullptr, this, nullptr); + + if (this->incubationControllers.removeOne(obj)) { + qCDebug(logIncubator) << "Deregistered incubation controller" << obj << "from" << this; + } else { + qCCritical(logIncubator) << "Failed to deregister incubation controller" << obj << "from" + << this << "as it was not registered to begin with"; + qCCritical(logIncubator) << "Current registered incuabation controllers" + << this->incubationControllers; + } + + // This function can run during destruction. + if (this->engine == nullptr) return; + + if (this->engine->incubationController() == controller) { + qCDebug(logIncubator + ) << "Destroyed incubation controller was currently active, reassigning from pool"; + this->assignIncubationController(); + } +} + +void EngineGeneration::incubationControllerDestroyed() { + auto* sender = this->sender(); + + if (this->incubationControllers.removeAll(sender) != 0) { + qCDebug(logIncubator) << "Destroyed incubation controller" << sender << "deregistered from" + << this; + } else { + qCCritical(logIncubator) << "Destroyed incubation controller" << sender + << "was not registered, but its destruction was observed by" << this; + + return; + } + + // This function can run during destruction. + if (this->engine == nullptr) return; + + if (dynamic_cast(this->engine->incubationController()) == sender) { + qCDebug(logIncubator + ) << "Destroyed incubation controller was currently active, reassigning from pool"; + this->assignIncubationController(); + } +} + void EngineGeneration::onEngineWarnings(const QList& warnings) { for (const auto& error: warnings) { const auto& url = error.url(); @@ -296,23 +367,20 @@ void EngineGeneration::exit(int code) { this->destroy(); } -void EngineGeneration::trackWindowIncubationController(QQuickWindow* window) { - if (this->trackedWindows.contains(window)) return; +void EngineGeneration::assignIncubationController() { + QQmlIncubationController* controller = nullptr; - QObject::connect(window, &QObject::destroyed, this, &EngineGeneration::onTrackedWindowDestroyed); - this->trackedWindows.append(window); - this->updateIncubationMode(); -} + if (this->incubationControllersLocked || this->incubationControllers.isEmpty()) { + controller = &this->delayedIncubationController; + } else { + controller = dynamic_cast(this->incubationControllers.first()); + } -void EngineGeneration::onTrackedWindowDestroyed(QObject* object) { - this->trackedWindows.removeAll(static_cast(object)); // NOLINT - this->updateIncubationMode(); -} + qCDebug(logIncubator) << "Assigning incubation controller" << controller << "to generation" + << this + << "fallback:" << (controller == &this->delayedIncubationController); -void EngineGeneration::updateIncubationMode() { - // If we're in a situation with only hidden but tracked windows this might be wrong, - // but it seems to at least work. - this->incubationController.setIncubationMode(!this->trackedWindows.empty()); + this->engine->setIncubationController(controller); } EngineGeneration* EngineGeneration::currentGeneration() { diff --git a/src/core/generation.hpp b/src/core/generation.hpp index 4543408..3c0c4ae 100644 --- a/src/core/generation.hpp +++ b/src/core/generation.hpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include "incubator.hpp" @@ -41,7 +40,8 @@ public: void setWatchingFiles(bool watching); bool setExtraWatchedFiles(const QVector& files); - void trackWindowIncubationController(QQuickWindow* window); + void registerIncubationController(QQmlIncubationController* controller); + void deregisterIncubationController(QQmlIncubationController* controller); // takes ownership void registerExtension(const void* key, EngineGenerationExt* extension); @@ -65,7 +65,7 @@ public: QFileSystemWatcher* watcher = nullptr; QVector deletedWatchedFiles; QVector extraWatchedFiles; - QsIncubationController incubationController; + DelayedQmlIncubationController delayedIncubationController; bool reloadComplete = false; QuickshellGlobal* qsgInstance = nullptr; @@ -84,13 +84,13 @@ public slots: private slots: void onFileChanged(const QString& name); void onDirectoryChanged(); - void onTrackedWindowDestroyed(QObject* object); + void incubationControllerDestroyed(); static void onEngineWarnings(const QList& warnings); private: void postReload(); - void updateIncubationMode(); - QVector trackedWindows; + void assignIncubationController(); + QVector incubationControllers; bool incubationControllersLocked = false; QHash extensions; diff --git a/src/core/iconimageprovider.cpp b/src/core/iconimageprovider.cpp index 1dbe3e7..43e00fd 100644 --- a/src/core/iconimageprovider.cpp +++ b/src/core/iconimageprovider.cpp @@ -19,7 +19,8 @@ IconImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& re if (splitIdx != -1) { iconName = id.sliced(0, splitIdx); path = id.sliced(splitIdx + 6); - path = QString("/%1/%2").arg(path, iconName.sliced(iconName.lastIndexOf('/') + 1)); + qWarning() << "Searching custom icon paths is not yet supported. Icon path will be ignored for" + << id; } else { splitIdx = id.indexOf("?fallback="); if (splitIdx != -1) { @@ -31,8 +32,7 @@ IconImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& re } auto icon = QIcon::fromTheme(iconName); - if (icon.isNull() && !fallbackName.isEmpty()) icon = QIcon::fromTheme(fallbackName); - if (icon.isNull() && !path.isEmpty()) icon = QPixmap(path); + if (icon.isNull()) icon = QIcon::fromTheme(fallbackName); auto targetSize = requestedSize.isValid() ? requestedSize : QSize(100, 100); if (targetSize.width() == 0 || targetSize.height() == 0) targetSize = QSize(2, 2); diff --git a/src/core/iconprovider.cpp b/src/core/iconprovider.cpp index 383f7e1..99b423e 100644 --- a/src/core/iconprovider.cpp +++ b/src/core/iconprovider.cpp @@ -22,8 +22,8 @@ class PixmapCacheIconEngine: public QIconEngine { QIcon::Mode /*unused*/, QIcon::State /*unused*/ ) override { - qFatal() - << "Unexpected icon paint request bypassed pixmap method. Please report this as a bug."; + qFatal( + ) << "Unexpected icon paint request bypassed pixmap method. Please report this as a bug."; } QPixmap pixmap(const QSize& size, QIcon::Mode /*unused*/, QIcon::State /*unused*/) override { diff --git a/src/core/incubator.cpp b/src/core/incubator.cpp index f031b11..c9d149a 100644 --- a/src/core/incubator.cpp +++ b/src/core/incubator.cpp @@ -1,16 +1,7 @@ #include "incubator.hpp" -#include -#include -#include #include -#include -#include -#include -#include -#include #include -#include #include #include "logcat.hpp" @@ -24,112 +15,3 @@ void QsQmlIncubator::statusChanged(QQmlIncubator::Status status) { default: break; } } - -void QsIncubationController::initLoop() { - auto* app = static_cast(QGuiApplication::instance()); // NOLINT - this->renderLoop = QSGRenderLoop::instance(); - - QObject::connect( - app, - &QGuiApplication::screenAdded, - this, - &QsIncubationController::updateIncubationTime - ); - - QObject::connect( - app, - &QGuiApplication::screenRemoved, - this, - &QsIncubationController::updateIncubationTime - ); - - this->updateIncubationTime(); - - QObject::connect( - this->renderLoop, - &QSGRenderLoop::timeToIncubate, - this, - &QsIncubationController::incubate - ); - - QAnimationDriver* animationDriver = this->renderLoop->animationDriver(); - if (animationDriver) { - QObject::connect( - animationDriver, - &QAnimationDriver::stopped, - this, - &QsIncubationController::animationStopped - ); - } else { - qCInfo(logIncubator) << "Render loop does not have animation driver, animationStopped cannot " - "be used to trigger incubation."; - } -} - -void QsIncubationController::setIncubationMode(bool render) { - if (render == this->followRenderloop) return; - this->followRenderloop = render; - - if (render) { - qCDebug(logIncubator) << "Incubation mode changed: render loop driven"; - } else { - qCDebug(logIncubator) << "Incubation mode changed: event loop driven"; - } - - if (!render && this->incubatingObjectCount()) this->incubateLater(); -} - -void QsIncubationController::timerEvent(QTimerEvent* /*event*/) { - this->killTimer(this->timerId); - this->timerId = 0; - this->incubate(); -} - -void QsIncubationController::incubateLater() { - if (this->followRenderloop) { - if (this->timerId != 0) { - this->killTimer(this->timerId); - this->timerId = 0; - } - - // Incubate again at the end of the event processing queue - QMetaObject::invokeMethod(this, &QsIncubationController::incubate, Qt::QueuedConnection); - } else if (this->timerId == 0) { - // Wait for a while before processing the next batch. Using a - // timer to avoid starvation of system events. - this->timerId = this->startTimer(this->incubationTime); - } -} - -void QsIncubationController::incubate() { - if ((!this->followRenderloop || this->renderLoop) && this->incubatingObjectCount()) { - if (!this->followRenderloop) { - this->incubateFor(10); - if (this->incubatingObjectCount()) this->incubateLater(); - } else if (this->renderLoop->interleaveIncubation()) { - this->incubateFor(this->incubationTime); - } else { - this->incubateFor(this->incubationTime * 2); - if (this->incubatingObjectCount()) this->incubateLater(); - } - } -} - -void QsIncubationController::animationStopped() { this->incubate(); } - -void QsIncubationController::incubatingObjectCountChanged(int count) { - if (count - && (!this->followRenderloop - || (this->renderLoop && !this->renderLoop->interleaveIncubation()))) - { - this->incubateLater(); - } -} - -void QsIncubationController::updateIncubationTime() { - auto* screen = QGuiApplication::primaryScreen(); - if (!screen) return; - - // 1/3 frame on primary screen - this->incubationTime = qMax(1, static_cast(1000 / screen->refreshRate() / 3)); -} diff --git a/src/core/incubator.hpp b/src/core/incubator.hpp index 15dc49a..5ebb9a0 100644 --- a/src/core/incubator.hpp +++ b/src/core/incubator.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include @@ -26,37 +25,7 @@ signals: void failed(); }; -class QSGRenderLoop; - -class QsIncubationController - : public QObject - , public QQmlIncubationController { - Q_OBJECT - -public: - void initLoop(); - void setIncubationMode(bool render); - void incubateLater(); - -protected: - void timerEvent(QTimerEvent* event) override; - -public slots: - void incubate(); - void animationStopped(); - void updateIncubationTime(); - -protected: - void incubatingObjectCountChanged(int count) override; - -private: -// QPointer did not work with forward declarations prior to 6.7 -#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) - QPointer renderLoop = nullptr; -#else - QSGRenderLoop* renderLoop = nullptr; -#endif - int incubationTime = 0; - int timerId = 0; - bool followRenderloop = false; +class DelayedQmlIncubationController: public QQmlIncubationController { + // Do nothing. + // This ensures lazy loaders don't start blocking before onReload creates windows. }; diff --git a/src/core/instanceinfo.cpp b/src/core/instanceinfo.cpp index b9b7b44..7f0132b 100644 --- a/src/core/instanceinfo.cpp +++ b/src/core/instanceinfo.cpp @@ -3,14 +3,12 @@ #include QDataStream& operator<<(QDataStream& stream, const InstanceInfo& info) { - stream << info.instanceId << info.configPath << info.shellId << info.appId << info.launchTime - << info.pid << info.display; + stream << info.instanceId << info.configPath << info.shellId << info.launchTime << info.pid; return stream; } QDataStream& operator>>(QDataStream& stream, InstanceInfo& info) { - stream >> info.instanceId >> info.configPath >> info.shellId >> info.appId >> info.launchTime - >> info.pid >> info.display; + stream >> info.instanceId >> info.configPath >> info.shellId >> info.launchTime >> info.pid; return stream; } diff --git a/src/core/instanceinfo.hpp b/src/core/instanceinfo.hpp index a4a7e66..98ce614 100644 --- a/src/core/instanceinfo.hpp +++ b/src/core/instanceinfo.hpp @@ -9,10 +9,8 @@ struct InstanceInfo { QString instanceId; QString configPath; QString shellId; - QString appId; QDateTime launchTime; pid_t pid = -1; - QString display; static InstanceInfo CURRENT; // NOLINT }; @@ -36,8 +34,6 @@ namespace qs::crash { struct CrashInfo { int logFd = -1; - int traceFd = -1; - int infoFd = -1; static CrashInfo INSTANCE; // NOLINT }; diff --git a/src/core/lazyloader.hpp b/src/core/lazyloader.hpp index 56cc964..dbaad4b 100644 --- a/src/core/lazyloader.hpp +++ b/src/core/lazyloader.hpp @@ -82,6 +82,9 @@ /// > Notably, @@Variants does not corrently support asynchronous /// > loading, meaning using it inside a LazyLoader will block similarly to not /// > having a loader to start with. +/// +/// > [!WARNING] LazyLoaders do not start loading before the first window is created, +/// > meaning if you create all windows inside of lazy loaders, none of them will ever load. class LazyLoader: public Reloadable { Q_OBJECT; /// The fully loaded item if the loader is @@loading or @@active, or `null` diff --git a/src/core/logging.cpp b/src/core/logging.cpp index 1b19fab..cb3a214 100644 --- a/src/core/logging.cpp +++ b/src/core/logging.cpp @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include @@ -28,13 +27,7 @@ #include #include #include -#ifdef __linux__ #include -#include -#endif -#ifdef __FreeBSD__ -#include -#endif #include "instanceinfo.hpp" #include "logcat.hpp" @@ -50,57 +43,6 @@ using namespace qt_logging_registry; QS_LOGGING_CATEGORY(logLogging, "quickshell.logging", QtWarningMsg); -namespace { -bool copyFileData(int sourceFd, int destFd, qint64 size) { - auto usize = static_cast(size); - -#ifdef __linux__ - off_t offset = 0; - auto remaining = usize; - - while (remaining > 0) { - auto r = sendfile(destFd, sourceFd, &offset, remaining); - if (r == -1) { - if (errno == EINTR) continue; - return false; - } - if (r == 0) break; - remaining -= static_cast(r); - } - - return true; -#else - std::array buffer = {}; - auto remaining = usize; - - while (remaining > 0) { - auto chunk = std::min(remaining, buffer.size()); - auto r = ::read(sourceFd, buffer.data(), chunk); - if (r == -1) { - if (errno == EINTR) continue; - return false; - } - if (r == 0) break; - - auto readBytes = static_cast(r); - size_t written = 0; - while (written < readBytes) { - auto w = ::write(destFd, buffer.data() + written, readBytes - written); - if (w == -1) { - if (errno == EINTR) continue; - return false; - } - written += static_cast(w); - } - - remaining -= readBytes; - } - - return true; -#endif -} -} // namespace - bool LogMessage::operator==(const LogMessage& other) const { // note: not including time return this->type == other.type && this->category == other.category && this->body == other.body; @@ -221,7 +163,6 @@ void LogManager::messageHandler( } if (display) { - auto locker = QMutexLocker(&self->stdoutMutex); LogMessage::formatMessage( self->stdoutStream, message, @@ -310,15 +251,10 @@ void LogManager::init( instance->rules->append(parser.rules()); } - instance->lastCategoryFilter = QLoggingCategory::installFilter(&LogManager::filterCategory); - - if (instance->lastCategoryFilter == &LogManager::filterCategory) { - qCFatal(logLogging) << "Quickshell's log filter has been installed twice. This is a bug."; - instance->lastCategoryFilter = nullptr; - } - qInstallMessageHandler(&LogManager::messageHandler); + instance->lastCategoryFilter = QLoggingCategory::installFilter(&LogManager::filterCategory); + qCDebug(logLogging) << "Creating offthread logger..."; auto* thread = new QThread(); instance->threadProxy.moveToThread(thread); @@ -377,12 +313,8 @@ void ThreadLogging::init() { if (logMfd != -1) { this->file = new QFile(); - - if (this->file->open(logMfd, QFile::ReadWrite, QFile::AutoCloseHandle)) { - this->fileStream.setDevice(this->file); - } else { - qCCritical(logLogging) << "Failed to open early logging memfd."; - } + this->file->open(logMfd, QFile::ReadWrite, QFile::AutoCloseHandle); + this->fileStream.setDevice(this->file); } if (dlogMfd != -1) { @@ -390,19 +322,14 @@ void ThreadLogging::init() { this->detailedFile = new QFile(); // buffered by WriteBuffer - if (this->detailedFile - ->open(dlogMfd, QFile::ReadWrite | QFile::Unbuffered, QFile::AutoCloseHandle)) - { - this->detailedWriter.setDevice(this->detailedFile); + this->detailedFile->open(dlogMfd, QFile::ReadWrite | QFile::Unbuffered, QFile::AutoCloseHandle); + this->detailedWriter.setDevice(this->detailedFile); - if (!this->detailedWriter.writeHeader()) { - qCCritical(logLogging) << "Could not write header for detailed logs."; - this->detailedWriter.setDevice(nullptr); - delete this->detailedFile; - this->detailedFile = nullptr; - } - } else { - qCCritical(logLogging) << "Failed to open early detailed logging memfd."; + if (!this->detailedWriter.writeHeader()) { + qCCritical(logLogging) << "Could not write header for detailed logs."; + this->detailedWriter.setDevice(nullptr); + delete this->detailedFile; + this->detailedFile = nullptr; } } @@ -425,8 +352,7 @@ void ThreadLogging::initFs() { auto* runDir = QsPaths::instance()->instanceRunDir(); if (!runDir) { - qCCritical( - logLogging + qCCritical(logLogging ) << "Could not start filesystem logging as the runtime directory could not be created."; return; } @@ -437,8 +363,7 @@ void ThreadLogging::initFs() { auto* detailedFile = new QFile(detailedPath); if (!file->open(QFile::ReadWrite | QFile::Truncate)) { - qCCritical( - logLogging + qCCritical(logLogging ) << "Could not start filesystem logger as the log file could not be created:" << path; delete file; @@ -449,14 +374,13 @@ void ThreadLogging::initFs() { // buffered by WriteBuffer if (!detailedFile->open(QFile::ReadWrite | QFile::Truncate | QFile::Unbuffered)) { - qCCritical( - logLogging + qCCritical(logLogging ) << "Could not start detailed filesystem logger as the log file could not be created:" << detailedPath; delete detailedFile; detailedFile = nullptr; } else { - struct flock lock = { + auto lock = flock { .l_type = F_WRLCK, .l_whence = SEEK_SET, .l_start = 0, @@ -478,11 +402,7 @@ void ThreadLogging::initFs() { auto* oldFile = this->file; if (oldFile) { oldFile->seek(0); - - if (!copyFileData(oldFile->handle(), file->handle(), oldFile->size())) { - qCritical(logLogging) << "Failed to copy log from memfd with error code " << errno - << qt_error_string(errno); - } + sendfile(file->handle(), oldFile->handle(), nullptr, oldFile->size()); } this->file = file; @@ -494,10 +414,7 @@ void ThreadLogging::initFs() { auto* oldFile = this->detailedFile; if (oldFile) { oldFile->seek(0); - if (!copyFileData(oldFile->handle(), detailedFile->handle(), oldFile->size())) { - qCritical(logLogging) << "Failed to copy detailed log from memfd with error code " << errno - << qt_error_string(errno); - } + sendfile(detailedFile->handle(), oldFile->handle(), nullptr, oldFile->size()); } crash::CrashInfo::INSTANCE.logFd = detailedFile->handle(); @@ -541,13 +458,13 @@ void ThreadLogging::onMessage(const LogMessage& msg, bool showInSparse) { } if (!this->detailedWriter.write(msg) || (this->detailedFile && !this->detailedFile->flush())) { - this->detailedWriter.setDevice(nullptr); - if (this->detailedFile) { - this->detailedFile->close(); - this->detailedFile = nullptr; qCCritical(logLogging) << "Detailed logger failed to write. Ending detailed logs."; } + + this->detailedWriter.setDevice(nullptr); + this->detailedFile->close(); + this->detailedFile = nullptr; } } @@ -820,11 +737,11 @@ bool EncodedLogReader::readVarInt(quint32* slot) { if (!this->reader.skip(1)) return false; *slot = qFromLittleEndian(n); } else if ((bytes[1] != 0xff || bytes[2] != 0xff) && readLength >= 3) { - auto n = *reinterpret_cast(bytes.data() + 1); // NOLINT + auto n = *reinterpret_cast(bytes.data() + 1); if (!this->reader.skip(3)) return false; *slot = qFromLittleEndian(n); } else if (readLength == 7) { - auto n = *reinterpret_cast(bytes.data() + 3); // NOLINT + auto n = *reinterpret_cast(bytes.data() + 3); if (!this->reader.skip(7)) return false; *slot = qFromLittleEndian(n); } else return false; @@ -960,7 +877,7 @@ bool LogReader::continueReading() { } void LogFollower::FcntlWaitThread::run() { - struct flock lock = { + auto lock = flock { .l_type = F_RDLCK, // won't block other read locks when we take it .l_whence = SEEK_SET, .l_start = 0, diff --git a/src/core/logging.hpp b/src/core/logging.hpp index 7b6a758..bf81133 100644 --- a/src/core/logging.hpp +++ b/src/core/logging.hpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include @@ -136,7 +135,6 @@ private: QHash allFilters; QTextStream stdoutStream; - QMutex stdoutMutex; LoggingThreadProxy threadProxy; friend void initLogCategoryLevel(const char* name, QtMsgType defaultLevel); diff --git a/src/core/model.cpp b/src/core/model.cpp index 47ef060..165c606 100644 --- a/src/core/model.cpp +++ b/src/core/model.cpp @@ -1,14 +1,81 @@ #include "model.hpp" -#include +#include #include #include +#include +#include +#include +#include +#include + +qint32 UntypedObjectModel::rowCount(const QModelIndex& parent) const { + if (parent != QModelIndex()) return 0; + return static_cast(this->valuesList.length()); +} + +QVariant UntypedObjectModel::data(const QModelIndex& index, qint32 role) const { + if (role != Qt::UserRole) return QVariant(); + return QVariant::fromValue(this->valuesList.at(index.row())); +} QHash UntypedObjectModel::roleNames() const { return {{Qt::UserRole, "modelData"}}; } +void UntypedObjectModel::insertObject(QObject* object, qsizetype index) { + auto iindex = index == -1 ? this->valuesList.length() : index; + emit this->objectInsertedPre(object, iindex); + + auto intIndex = static_cast(iindex); + this->beginInsertRows(QModelIndex(), intIndex, intIndex); + this->valuesList.insert(iindex, object); + this->endInsertRows(); + + emit this->valuesChanged(); + emit this->objectInsertedPost(object, iindex); +} + +void UntypedObjectModel::removeAt(qsizetype index) { + auto* object = this->valuesList.at(index); + emit this->objectRemovedPre(object, index); + + auto intIndex = static_cast(index); + this->beginRemoveRows(QModelIndex(), intIndex, intIndex); + this->valuesList.removeAt(index); + this->endRemoveRows(); + + emit this->valuesChanged(); + emit this->objectRemovedPost(object, index); +} + +bool UntypedObjectModel::removeObject(const QObject* object) { + auto index = this->valuesList.indexOf(object); + if (index == -1) return false; + + this->removeAt(index); + return true; +} + +void UntypedObjectModel::diffUpdate(const QVector& newValues) { + for (qsizetype i = 0; i < this->valuesList.length();) { + if (newValues.contains(this->valuesList.at(i))) i++; + else this->removeAt(i); + } + + qsizetype oi = 0; + for (auto* object: newValues) { + if (this->valuesList.length() == oi || this->valuesList.at(oi) != object) { + this->insertObject(object, oi); + } + + oi++; + } +} + +qsizetype UntypedObjectModel::indexOf(QObject* object) { return this->valuesList.indexOf(object); } + UntypedObjectModel* UntypedObjectModel::emptyInstance() { - static auto* instance = new ObjectModel(nullptr); + static auto* instance = new UntypedObjectModel(nullptr); // NOLINT return instance; } diff --git a/src/core/model.hpp b/src/core/model.hpp index 0e88025..3c5822a 100644 --- a/src/core/model.hpp +++ b/src/core/model.hpp @@ -2,7 +2,7 @@ #include -#include +#include #include #include #include @@ -49,11 +49,14 @@ class UntypedObjectModel: public QAbstractListModel { public: explicit UntypedObjectModel(QObject* parent): QAbstractListModel(parent) {} + [[nodiscard]] qint32 rowCount(const QModelIndex& parent) const override; + [[nodiscard]] QVariant data(const QModelIndex& index, qint32 role) const override; [[nodiscard]] QHash roleNames() const override; - [[nodiscard]] virtual QList values() = 0; + [[nodiscard]] QList values() const { return this->valuesList; } + void removeAt(qsizetype index); - Q_INVOKABLE virtual qsizetype indexOf(QObject* object) const = 0; + Q_INVOKABLE qsizetype indexOf(QObject* object); static UntypedObjectModel* emptyInstance(); @@ -68,6 +71,15 @@ signals: /// Sent immediately after an object is removed from the list. void objectRemovedPost(QObject* object, qsizetype index); +protected: + void insertObject(QObject* object, qsizetype index = -1); + bool removeObject(const QObject* object); + + // Assumes only one instance of a specific value + void diffUpdate(const QVector& newValues); + + QVector valuesList; + private: static qsizetype valuesCount(QQmlListProperty* property); static QObject* valueAt(QQmlListProperty* property, qsizetype index); @@ -78,20 +90,14 @@ class ObjectModel: public UntypedObjectModel { public: explicit ObjectModel(QObject* parent): UntypedObjectModel(parent) {} - [[nodiscard]] const QList& valueList() const { return this->mValuesList; } - [[nodiscard]] QList& valueList() { return this->mValuesList; } + [[nodiscard]] QVector& valueList() { return *std::bit_cast*>(&this->valuesList); } + + [[nodiscard]] const QVector& valueList() const { + return *std::bit_cast*>(&this->valuesList); + } void insertObject(T* object, qsizetype index = -1) { - auto iindex = index == -1 ? this->mValuesList.length() : index; - emit this->objectInsertedPre(object, iindex); - - auto intIndex = static_cast(iindex); - this->beginInsertRows(QModelIndex(), intIndex, intIndex); - this->mValuesList.insert(iindex, object); - this->endInsertRows(); - - emit this->valuesChanged(); - emit this->objectInsertedPost(object, iindex); + this->UntypedObjectModel::insertObject(object, index); } void insertObjectSorted(T* object, const std::function& compare) { @@ -104,71 +110,17 @@ public: } auto idx = iter - list.begin(); - this->insertObject(object, idx); + this->UntypedObjectModel::insertObject(object, idx); } - bool removeObject(const T* object) { - auto index = this->mValuesList.indexOf(object); - if (index == -1) return false; - - this->removeAt(index); - return true; - } - - void removeAt(qsizetype index) { - auto* object = this->mValuesList.at(index); - emit this->objectRemovedPre(object, index); - - auto intIndex = static_cast(index); - this->beginRemoveRows(QModelIndex(), intIndex, intIndex); - this->mValuesList.removeAt(index); - this->endRemoveRows(); - - emit this->valuesChanged(); - emit this->objectRemovedPost(object, index); - } + void removeObject(const T* object) { this->UntypedObjectModel::removeObject(object); } // Assumes only one instance of a specific value - void diffUpdate(const QList& newValues) { - for (qsizetype i = 0; i < this->mValuesList.length();) { - if (newValues.contains(this->mValuesList.at(i))) i++; - else this->removeAt(i); - } - - qsizetype oi = 0; - for (auto* object: newValues) { - if (this->mValuesList.length() == oi || this->mValuesList.at(oi) != object) { - this->insertObject(object, oi); - } - - oi++; - } + void diffUpdate(const QVector& newValues) { + this->UntypedObjectModel::diffUpdate(*std::bit_cast*>(&newValues)); } static ObjectModel* emptyInstance() { return static_cast*>(UntypedObjectModel::emptyInstance()); } - - [[nodiscard]] qint32 rowCount(const QModelIndex& parent) const override { - if (parent != QModelIndex()) return 0; - return static_cast(this->mValuesList.length()); - } - - [[nodiscard]] QVariant data(const QModelIndex& index, qint32 role) const override { - if (role != Qt::UserRole) return QVariant(); - // Values must be QObject derived, but we can't assert that here without breaking forward decls, - // so no static_cast. - return QVariant::fromValue(reinterpret_cast(this->mValuesList.at(index.row()))); - } - - qsizetype indexOf(QObject* object) const override { - return this->mValuesList.indexOf(reinterpret_cast(object)); - } - - [[nodiscard]] QList values() override { - return *reinterpret_cast*>(&this->mValuesList); - } - -private: - QList mValuesList; }; diff --git a/src/core/module.md b/src/core/module.md index 41f065d..b9404ea 100644 --- a/src/core/module.md +++ b/src/core/module.md @@ -21,6 +21,7 @@ headers = [ "model.hpp", "elapsedtimer.hpp", "desktopentry.hpp", + "objectrepeater.hpp", "qsmenu.hpp", "retainable.hpp", "popupanchor.hpp", diff --git a/src/core/objectrepeater.cpp b/src/core/objectrepeater.cpp new file mode 100644 index 0000000..7971952 --- /dev/null +++ b/src/core/objectrepeater.cpp @@ -0,0 +1,190 @@ +#include "objectrepeater.hpp" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QVariant ObjectRepeater::model() const { return this->mModel; } + +void ObjectRepeater::setModel(QVariant model) { + if (model == this->mModel) return; + + if (this->itemModel != nullptr) { + QObject::disconnect(this->itemModel, nullptr, this, nullptr); + } + + this->mModel = std::move(model); + emit this->modelChanged(); + this->reloadElements(); +} + +void ObjectRepeater::onModelDestroyed() { + this->mModel.clear(); + this->itemModel = nullptr; + emit this->modelChanged(); + this->reloadElements(); +} + +QQmlComponent* ObjectRepeater::delegate() const { return this->mDelegate; } + +void ObjectRepeater::setDelegate(QQmlComponent* delegate) { + if (delegate == this->mDelegate) return; + + if (this->mDelegate != nullptr) { + QObject::disconnect(this->mDelegate, nullptr, this, nullptr); + } + + this->mDelegate = delegate; + + if (delegate != nullptr) { + QObject::connect( + this->mDelegate, + &QObject::destroyed, + this, + &ObjectRepeater::onDelegateDestroyed + ); + } + + emit this->delegateChanged(); + this->reloadElements(); +} + +void ObjectRepeater::onDelegateDestroyed() { + this->mDelegate = nullptr; + emit this->delegateChanged(); + this->reloadElements(); +} + +void ObjectRepeater::reloadElements() { + for (auto i = this->valuesList.length() - 1; i >= 0; i--) { + this->removeComponent(i); + } + + if (this->mDelegate == nullptr || !this->mModel.isValid()) return; + + if (this->mModel.canConvert()) { + auto* model = this->mModel.value(); + this->itemModel = model; + + this->insertModelElements(model, 0, model->rowCount() - 1); // -1 is fine + + // clang-format off + QObject::connect(model, &QObject::destroyed, this, &ObjectRepeater::onModelDestroyed); + QObject::connect(model, &QAbstractItemModel::rowsInserted, this, &ObjectRepeater::onModelRowsInserted); + QObject::connect(model, &QAbstractItemModel::rowsRemoved, this, &ObjectRepeater::onModelRowsRemoved); + QObject::connect(model, &QAbstractItemModel::rowsMoved, this, &ObjectRepeater::onModelRowsMoved); + QObject::connect(model, &QAbstractItemModel::modelAboutToBeReset, this, &ObjectRepeater::onModelAboutToBeReset); + // clang-format on + } else if (this->mModel.canConvert()) { + auto values = this->mModel.value(); + auto len = values.count(); + + for (auto i = 0; i != len; i++) { + this->insertComponent(i, {{"modelData", QVariant::fromValue(values.at(i))}}); + } + } else if (this->mModel.canConvert>()) { + auto values = this->mModel.value>(); + + for (auto& value: values) { + this->insertComponent(this->valuesList.length(), {{"modelData", value}}); + } + } else { + qCritical() << this + << "Cannot create components as the model is not compatible:" << this->mModel; + } +} + +void ObjectRepeater::insertModelElements(QAbstractItemModel* model, int first, int last) { + auto roles = model->roleNames(); + auto roleDataVec = QVector(); + for (auto id: roles.keys()) { + roleDataVec.push_back(QModelRoleData(id)); + } + + auto values = QModelRoleDataSpan(roleDataVec); + auto props = QVariantMap(); + + for (auto i = first; i != last + 1; i++) { + auto index = model->index(i, 0); + model->multiData(index, values); + + for (auto [id, name]: roles.asKeyValueRange()) { + props.insert(name, *values.dataForRole(id)); + } + + this->insertComponent(i, props); + + props.clear(); + } +} + +void ObjectRepeater::onModelRowsInserted(const QModelIndex& parent, int first, int last) { + if (parent != QModelIndex()) return; + + this->insertModelElements(this->itemModel, first, last); +} + +void ObjectRepeater::onModelRowsRemoved(const QModelIndex& parent, int first, int last) { + if (parent != QModelIndex()) return; + + for (auto i = last; i != first - 1; i--) { + this->removeComponent(i); + } +} + +void ObjectRepeater::onModelRowsMoved( + const QModelIndex& sourceParent, + int sourceStart, + int sourceEnd, + const QModelIndex& destParent, + int destStart +) { + auto hasSource = sourceParent != QModelIndex(); + auto hasDest = destParent != QModelIndex(); + + if (!hasSource && !hasDest) return; + + if (hasSource) { + this->onModelRowsRemoved(sourceParent, sourceStart, sourceEnd); + } + + if (hasDest) { + this->onModelRowsInserted(destParent, destStart, destStart + (sourceEnd - sourceStart)); + } +} + +void ObjectRepeater::onModelAboutToBeReset() { + auto last = static_cast(this->valuesList.length() - 1); + this->onModelRowsRemoved(QModelIndex(), 0, last); // -1 is fine +} + +void ObjectRepeater::insertComponent(qsizetype index, const QVariantMap& properties) { + auto* context = QQmlEngine::contextForObject(this); + auto* instance = this->mDelegate->createWithInitialProperties(properties, context); + + if (instance == nullptr) { + qWarning().noquote() << this->mDelegate->errorString(); + qWarning() << this << "failed to create object for model data" << properties; + } else { + QQmlEngine::setObjectOwnership(instance, QQmlEngine::CppOwnership); + instance->setParent(this); + } + + this->insertObject(instance, index); +} + +void ObjectRepeater::removeComponent(qsizetype index) { + auto* instance = this->valuesList.at(index); + this->removeAt(index); + delete instance; +} diff --git a/src/core/objectrepeater.hpp b/src/core/objectrepeater.hpp new file mode 100644 index 0000000..409b12d --- /dev/null +++ b/src/core/objectrepeater.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "model.hpp" + +///! A Repeater / for loop / map for non Item derived objects. +/// > [!ERROR] Removed in favor of @@QtQml.Models.Instantiator +/// +/// The ObjectRepeater creates instances of the provided delegate for every entry in the +/// given model, similarly to a @@QtQuick.Repeater but for non visual types. +class ObjectRepeater: public ObjectModel { + Q_OBJECT; + /// The model providing data to the ObjectRepeater. + /// + /// Currently accepted model types are `list` lists, javascript arrays, + /// and [QAbstractListModel] derived models, though only one column will be repeated + /// from the latter. + /// + /// Note: @@ObjectModel is a [QAbstractListModel] with a single column. + /// + /// [QAbstractListModel]: https://doc.qt.io/qt-6/qabstractlistmodel.html + Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged); + /// The delegate component to repeat. + /// + /// The delegate is given the same properties as in a Repeater, except `index` which + /// is not currently implemented. + /// + /// If the model is a `list` or javascript array, a `modelData` property will be + /// exposed containing the entry from the model. If the model is a [QAbstractListModel], + /// the roles from the model will be exposed. + /// + /// Note: @@ObjectModel has a single role named `modelData` for compatibility with normal lists. + /// + /// [QAbstractListModel]: https://doc.qt.io/qt-6/qabstractlistmodel.html + Q_PROPERTY(QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY delegateChanged); + Q_CLASSINFO("DefaultProperty", "delegate"); + QML_ELEMENT; + QML_UNCREATABLE("ObjectRepeater has been removed in favor of QtQml.Models.Instantiator."); + +public: + explicit ObjectRepeater(QObject* parent = nullptr): ObjectModel(parent) {} + + [[nodiscard]] QVariant model() const; + void setModel(QVariant model); + + [[nodiscard]] QQmlComponent* delegate() const; + void setDelegate(QQmlComponent* delegate); + +signals: + void modelChanged(); + void delegateChanged(); + +private slots: + void onDelegateDestroyed(); + void onModelDestroyed(); + void onModelRowsInserted(const QModelIndex& parent, int first, int last); + void onModelRowsRemoved(const QModelIndex& parent, int first, int last); + + void onModelRowsMoved( + const QModelIndex& sourceParent, + int sourceStart, + int sourceEnd, + const QModelIndex& destParent, + int destStart + ); + + void onModelAboutToBeReset(); + +private: + void reloadElements(); + void insertModelElements(QAbstractItemModel* model, int first, int last); + void insertComponent(qsizetype index, const QVariantMap& properties); + void removeComponent(qsizetype index); + + QVariant mModel; + QAbstractItemModel* itemModel = nullptr; + QQmlComponent* mDelegate = nullptr; +}; diff --git a/src/core/paths.cpp b/src/core/paths.cpp index d361e3d..e17c3bc 100644 --- a/src/core/paths.cpp +++ b/src/core/paths.cpp @@ -27,19 +27,12 @@ QsPaths* QsPaths::instance() { return instance; } -void QsPaths::init( - QString shellId, - QString pathId, - QString dataOverride, - QString stateOverride, - QString cacheOverride -) { +void QsPaths::init(QString shellId, QString pathId, QString dataOverride, QString stateOverride) { auto* instance = QsPaths::instance(); instance->shellId = std::move(shellId); instance->pathId = std::move(pathId); instance->shellDataOverride = std::move(dataOverride); instance->shellStateOverride = std::move(stateOverride); - instance->shellCacheOverride = std::move(cacheOverride); } QDir QsPaths::crashDir(const QString& id) { @@ -64,7 +57,7 @@ QDir* QsPaths::baseRunDir() { if (this->baseRunState == DirState::Unknown) { auto runtimeDir = qEnvironmentVariable("XDG_RUNTIME_DIR"); if (runtimeDir.isEmpty()) { - runtimeDir = QString("/run/user/%1").arg(getuid()); + runtimeDir = QString("/run/user/$1").arg(getuid()); qCInfo(logPaths) << "XDG_RUNTIME_DIR was not set, defaulting to" << runtimeDir; } @@ -175,8 +168,7 @@ void QsPaths::linkRunDir() { auto* shellDir = this->shellRunDir(); if (!shellDir) { - qCCritical( - logPaths + qCCritical(logPaths ) << "Could not create by-id symlink as the shell runtime path could not be created."; } else { auto shellPath = shellDir->filePath(runDir->dirName()); @@ -324,16 +316,9 @@ QDir QsPaths::shellStateDir() { QDir QsPaths::shellCacheDir() { if (this->shellCacheState == DirState::Unknown) { - QDir dir; - if (this->shellCacheOverride.isEmpty()) { - dir = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); - dir = QDir(dir.filePath("by-shell")); - dir = QDir(dir.filePath(this->shellId)); - } else { - auto basedir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation); - dir = QDir(this->shellCacheOverride.replace("$BASE", basedir)); - } - + auto dir = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + dir = QDir(dir.filePath("by-shell")); + dir = QDir(dir.filePath(this->shellId)); this->mShellCacheDir = dir; qCDebug(logPaths) << "Initialized cache path:" << dir.path(); @@ -361,7 +346,7 @@ void QsPaths::createLock() { return; } - struct flock lock = { + auto lock = flock { .l_type = F_WRLCK, .l_whence = SEEK_SET, .l_start = 0, @@ -379,8 +364,7 @@ void QsPaths::createLock() { qCDebug(logPaths) << "Created instance lock at" << path; } } else { - qCCritical( - logPaths + qCCritical(logPaths ) << "Could not create instance lock, as the instance runtime directory could not be created."; } } @@ -389,7 +373,7 @@ bool QsPaths::checkLock(const QString& path, InstanceLockInfo* info, bool allowD auto file = QFile(QDir(path).filePath("instance.lock")); if (!file.open(QFile::ReadOnly)) return false; - struct flock lock = { + auto lock = flock { .l_type = F_WRLCK, .l_whence = SEEK_SET, .l_start = 0, @@ -413,7 +397,7 @@ bool QsPaths::checkLock(const QString& path, InstanceLockInfo* info, bool allowD } QPair, QVector> -QsPaths::collectInstances(const QString& path, const QString& display) { +QsPaths::collectInstances(const QString& path) { qCDebug(logPaths) << "Collecting instances from" << path; auto liveInstances = QVector(); auto deadInstances = QVector(); @@ -427,11 +411,6 @@ QsPaths::collectInstances(const QString& path, const QString& display) { qCDebug(logPaths).nospace() << "Found instance " << info.instance.instanceId << " (pid " << info.pid << ") at " << path; - if (!display.isEmpty() && info.instance.display != display) { - qCDebug(logPaths) << "Skipped instance with mismatched display at" << path; - continue; - } - if (info.pid == -1) { deadInstances.push_back(info); } else { diff --git a/src/core/paths.hpp b/src/core/paths.hpp index c2500ed..178bcda 100644 --- a/src/core/paths.hpp +++ b/src/core/paths.hpp @@ -17,20 +17,14 @@ QDataStream& operator>>(QDataStream& stream, InstanceLockInfo& info); class QsPaths { public: static QsPaths* instance(); - static void init( - QString shellId, - QString pathId, - QString dataOverride, - QString stateOverride, - QString cacheOverride - ); + static void init(QString shellId, QString pathId, QString dataOverride, QString stateOverride); static QDir crashDir(const QString& id); static QString basePath(const QString& id); static QString ipcPath(const QString& id); static bool checkLock(const QString& path, InstanceLockInfo* info = nullptr, bool allowDead = false); static QPair, QVector> - collectInstances(const QString& path, const QString& display); + collectInstances(const QString& path); QDir* baseRunDir(); QDir* shellRunDir(); @@ -71,5 +65,4 @@ private: QString shellDataOverride; QString shellStateOverride; - QString shellCacheOverride; }; diff --git a/src/core/platformmenu.cpp b/src/core/platformmenu.cpp index d8901e2..427dde0 100644 --- a/src/core/platformmenu.cpp +++ b/src/core/platformmenu.cpp @@ -18,6 +18,7 @@ #include #include "../window/proxywindow.hpp" +#include "../window/windowinterface.hpp" #include "iconprovider.hpp" #include "model.hpp" #include "platformmenu_p.hpp" @@ -90,8 +91,10 @@ bool PlatformMenuEntry::display(QObject* parentWindow, int relativeX, int relati } else if (parentWindow == nullptr) { qCritical() << "Cannot display PlatformMenuEntry with null parent window."; return false; - } else if (auto* proxy = ProxyWindowBase::forObject(parentWindow)) { + } else if (auto* proxy = qobject_cast(parentWindow)) { window = proxy->backingWindow(); + } else if (auto* interface = qobject_cast(parentWindow)) { + window = interface->proxyWindow()->backingWindow(); } else { qCritical() << "PlatformMenuEntry.display() must be called with a window."; return false; diff --git a/src/core/plugin.cpp b/src/core/plugin.cpp index e6cd1bb..0eb9a06 100644 --- a/src/core/plugin.cpp +++ b/src/core/plugin.cpp @@ -9,18 +9,6 @@ static QVector plugins; // NOLINT void QsEnginePlugin::registerPlugin(QsEnginePlugin& plugin) { plugins.push_back(&plugin); } -void QsEnginePlugin::preinitPluginsOnly() { - plugins.removeIf([](QsEnginePlugin* plugin) { return !plugin->applies(); }); - - std::ranges::sort(plugins, [](QsEnginePlugin* a, QsEnginePlugin* b) { - return b->dependencies().contains(a->name()); - }); - - for (QsEnginePlugin* plugin: plugins) { - plugin->preinit(); - } -} - void QsEnginePlugin::initPlugins() { plugins.removeIf([](QsEnginePlugin* plugin) { return !plugin->applies(); }); @@ -28,10 +16,6 @@ void QsEnginePlugin::initPlugins() { return b->dependencies().contains(a->name()); }); - for (QsEnginePlugin* plugin: plugins) { - plugin->preinit(); - } - for (QsEnginePlugin* plugin: plugins) { plugin->init(); } diff --git a/src/core/plugin.hpp b/src/core/plugin.hpp index f692e91..f0c14dc 100644 --- a/src/core/plugin.hpp +++ b/src/core/plugin.hpp @@ -18,14 +18,12 @@ public: virtual QString name() { return QString(); } virtual QList dependencies() { return {}; } virtual bool applies() { return true; } - virtual void preinit() {} virtual void init() {} virtual void registerTypes() {} virtual void constructGeneration(EngineGeneration& /*unused*/) {} // NOLINT virtual void onReload() {} static void registerPlugin(QsEnginePlugin& plugin); - static void preinitPluginsOnly(); static void initPlugins(); static void runConstructGeneration(EngineGeneration& generation); static void runOnReload(); diff --git a/src/core/popupanchor.cpp b/src/core/popupanchor.cpp index ca817c9..bbcc3a5 100644 --- a/src/core/popupanchor.cpp +++ b/src/core/popupanchor.cpp @@ -11,6 +11,7 @@ #include #include "../window/proxywindow.hpp" +#include "../window/windowinterface.hpp" #include "types.hpp" bool PopupAnchorState::operator==(const PopupAnchorState& other) const { @@ -27,7 +28,7 @@ void PopupAnchor::markClean() { this->lastState = this->state; } void PopupAnchor::markDirty() { this->lastState.reset(); } QWindow* PopupAnchor::backingWindow() const { - return this->bProxyWindow ? this->bProxyWindow->backingWindow() : nullptr; + return this->mProxyWindow ? this->mProxyWindow->backingWindow() : nullptr; } void PopupAnchor::setWindowInternal(QObject* window) { @@ -35,12 +36,14 @@ void PopupAnchor::setWindowInternal(QObject* window) { if (this->mWindow) { QObject::disconnect(this->mWindow, nullptr, this, nullptr); - QObject::disconnect(this->bProxyWindow, nullptr, this, nullptr); + QObject::disconnect(this->mProxyWindow, nullptr, this, nullptr); } if (window) { - if (auto* proxy = ProxyWindowBase::forObject(window)) { - this->bProxyWindow = proxy; + if (auto* proxy = qobject_cast(window)) { + this->mProxyWindow = proxy; + } else if (auto* interface = qobject_cast(window)) { + this->mProxyWindow = interface->proxyWindow(); } else { qWarning() << "Tried to set popup anchor window to" << window << "which is not a quickshell window."; @@ -52,7 +55,7 @@ void PopupAnchor::setWindowInternal(QObject* window) { QObject::connect(this->mWindow, &QObject::destroyed, this, &PopupAnchor::onWindowDestroyed); QObject::connect( - this->bProxyWindow, + this->mProxyWindow, &ProxyWindowBase::backerVisibilityChanged, this, &PopupAnchor::backingWindowVisibilityChanged @@ -67,7 +70,7 @@ void PopupAnchor::setWindowInternal(QObject* window) { setnull: if (this->mWindow) { this->mWindow = nullptr; - this->bProxyWindow = nullptr; + this->mProxyWindow = nullptr; emit this->windowChanged(); emit this->backingWindowVisibilityChanged(); @@ -97,7 +100,7 @@ void PopupAnchor::setItem(QQuickItem* item) { void PopupAnchor::onWindowDestroyed() { this->mWindow = nullptr; - this->bProxyWindow = nullptr; + this->mProxyWindow = nullptr; emit this->windowChanged(); emit this->backingWindowVisibilityChanged(); } @@ -183,11 +186,11 @@ void PopupAnchor::updatePlacement(const QPoint& anchorpoint, const QSize& size) } void PopupAnchor::updateAnchor() { - if (this->mItem && this->bProxyWindow) { + if (this->mItem && this->mProxyWindow) { auto baseRect = this->mUserRect.isEmpty() ? this->mItem->boundingRect() : this->mUserRect.qrect(); - auto rect = this->bProxyWindow->contentItem()->mapFromItem( + auto rect = this->mProxyWindow->contentItem()->mapFromItem( this->mItem, baseRect.marginsRemoved(this->mMargins.qmargins()) ); diff --git a/src/core/popupanchor.hpp b/src/core/popupanchor.hpp index 9f08512..a9b121e 100644 --- a/src/core/popupanchor.hpp +++ b/src/core/popupanchor.hpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -140,9 +139,7 @@ public: void markDirty(); [[nodiscard]] QObject* window() const { return this->mWindow; } - [[nodiscard]] QBindable bindableProxyWindow() const { - return &this->bProxyWindow; - } + [[nodiscard]] ProxyWindowBase* proxyWindow() const { return this->mProxyWindow; } [[nodiscard]] QWindow* backingWindow() const; void setWindowInternal(QObject* window); void setWindow(QObject* window); @@ -196,12 +193,11 @@ private slots: private: QObject* mWindow = nullptr; QQuickItem* mItem = nullptr; + ProxyWindowBase* mProxyWindow = nullptr; PopupAnchorState state; Box mUserRect; Margins mMargins; std::optional lastState; - - Q_OBJECT_BINDABLE_PROPERTY(PopupAnchor, ProxyWindowBase*, bProxyWindow); }; class PopupPositioner { diff --git a/src/core/qmlglobal.cpp b/src/core/qmlglobal.cpp index 6cac3aa..07238f6 100644 --- a/src/core/qmlglobal.cpp +++ b/src/core/qmlglobal.cpp @@ -26,11 +26,9 @@ #include "../io/processcore.hpp" #include "generation.hpp" #include "iconimageprovider.hpp" -#include "instanceinfo.hpp" #include "paths.hpp" #include "qmlscreen.hpp" #include "rootwrapper.hpp" -#include "scanenv.hpp" QuickshellSettings::QuickshellSettings() { QObject::connect( @@ -61,9 +59,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; @@ -154,22 +150,6 @@ qint32 QuickshellGlobal::processId() const { // NOLINT return getpid(); } -QString QuickshellGlobal::instanceId() const { // NOLINT - return InstanceInfo::CURRENT.instanceId; -} - -QString QuickshellGlobal::shellId() const { // NOLINT - return InstanceInfo::CURRENT.shellId; -} - -QString QuickshellGlobal::appId() const { // NOLINT - return InstanceInfo::CURRENT.appId; -} - -QDateTime QuickshellGlobal::launchTime() const { // NOLINT - return InstanceInfo::CURRENT.launchTime; -} - qsizetype QuickshellGlobal::screensCount(QQmlListProperty* /*unused*/) { return QuickshellTracked::instance()->screens.size(); } @@ -333,16 +313,6 @@ QString QuickshellGlobal::iconPath(const QString& icon, const QString& fallback) return IconImageProvider::requestString(icon, "", fallback); } -bool QuickshellGlobal::hasThemeIcon(const QString& icon) { return QIcon::hasThemeIcon(icon); } - -bool QuickshellGlobal::hasVersion(qint32 major, qint32 minor, const QStringList& features) { - return qs::scan::env::PreprocEnv::hasVersion(major, minor, features); -} - -bool QuickshellGlobal::hasVersion(qint32 major, qint32 minor) { - return QuickshellGlobal::hasVersion(major, minor, QStringList()); -} - QuickshellGlobal* QuickshellGlobal::create(QQmlEngine* engine, QJSEngine* /*unused*/) { auto* qsg = new QuickshellGlobal(); auto* generation = EngineGeneration::findEngineGeneration(engine); diff --git a/src/core/qmlglobal.hpp b/src/core/qmlglobal.hpp index 72055df..9d88591 100644 --- a/src/core/qmlglobal.hpp +++ b/src/core/qmlglobal.hpp @@ -17,7 +17,6 @@ #include "../io/processcore.hpp" #include "doc.hpp" -#include "instanceinfo.hpp" #include "qmlscreen.hpp" ///! Accessor for some options under the Quickshell type. @@ -84,21 +83,6 @@ class QuickshellGlobal: public QObject { // clang-format off /// Quickshell's process id. Q_PROPERTY(qint32 processId READ processId CONSTANT); - /// A unique identifier for this Quickshell instance - Q_PROPERTY(QString instanceId READ instanceId CONSTANT) - /// The shell ID, used to differentiate between different shell configurations. - /// - /// Defaults to a stable value derived from the config path. - /// Can be overridden with `//@ pragma ShellId ` in the root qml file. - Q_PROPERTY(QString shellId READ shellId CONSTANT) - /// The desktop application ID. - /// - /// Defaults to `org.quickshell`. - /// Can be overridden with `//@ pragma AppId ` in the root qml file - /// or the `QS_APP_ID` environment variable. - Q_PROPERTY(QString appId READ appId CONSTANT) - /// The time at which this Quickshell instance was launched. - Q_PROPERTY(QDateTime launchTime READ launchTime CONSTANT) /// All currently connected screens. /// /// This property updates as connected screens change. @@ -143,21 +127,18 @@ class QuickshellGlobal: public QObject { /// Usually `~/.local/share/quickshell/by-shell/` /// /// Can be overridden using `//@ pragma DataDir $BASE/path` in the root qml file, where `$BASE` - /// corresponds to `$XDG_DATA_HOME` (usually `~/.local/share`). + /// corrosponds to `$XDG_DATA_HOME` (usually `~/.local/share`). Q_PROPERTY(QString dataDir READ dataDir CONSTANT); /// The per-shell state directory. /// /// Usually `~/.local/state/quickshell/by-shell/` /// /// Can be overridden using `//@ pragma StateDir $BASE/path` in the root qml file, where `$BASE` - /// corresponds to `$XDG_STATE_HOME` (usually `~/.local/state`). + /// corrosponds to `$XDG_STATE_HOME` (usually `~/.local/state`). Q_PROPERTY(QString stateDir READ stateDir CONSTANT); /// The per-shell cache directory. /// /// Usually `~/.cache/quickshell/by-shell/` - /// - /// Can be overridden using `//@ pragma CacheDir $BASE/path` in the root qml file, where `$BASE` - /// corresponds to `$XDG_CACHE_HOME` (usually `~/.cache`). Q_PROPERTY(QString cacheDir READ cacheDir CONSTANT); // clang-format on QML_SINGLETON; @@ -165,10 +146,6 @@ class QuickshellGlobal: public QObject { public: [[nodiscard]] qint32 processId() const; - [[nodiscard]] QString instanceId() const; - [[nodiscard]] QString shellId() const; - [[nodiscard]] QString appId() const; - [[nodiscard]] QDateTime launchTime() const; QQmlListProperty screens(); @@ -222,8 +199,6 @@ public: /// Setting the `fallback` parameter of `iconPath` will attempt to load the fallback /// icon if the requested one could not be loaded. Q_INVOKABLE static QString iconPath(const QString& icon, const QString& fallback); - /// Check if specified icon has an available icon in your icon theme - Q_INVOKABLE static bool hasThemeIcon(const QString& icon); /// Equivalent to `${Quickshell.configDir}/${path}` Q_INVOKABLE [[nodiscard]] QString shellPath(const QString& path) const; /// > [!WARNING] Deprecated: Renamed to @@shellPath() for clarity. @@ -239,21 +214,6 @@ public: /// /// The popup can also be blocked by setting `QS_NO_RELOAD_POPUP=1`. Q_INVOKABLE void inhibitReloadPopup() { this->mInhibitReloadPopup = true; } - /// Check if Quickshell's version is at least `major.minor` and the listed - /// unreleased features are available. If Quickshell is newer than the given version - /// it is assumed that all unreleased features are present. The unreleased feature list - /// may be omitted. - /// - /// > [!NOTE] You can feature gate code blocks using Quickshell's preprocessor which - /// > has the same function available. - /// > - /// > ```qml - /// > //@ if hasVersion(0, 3, ["feature"]) - /// > ... - /// > //@ endif - /// > ``` - Q_INVOKABLE static bool hasVersion(qint32 major, qint32 minor, const QStringList& features); - Q_INVOKABLE static bool hasVersion(qint32 major, qint32 minor); void clearReloadPopupInhibit() { this->mInhibitReloadPopup = false; } [[nodiscard]] bool isReloadPopupInhibited() const { return this->mInhibitReloadPopup; } diff --git a/src/core/region.cpp b/src/core/region.cpp index 82cc2e7..11892d6 100644 --- a/src/core/region.cpp +++ b/src/core/region.cpp @@ -1,5 +1,4 @@ #include "region.hpp" -#include #include #include @@ -19,11 +18,6 @@ PendingRegion::PendingRegion(QObject* parent): QObject(parent) { QObject::connect(this, &PendingRegion::yChanged, this, &PendingRegion::changed); QObject::connect(this, &PendingRegion::widthChanged, this, &PendingRegion::changed); QObject::connect(this, &PendingRegion::heightChanged, this, &PendingRegion::changed); - QObject::connect(this, &PendingRegion::radiusChanged, this, &PendingRegion::changed); - QObject::connect(this, &PendingRegion::topLeftRadiusChanged, this, &PendingRegion::changed); - QObject::connect(this, &PendingRegion::topRightRadiusChanged, this, &PendingRegion::changed); - QObject::connect(this, &PendingRegion::bottomLeftRadiusChanged, this, &PendingRegion::changed); - QObject::connect(this, &PendingRegion::bottomRightRadiusChanged, this, &PendingRegion::changed); QObject::connect(this, &PendingRegion::childrenChanged, this, &PendingRegion::changed); } @@ -51,79 +45,6 @@ void PendingRegion::onItemDestroyed() { this->mItem = nullptr; } void PendingRegion::onChildDestroyed() { this->mRegions.removeAll(this->sender()); } -qint32 PendingRegion::radius() const { return this->mRadius; } - -void PendingRegion::setRadius(qint32 radius) { - if (radius == this->mRadius) return; - this->mRadius = radius; - emit this->radiusChanged(); - - if (!(this->mCornerOverrides & TopLeft)) emit this->topLeftRadiusChanged(); - if (!(this->mCornerOverrides & TopRight)) emit this->topRightRadiusChanged(); - if (!(this->mCornerOverrides & BottomLeft)) emit this->bottomLeftRadiusChanged(); - if (!(this->mCornerOverrides & BottomRight)) emit this->bottomRightRadiusChanged(); -} - -qint32 PendingRegion::topLeftRadius() const { - return (this->mCornerOverrides & TopLeft) ? this->mTopLeftRadius : this->mRadius; -} - -void PendingRegion::setTopLeftRadius(qint32 radius) { - this->mTopLeftRadius = radius; - this->mCornerOverrides |= TopLeft; - emit this->topLeftRadiusChanged(); -} - -void PendingRegion::resetTopLeftRadius() { - this->mCornerOverrides &= ~TopLeft; - emit this->topLeftRadiusChanged(); -} - -qint32 PendingRegion::topRightRadius() const { - return (this->mCornerOverrides & TopRight) ? this->mTopRightRadius : this->mRadius; -} - -void PendingRegion::setTopRightRadius(qint32 radius) { - this->mTopRightRadius = radius; - this->mCornerOverrides |= TopRight; - emit this->topRightRadiusChanged(); -} - -void PendingRegion::resetTopRightRadius() { - this->mCornerOverrides &= ~TopRight; - emit this->topRightRadiusChanged(); -} - -qint32 PendingRegion::bottomLeftRadius() const { - return (this->mCornerOverrides & BottomLeft) ? this->mBottomLeftRadius : this->mRadius; -} - -void PendingRegion::setBottomLeftRadius(qint32 radius) { - this->mBottomLeftRadius = radius; - this->mCornerOverrides |= BottomLeft; - emit this->bottomLeftRadiusChanged(); -} - -void PendingRegion::resetBottomLeftRadius() { - this->mCornerOverrides &= ~BottomLeft; - emit this->bottomLeftRadiusChanged(); -} - -qint32 PendingRegion::bottomRightRadius() const { - return (this->mCornerOverrides & BottomRight) ? this->mBottomRightRadius : this->mRadius; -} - -void PendingRegion::setBottomRightRadius(qint32 radius) { - this->mBottomRightRadius = radius; - this->mCornerOverrides |= BottomRight; - emit this->bottomRightRadiusChanged(); -} - -void PendingRegion::resetBottomRightRadius() { - this->mCornerOverrides &= ~BottomRight; - emit this->bottomRightRadiusChanged(); -} - QQmlListProperty PendingRegion::regions() { return QQmlListProperty( this, @@ -169,60 +90,6 @@ QRegion PendingRegion::build() const { region = QRegion(this->mX, this->mY, this->mWidth, this->mHeight, type); } - if (this->mShape == RegionShape::Rect && !region.isEmpty()) { - auto tl = std::max(this->topLeftRadius(), 0); - auto tr = std::max(this->topRightRadius(), 0); - auto bl = std::max(this->bottomLeftRadius(), 0); - auto br = std::max(this->bottomRightRadius(), 0); - - if (tl > 0 || tr > 0 || bl > 0 || br > 0) { - auto rect = region.boundingRect(); - auto x = rect.x(); - auto y = rect.y(); - auto w = rect.width(); - auto h = rect.height(); - - // Normalize so adjacent corners don't exceed their shared edge. - // Each corner is scaled by the tightest constraint of its two edges. - auto topScale = tl + tr > w ? static_cast(w) / (tl + tr) : 1.0; - auto bottomScale = bl + br > w ? static_cast(w) / (bl + br) : 1.0; - auto leftScale = tl + bl > h ? static_cast(h) / (tl + bl) : 1.0; - auto rightScale = tr + br > h ? static_cast(h) / (tr + br) : 1.0; - - tl = static_cast(tl * std::min(topScale, leftScale)); - tr = static_cast(tr * std::min(topScale, rightScale)); - bl = static_cast(bl * std::min(bottomScale, leftScale)); - br = static_cast(br * std::min(bottomScale, rightScale)); - - // Unlock each corner: subtract (cornerBox - quarterEllipse) from the - // full rect. Each corner only modifies pixels inside its own box, - // so no diagonal overlap is possible. - if (tl > 0) { - auto box = QRegion(x, y, tl, tl); - auto ellipse = QRegion(x, y, tl * 2, tl * 2, QRegion::Ellipse); - region -= box - (ellipse & box); - } - - if (tr > 0) { - auto box = QRegion(x + w - tr, y, tr, tr); - auto ellipse = QRegion(x + w - tr * 2, y, tr * 2, tr * 2, QRegion::Ellipse); - region -= box - (ellipse & box); - } - - if (bl > 0) { - auto box = QRegion(x, y + h - bl, bl, bl); - auto ellipse = QRegion(x, y + h - bl * 2, bl * 2, bl * 2, QRegion::Ellipse); - region -= box - (ellipse & box); - } - - if (br > 0) { - auto box = QRegion(x + w - br, y + h - br, br, br); - auto ellipse = QRegion(x + w - br * 2, y + h - br * 2, br * 2, br * 2, QRegion::Ellipse); - region -= box - (ellipse & box); - } - } - } - for (const auto& childRegion: this->mRegions) { region = childRegion->applyTo(region); } diff --git a/src/core/region.hpp b/src/core/region.hpp index dfd1566..6637d7b 100644 --- a/src/core/region.hpp +++ b/src/core/region.hpp @@ -66,29 +66,6 @@ class PendingRegion: public QObject { Q_PROPERTY(qint32 width MEMBER mWidth NOTIFY widthChanged); /// Defaults to 0. Does nothing if @@item is set. Q_PROPERTY(qint32 height MEMBER mHeight NOTIFY heightChanged); - // clang-format off - /// Corner radius for rounded rectangles. Only applies when @@shape is `Rect`. Defaults to 0. - /// - /// Acts as the default for @@topLeftRadius, @@topRightRadius, @@bottomLeftRadius, - /// and @@bottomRightRadius. - Q_PROPERTY(qint32 radius READ radius WRITE setRadius NOTIFY radiusChanged); - /// Top-left corner radius. Only applies when @@shape is `Rect`. - /// - /// Defaults to @@radius, and may be reset by assigning `undefined`. - Q_PROPERTY(qint32 topLeftRadius READ topLeftRadius WRITE setTopLeftRadius RESET resetTopLeftRadius NOTIFY topLeftRadiusChanged); - /// Top-right corner radius. Only applies when @@shape is `Rect`. - /// - /// Defaults to @@radius, and may be reset by assigning `undefined`. - Q_PROPERTY(qint32 topRightRadius READ topRightRadius WRITE setTopRightRadius RESET resetTopRightRadius NOTIFY topRightRadiusChanged); - /// Bottom-left corner radius. Only applies when @@shape is `Rect`. - /// - /// Defaults to @@radius, and may be reset by assigning `undefined`. - Q_PROPERTY(qint32 bottomLeftRadius READ bottomLeftRadius WRITE setBottomLeftRadius RESET resetBottomLeftRadius NOTIFY bottomLeftRadiusChanged); - /// Bottom-right corner radius. Only applies when @@shape is `Rect`. - /// - /// Defaults to @@radius, and may be reset by assigning `undefined`. - Q_PROPERTY(qint32 bottomRightRadius READ bottomRightRadius WRITE setBottomRightRadius RESET resetBottomRightRadius NOTIFY bottomRightRadiusChanged); - // clang-format on /// Regions to apply on top of this region. /// @@ -114,25 +91,6 @@ public: void setItem(QQuickItem* item); - [[nodiscard]] qint32 radius() const; - void setRadius(qint32 radius); - - [[nodiscard]] qint32 topLeftRadius() const; - void setTopLeftRadius(qint32 radius); - void resetTopLeftRadius(); - - [[nodiscard]] qint32 topRightRadius() const; - void setTopRightRadius(qint32 radius); - void resetTopRightRadius(); - - [[nodiscard]] qint32 bottomLeftRadius() const; - void setBottomLeftRadius(qint32 radius); - void resetBottomLeftRadius(); - - [[nodiscard]] qint32 bottomRightRadius() const; - void setBottomRightRadius(qint32 radius); - void resetBottomRightRadius(); - QQmlListProperty regions(); [[nodiscard]] bool empty() const; @@ -151,11 +109,6 @@ signals: void yChanged(); void widthChanged(); void heightChanged(); - void radiusChanged(); - void topLeftRadiusChanged(); - void topRightRadiusChanged(); - void bottomLeftRadiusChanged(); - void bottomRightRadiusChanged(); void childrenChanged(); /// Triggered when the region's geometry changes. @@ -177,25 +130,12 @@ private: static void regionsReplace(QQmlListProperty* prop, qsizetype i, PendingRegion* region); - enum CornerOverride : quint8 { - TopLeft = 0b1, - TopRight = 0b10, - BottomLeft = 0b100, - BottomRight = 0b1000, - }; - QQuickItem* mItem = nullptr; qint32 mX = 0; qint32 mY = 0; qint32 mWidth = 0; qint32 mHeight = 0; - qint32 mRadius = 0; - qint32 mTopLeftRadius = 0; - qint32 mTopRightRadius = 0; - qint32 mBottomLeftRadius = 0; - qint32 mBottomRightRadius = 0; - quint8 mCornerOverrides = 0; QList mRegions; }; diff --git a/src/core/rootwrapper.cpp b/src/core/rootwrapper.cpp index 1e75819..25c46cc 100644 --- a/src/core/rootwrapper.cpp +++ b/src/core/rootwrapper.cpp @@ -63,6 +63,9 @@ void RootWrapper::reloadGraph(bool hard) { qs::core::QmlToolingSupport::updateTooling(rootPath, scanner); this->configDirWatcher.addPath(rootPath.path()); + auto* generation = new EngineGeneration(rootPath, std::move(scanner)); + generation->wrapper = this; + // todo: move into EngineGeneration if (this->generation != nullptr) { qInfo() << "Reloading configuration..."; @@ -71,33 +74,6 @@ void RootWrapper::reloadGraph(bool hard) { QDir::setCurrent(this->originalWorkingDirectory); - if (!scanner.scanErrors.isEmpty()) { - qCritical() << "Failed to load configuration"; - QString errorString = "Failed to load configuration"; - for (auto& error: scanner.scanErrors) { - const auto& file = error.file; - QString rel; - if (file.startsWith(rootPath.path() % '/')) { - rel = '@' % file.sliced(rootPath.path().length() + 1); - } else { - rel = file; - } - - auto msg = " error in " % rel % '[' % QString::number(error.line) % ":0]: " % error.message; - errorString += '\n' % msg; - qCritical().noquote() << msg; - } - - if (this->generation != nullptr && this->generation->qsgInstance != nullptr) { - emit this->generation->qsgInstance->reloadFailed(errorString); - } - - return; - } - - auto* generation = new EngineGeneration(rootPath, std::move(scanner)); - generation->wrapper = this; - QUrl url; url.setScheme("qs"); url.setPath("@/qs/" % rootFile.fileName()); diff --git a/src/core/scan.cpp b/src/core/scan.cpp index 3605c52..4306de7 100644 --- a/src/core/scan.cpp +++ b/src/core/scan.cpp @@ -1,12 +1,9 @@ #include "scan.hpp" #include -#include #include -#include #include #include -#include #include #include #include @@ -15,39 +12,19 @@ #include #include #include +#include #include #include "logcat.hpp" -#include "scanenv.hpp" 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); - - const auto& path = dir.path(); +void QmlScanner::scanDir(const QString& path) { + if (this->scannedDirs.contains(path)) return; + this->scannedDirs.push_back(path); qCDebug(logQmlScanner) << "Scanning directory" << path; + auto dir = QDir(path); struct Entry { QString name; @@ -60,8 +37,7 @@ void QmlScanner::scanDir(const QDir& dir) { for (auto& name: dir.entryList(QDir::Files | QDir::NoDotAndDotDot)) { if (name == "qmldir") { - qCDebug( - logQmlScanner + qCDebug(logQmlScanner ) << "Found qmldir file, qmldir synthesization will be disabled for directory" << path; seenQmldir = true; @@ -129,125 +105,68 @@ 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; - auto ifScopes = QVector(); - bool sourceMasked = false; - int lineNum = 0; - QString overrideText; - bool isOverridden = false; - - auto& pragmaEngine = *QmlScanner::preprocEngine(); - - auto postError = [&, this](QString error) { - this->scanErrors.append({.file = path, .message = std::move(error), .line = lineNum}); - }; - while (!stream.atEnd()) { - ++lineNum; - bool hideMask = false; - auto rawLine = stream.readLine(); - auto line = rawLine.trimmed(); - if (!sourceMasked && inHeader) { - if (!singleton && line == "pragma Singleton") { - singleton = true; - } else if (line.startsWith("import")) { - // we dont care about "import qs" as we always load the root folder - if (auto importCursor = line.indexOf(" qs."); importCursor != -1) { - importCursor += 4; - QString path; + auto line = stream.readLine().trimmed(); + if (!singleton && line == "pragma Singleton") { + singleton = true; + } else if (!internal && line == "//@ pragma Internal") { + internal = true; + } else if (line.startsWith("import")) { + // we dont care about "import qs" as we always load the root folder + if (auto importCursor = line.indexOf(" qs."); importCursor != -1) { + importCursor += 4; + QString path; - while (importCursor != line.length()) { - auto c = line.at(importCursor); - if (c == '.') c = '/'; - else if (c == ' ') break; - else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') - || c == '_') - { - } else { - qCWarning(logQmlScanner) << "Import line contains invalid characters: " << line; - goto next; - } - - path.append(c); - importCursor += 1; + while (importCursor != line.length()) { + auto c = line.at(importCursor); + if (c == '.') c = '/'; + else if (c == ' ') break; + else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') + || c == '_') + { + } else { + qCWarning(logQmlScanner) << "Import line contains invalid characters: " << line; + goto next; } - imports.append(this->rootPath.filePath(path)); - } else if (auto startQuot = line.indexOf('"'); - startQuot != -1 && line.length() >= startQuot + 3) - { - auto endQuot = line.indexOf('"', startQuot + 1); - if (endQuot == -1) continue; - - auto name = line.sliced(startQuot + 1, endQuot - startQuot - 1); - imports.push_back(name); + path.append(c); + importCursor += 1; } - } else if (!internal && line == "//@ pragma Internal") { - internal = true; - } else if (line.contains('{')) { - inHeader = false; + + imports.append(this->rootPath.filePath(path)); + } else if (auto startQuot = line.indexOf('"'); + startQuot != -1 && line.length() >= startQuot + 3) + { + auto endQuot = line.indexOf('"', startQuot + 1); + if (endQuot == -1) continue; + + auto name = line.sliced(startQuot + 1, endQuot - startQuot - 1); + imports.push_back(name); } - } - - if (line.startsWith("//@ if ")) { - auto code = line.sliced(7); - auto value = pragmaEngine.evaluate(code, path, 1234); - bool mask = true; - - if (value.isError()) { - postError(QString("Evaluating if: %0").arg(value.toString())); - } else if (!value.isBool()) { - postError(QString("If expression \"%0\" is not a boolean").arg(value.toString())); - } else if (value.toBool()) { - mask = false; - } - if (!sourceMasked && mask) hideMask = true; - mask = sourceMasked || mask; // cant unmask if a nested if passes - ifScopes.append(mask); - if (mask) isOverridden = true; - sourceMasked = mask; - } else if (line.startsWith("//@ endif")) { - if (ifScopes.isEmpty()) { - postError("endif without matching if"); - } else { - ifScopes.pop_back(); - - if (ifScopes.isEmpty()) sourceMasked = false; - else sourceMasked = ifScopes.last(); - } - } - - if (!hideMask && sourceMasked) overrideText.append("// MASKED: " % rawLine % '\n'); - else overrideText.append(rawLine % '\n'); + } else if (line.contains('{')) break; next:; } - if (!ifScopes.isEmpty()) { - postError("unclosed preprocessor if block"); - } - - if (isOverridden) { - this->fileIntercepts.insert(path, overrideText); - } + file.close(); if (logQmlScanner().isDebugEnabled() && !imports.isEmpty()) { qCDebug(logQmlScanner) << "Found imports" << imports; } - auto currentdir = QDir(QFileInfo(path).absolutePath()); + auto currentdir = QDir(QFileInfo(path).canonicalPath()); // the root can never be a singleton so it dosent matter if we skip it - this->scanDir(currentdir); + this->scanDir(currentdir.path()); for (auto& import: imports) { QString ipath; @@ -260,9 +179,9 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna } auto pathInfo = QFileInfo(ipath); - auto cpath = pathInfo.absoluteFilePath(); + auto cpath = pathInfo.canonicalFilePath(); - if (!pathInfo.exists()) { + if (cpath.isEmpty()) { qCWarning(logQmlScanner) << "Ignoring unresolvable import" << ipath << "from" << path; continue; } @@ -272,11 +191,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; @@ -291,12 +207,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); @@ -367,13 +285,3 @@ QPair QmlScanner::jsonToQml(const QJsonValue& value, int inden return qMakePair(QStringLiteral("var"), "null"); } } - -QJSEngine* QmlScanner::preprocEngine() { - static auto* engine = [] { - auto* engine = new QJSEngine(); - engine->globalObject().setPrototype(engine->newQObject(new qs::scan::env::PreprocEnv())); - return engine; - }(); - - return engine; -} diff --git a/src/core/scan.hpp b/src/core/scan.hpp index 7d807e1..1d3be85 100644 --- a/src/core/scan.hpp +++ b/src/core/scan.hpp @@ -1,10 +1,8 @@ #pragma once -#include #include #include #include -#include #include #include @@ -18,31 +16,19 @@ public: QmlScanner() = default; QmlScanner(const QDir& rootPath): rootPath(rootPath) {} - void scanDir(const QDir& dir); + // path must be canonical + void scanDir(const QString& path); + void scanQmlRoot(const QString& path); - QVector scannedDirs; + QVector scannedDirs; QVector scannedFiles; - QHash fileHashes; QHash fileIntercepts; - struct ScanError { - QString file; - QString message; - int line; - }; - - QVector scanErrors; - - bool readAndHashFile(const QString& path, QByteArray& data); - [[nodiscard]] bool hasFileContentChanged(const QString& path) const; - private: QDir rootPath; bool scanQmlFile(const QString& path, bool& singleton, bool& internal); bool scanQmlJson(const QString& path); [[nodiscard]] static QPair jsonToQml(const QJsonValue& value, int indent = 0); - - static QJSEngine* preprocEngine(); }; diff --git a/src/core/scanenv.cpp b/src/core/scanenv.cpp deleted file mode 100644 index 047f472..0000000 --- a/src/core/scanenv.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "scanenv.hpp" - -#include -#include - -#include "build.hpp" - -namespace qs::scan::env { - -bool PreprocEnv::hasVersion(int major, int minor, const QStringList& features) { - if (QS_VERSION_MAJOR > major) return true; - if (QS_VERSION_MAJOR == major && QS_VERSION_MINOR > minor) return true; - - auto availFeatures = QString(QS_UNRELEASED_FEATURES).split(';'); - - for (const auto& feature: features) { - if (!availFeatures.contains(feature)) return false; - } - - return QS_VERSION_MAJOR == major && QS_VERSION_MINOR == minor; -} - -QString PreprocEnv::env(const QString& variable) { - return qEnvironmentVariable(variable.toStdString().c_str()); -} - -bool PreprocEnv::isEnvSet(const QString& variable) { - return qEnvironmentVariableIsSet(variable.toStdString().c_str()); -} - -} // namespace qs::scan::env diff --git a/src/core/scanenv.hpp b/src/core/scanenv.hpp deleted file mode 100644 index c1c6814..0000000 --- a/src/core/scanenv.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace qs::scan::env { - -class PreprocEnv: public QObject { - Q_OBJECT; - -public: - Q_INVOKABLE static bool - hasVersion(int major, int minor, const QStringList& features = QStringList()); - - Q_INVOKABLE static QString env(const QString& variable); - Q_INVOKABLE static bool isEnvSet(const QString& variable); -}; - -} // namespace qs::scan::env diff --git a/src/core/scriptmodel.cpp b/src/core/scriptmodel.cpp index 5407e2b..6837c4a 100644 --- a/src/core/scriptmodel.cpp +++ b/src/core/scriptmodel.cpp @@ -19,7 +19,7 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) { auto newIter = newValues.begin(); // TODO: cache this - auto getCmpKey = [this](const QVariant& v) { + auto getCmpKey = [&](const QVariant& v) { if (v.canConvert()) { auto vMap = v.value(); if (vMap.contains(this->cmpKey)) { @@ -30,7 +30,7 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) { return v; }; - auto variantCmp = [&, this](const QVariant& a, const QVariant& b) { + auto variantCmp = [&](const QVariant& a, const QVariant& b) { if (!this->cmpKey.isEmpty()) return getCmpKey(a) == getCmpKey(b); else return a == b; }; @@ -72,8 +72,8 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) { do { ++iter; } while (iter != this->mValues.end() - && std::find_if(newIter, newValues.end(), eqPredicate(*iter)) - == newValues.end()); + && std::find_if(newIter, newValues.end(), eqPredicate(*iter)) == newValues.end() + ); auto index = static_cast(std::distance(this->mValues.begin(), iter)); auto startIndex = static_cast(std::distance(this->mValues.begin(), startIter)); 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/toolsupport.cpp b/src/core/toolsupport.cpp index 585656e..afce008 100644 --- a/src/core/toolsupport.cpp +++ b/src/core/toolsupport.cpp @@ -54,7 +54,7 @@ bool QmlToolingSupport::lockTooling() { return false; } - struct flock lock = { + auto lock = flock { .l_type = F_WRLCK, .l_whence = SEEK_SET, // NOLINT (fcntl.h??) .l_start = 0, @@ -177,8 +177,6 @@ void QmlToolingSupport::updateToolingFs( auto fileInfo = QFileInfo(path); if (!fileInfo.isFile()) continue; - if (scanner.fileIntercepts.contains(path)) continue; - auto spath = linkDir.filePath(name); auto sFileInfo = QFileInfo(spath); @@ -207,10 +205,8 @@ void QmlToolingSupport::updateToolingFs( } auto spath = linkDir.filePath(name); - QFile::remove(spath); - auto file = QFile(spath); - if (!file.open(QFile::ReadWrite | QFile::Text | QFile::NewOnly)) { + if (!file.open(QFile::ReadWrite | QFile::Text)) { qCCritical(logTooling) << "Failed to open injected file" << spath; continue; } diff --git a/src/core/util.hpp b/src/core/util.hpp index bb8dd85..88583d0 100644 --- a/src/core/util.hpp +++ b/src/core/util.hpp @@ -29,7 +29,7 @@ struct StringLiteral16 { } [[nodiscard]] constexpr const QChar* qCharPtr() const noexcept { - return std::bit_cast(&this->value); // NOLINT + return std::bit_cast(&this->value); } [[nodiscard]] Q_ALWAYS_INLINE operator QString() const noexcept { @@ -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 33506a6..1433a87 100644 --- a/src/crash/handler.cpp +++ b/src/crash/handler.cpp @@ -1,15 +1,12 @@ #include "handler.hpp" -#include #include -#include -#include #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include @@ -22,78 +19,92 @@ 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; + file.open(this->d->infoFd, QFile::ReadWrite); - *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*/ ) { - // NOLINTBEGIN (misc-include-cleaner) - sigset_t set; - sigfillset(&set); - sigprocmask(SIG_UNBLOCK, &set, nullptr); - // NOLINTEND - - 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:; - } - - // TODO: coredump fork and crash reporter remain as zombies, fix + // A fork that just dies to ensure the coredump is caught by the system. auto coredumpPid = fork(); + if (coredumpPid == 0) { - 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"); @@ -105,19 +116,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) { @@ -129,17 +138,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(); @@ -156,99 +178,8 @@ void signalHandler( perror("Failed to relaunch quickshell.\n"); _exit(-1); } -} -void handleCppTerminate() { - if (auto ptr = std::current_exception()) { - try { - std::rethrow_exception(ptr); - } catch (std::exception& e) { - qFatal().nospace() << "Terminate called with C++ exception (" - << cpptrace::demangle(typeid(e).name()).data() << "): " << e.what(); - } catch (...) { - qFatal() << "Terminate called with non exception object"; - } - } - - qFatal() << "Terminate called without active C++ exception"; -} - -} // 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) - - std::set_terminate(&handleCppTerminate); - - 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..c633440 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) { @@ -78,17 +66,22 @@ CrashReporterGui::CrashReporterGui(QString reportFolder, int pid) mainLayout->addSpacing(textHeight); if (qtVersionMatches) { - mainLayout->addWidget( - new QLabel("Please open a bug report for this issue on the issue tracker.") + mainLayout->addWidget(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 +111,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 6533b43..b9f0eab 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,19 +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/plugin.hpp" -#include "../core/ringbuf.hpp" +#include "build.hpp" #include "interface.hpp" namespace { @@ -67,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(); @@ -147,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(); } @@ -212,10 +161,7 @@ void qsCheckCrash(int argc, char** argv) { auto infoFd = qEnvironmentVariable("__QUICKSHELL_CRASH_INFO_FD").toInt(); QFile file; - if (!file.open(infoFd, QFile::ReadOnly, QFile::AutoCloseHandle)) { - qFatal() << "Failed to open instance info fd."; - } - + file.open(infoFd, QFile::ReadOnly, QFile::AutoCloseHandle); file.seek(0); auto ds = QDataStream(&file); @@ -231,17 +177,12 @@ void qsCheckCrash(int argc, char** argv) { ); auto app = QApplication(argc, argv); - auto desktopId = - info.instance.appId.isEmpty() ? QStringLiteral("org.quickshell") : info.instance.appId; - QApplication::setDesktopFileName(desktopId); + QApplication::setDesktopFileName("org.quickshell"); auto crashDir = QsPaths::crashDir(info.instance.instanceId); qCInfo(logCrashReporter) << "Starting crash reporter..."; - // Required platform compatibility hooks - QsEnginePlugin::preinitPluginsOnly(); - recordCrashInfo(crashDir, info.instance); auto gui = CrashReporterGui(crashDir.path(), crashProc); diff --git a/src/dbus/dbusmenu/dbusmenu.cpp b/src/dbus/dbusmenu/dbusmenu.cpp index bcb354d..186b133 100644 --- a/src/dbus/dbusmenu/dbusmenu.cpp +++ b/src/dbus/dbusmenu/dbusmenu.cpp @@ -183,7 +183,7 @@ void DBusMenuItem::updateProperties(const QVariantMap& properties, const QString } } else if (removed.isEmpty() || removed.contains("icon-data")) { imageChanged = this->image.hasData(); - this->image.data.clear(); + image.data.clear(); } auto type = properties.value("type"); diff --git a/src/dbus/dbusmenu/dbusmenu.hpp b/src/dbus/dbusmenu/dbusmenu.hpp index 06cbc34..1192baa 100644 --- a/src/dbus/dbusmenu/dbusmenu.hpp +++ b/src/dbus/dbusmenu/dbusmenu.hpp @@ -36,7 +36,7 @@ class DBusMenuPngImage: public QsIndexedImageHandle { public: explicit DBusMenuPngImage(): QsIndexedImageHandle(QQuickImageProvider::Image) {} - [[nodiscard]] bool hasData() const { return !this->data.isEmpty(); } + [[nodiscard]] bool hasData() const { return !data.isEmpty(); } QImage requestImage(const QString& id, QSize* size, const QSize& requestedSize) override; QByteArray data; diff --git a/src/dbus/properties.cpp b/src/dbus/properties.cpp index 2c478ef..d0f65d9 100644 --- a/src/dbus/properties.cpp +++ b/src/dbus/properties.cpp @@ -214,10 +214,8 @@ void DBusPropertyGroup::updatePropertySet(const QVariantMap& properties, bool co } } -void DBusPropertyGroup::tryUpdateProperty( - DBusPropertyCore* property, - const QVariant& variant -) const { +void DBusPropertyGroup::tryUpdateProperty(DBusPropertyCore* property, const QVariant& variant) + const { property->mExists = true; auto error = property->store(variant); diff --git a/src/dbus/properties.hpp b/src/dbus/properties.hpp index 1596cb7..f6a6330 100644 --- a/src/dbus/properties.hpp +++ b/src/dbus/properties.hpp @@ -217,7 +217,7 @@ protected: private: [[nodiscard]] constexpr Owner* owner() const { - auto* self = std::bit_cast(this); // NOLINT + auto* self = std::bit_cast(this); return std::bit_cast(self - offset()); // NOLINT } diff --git a/src/debug/lint.cpp b/src/debug/lint.cpp index 5e12f76..dd65a28 100644 --- a/src/debug/lint.cpp +++ b/src/debug/lint.cpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include "../core/logcat.hpp" diff --git a/src/io/CMakeLists.txt b/src/io/CMakeLists.txt index 991beaa..17628d3 100644 --- a/src/io/CMakeLists.txt +++ b/src/io/CMakeLists.txt @@ -24,7 +24,7 @@ qt_add_qml_module(quickshell-io qs_add_module_deps_light(quickshell-io Quickshell) install_qml_module(quickshell-io) -target_link_libraries(quickshell-io PRIVATE Qt::Quick quickshell-ipc) +target_link_libraries(quickshell-io PRIVATE Qt::Quick) target_link_libraries(quickshell PRIVATE quickshell-ioplugin) qs_module_pch(quickshell-io) diff --git a/src/io/fileview.cpp b/src/io/fileview.cpp index 04d77bd..1585f26 100644 --- a/src/io/fileview.cpp +++ b/src/io/fileview.cpp @@ -93,8 +93,7 @@ void FileViewReader::run() { FileViewReader::read(this->owner, this->state, this->doStringConversion, this->shouldCancel); if (this->shouldCancel.loadAcquire()) { - qCDebug(logFileView) << "Read" << this << "of" << this->state.path << "canceled for" - << this->owner; + qCDebug(logFileView) << "Read" << this << "of" << state.path << "canceled for" << this->owner; } } @@ -207,7 +206,7 @@ void FileViewWriter::run() { FileViewWriter::write(this->owner, this->state, this->doAtomicWrite, this->shouldCancel); if (this->shouldCancel.loadAcquire()) { - qCDebug(logFileView) << "Write" << this << "of" << this->state.path << "canceled for" + qCDebug(logFileView) << "Write" << this << "of" << state.path << "canceled for" << this->owner; } } diff --git a/src/io/ipc.cpp b/src/io/ipc.cpp index c381567..768299e 100644 --- a/src/io/ipc.cpp +++ b/src/io/ipc.cpp @@ -190,14 +190,6 @@ QString WirePropertyDefinition::toString() const { return "property " % this->name % ": " % this->type; } -QString WireSignalDefinition::toString() const { - if (this->rettype.isEmpty()) { - return "signal " % this->name % "()"; - } else { - return "signal " % this->name % "(" % this->retname % ": " % this->rettype % ')'; - } -} - QString WireTargetDefinition::toString() const { QString accum = "target " % this->name; @@ -209,10 +201,6 @@ QString WireTargetDefinition::toString() const { accum += "\n " % prop.toString(); } - for (const auto& sig: this->signalFunctions) { - accum += "\n " % sig.toString(); - } - return accum; } diff --git a/src/io/ipc.hpp b/src/io/ipc.hpp index 32486d6..d2b865a 100644 --- a/src/io/ipc.hpp +++ b/src/io/ipc.hpp @@ -146,31 +146,14 @@ struct WirePropertyDefinition { DEFINE_SIMPLE_DATASTREAM_OPS(WirePropertyDefinition, data.name, data.type); -struct WireSignalDefinition { - QString name; - QString retname; - QString rettype; - - [[nodiscard]] QString toString() const; -}; - -DEFINE_SIMPLE_DATASTREAM_OPS(WireSignalDefinition, data.name, data.retname, data.rettype); - struct WireTargetDefinition { QString name; QVector functions; QVector properties; - QVector signalFunctions; [[nodiscard]] QString toString() const; }; -DEFINE_SIMPLE_DATASTREAM_OPS( - WireTargetDefinition, - data.name, - data.functions, - data.properties, - data.signalFunctions -); +DEFINE_SIMPLE_DATASTREAM_OPS(WireTargetDefinition, data.name, data.functions, data.properties); } // namespace qs::io::ipc diff --git a/src/io/ipccomm.cpp b/src/io/ipccomm.cpp index 03b688a..7203a30 100644 --- a/src/io/ipccomm.cpp +++ b/src/io/ipccomm.cpp @@ -1,11 +1,10 @@ #include "ipccomm.hpp" -#include +#include #include #include #include #include -#include #include #include @@ -20,6 +19,10 @@ using namespace qs::ipc; namespace qs::io::ipc::comm { +struct NoCurrentGeneration: std::monostate {}; +struct TargetNotFound: std::monostate {}; +struct EntryNotFound: std::monostate {}; + using QueryResponse = std::variant< std::monostate, NoCurrentGeneration, @@ -311,106 +314,4 @@ int getProperty(IpcClient* client, const QString& target, const QString& propert return -1; } -int listenToSignal(IpcClient* client, const QString& target, const QString& signal, bool once) { - if (target.isEmpty()) { - qCCritical(logBare) << "Target required to listen for signals."; - return -1; - } else if (signal.isEmpty()) { - qCCritical(logBare) << "Signal required to listen."; - return -1; - } - - client->sendMessage(IpcCommand(SignalListenCommand {.target = target, .signal = signal})); - - while (true) { - SignalListenResponse slot; - if (!client->waitForResponse(slot)) return -1; - - if (std::holds_alternative(slot)) { - auto& result = std::get(slot); - QTextStream(stdout) << result.response << Qt::endl; - if (once) return 0; - else continue; - } else if (std::holds_alternative(slot)) { - qCCritical(logBare) << "Target not found."; - } else if (std::holds_alternative(slot)) { - qCCritical(logBare) << "Signal not found."; - } else if (std::holds_alternative(slot)) { - qCCritical(logBare) << "Not ready to accept queries yet."; - } else { - qCCritical(logIpc) << "Received invalid IPC response from" << client; - } - break; - } - - return -1; -} - -void SignalListenCommand::exec(qs::ipc::IpcServerConnection* conn) { - auto resp = conn->responseStream(); - - if (auto* generation = EngineGeneration::currentGeneration()) { - auto* registry = IpcHandlerRegistry::forGeneration(generation); - - auto* handler = registry->findHandler(this->target); - if (!handler) { - resp << TargetNotFound(); - return; - } - - auto* signal = handler->findSignal(this->signal); - if (!signal) { - resp << EntryNotFound(); - return; - } - - new RemoteSignalListener(conn, *this); - } else { - conn->respond(SignalListenResponse(NoCurrentGeneration())); - } -} - -RemoteSignalListener::RemoteSignalListener( - qs::ipc::IpcServerConnection* conn, - SignalListenCommand command -) - : conn(conn) - , command(std::move(command)) { - conn->setParent(this); - - QObject::connect( - IpcSignalRemoteListener::instance(), - &IpcSignalRemoteListener::triggered, - this, - &RemoteSignalListener::onSignal - ); - - QObject::connect( - conn, - &qs::ipc::IpcServerConnection::destroyed, - this, - &RemoteSignalListener::onConnDestroyed - ); - - qCDebug(logIpc) << "Remote listener created for" << this->command.target << this->command.signal - << ":" << this; -} - -RemoteSignalListener::~RemoteSignalListener() { - qCDebug(logIpc) << "Destroying remote listener" << this; -} - -void RemoteSignalListener::onSignal( - const QString& target, - const QString& signal, - const QString& value -) { - if (target != this->command.target || signal != this->command.signal) return; - qCDebug(logIpc) << "Remote signal" << signal << "triggered on" << target << "with value" << value; - - this->conn->respond(SignalListenResponse(SignalResponse {.response = value})); -} - -void RemoteSignalListener::onConnDestroyed() { this->deleteLater(); } - } // namespace qs::io::ipc::comm diff --git a/src/io/ipccomm.hpp b/src/io/ipccomm.hpp index ac12979..bc7dbf9 100644 --- a/src/io/ipccomm.hpp +++ b/src/io/ipccomm.hpp @@ -2,8 +2,6 @@ #include #include -#include -#include #include #include "../ipc/ipc.hpp" @@ -50,52 +48,4 @@ DEFINE_SIMPLE_DATASTREAM_OPS(StringPropReadCommand, data.target, data.property); int getProperty(qs::ipc::IpcClient* client, const QString& target, const QString& property); -struct SignalListenCommand { - QString target; - QString signal; - - void exec(qs::ipc::IpcServerConnection* conn); -}; - -DEFINE_SIMPLE_DATASTREAM_OPS(SignalListenCommand, data.target, data.signal); - -int listenToSignal( - qs::ipc::IpcClient* client, - const QString& target, - const QString& signal, - bool once -); - -struct NoCurrentGeneration: std::monostate {}; -struct TargetNotFound: std::monostate {}; -struct EntryNotFound: std::monostate {}; - -struct SignalResponse { - QString response; -}; - -DEFINE_SIMPLE_DATASTREAM_OPS(SignalResponse, data.response); - -using SignalListenResponse = std:: - variant; - -class RemoteSignalListener: public QObject { - Q_OBJECT; - -public: - explicit RemoteSignalListener(qs::ipc::IpcServerConnection* conn, SignalListenCommand command); - - ~RemoteSignalListener() override; - - Q_DISABLE_COPY_MOVE(RemoteSignalListener); - -private slots: - void onSignal(const QString& target, const QString& signal, const QString& value); - void onConnDestroyed(); - -private: - qs::ipc::IpcServerConnection* conn; - SignalListenCommand command; -}; - } // namespace qs::io::ipc::comm diff --git a/src/io/ipchandler.cpp b/src/io/ipchandler.cpp index e80cf4b..5ffa0ad 100644 --- a/src/io/ipchandler.cpp +++ b/src/io/ipchandler.cpp @@ -1,7 +1,5 @@ #include "ipchandler.hpp" #include -#include -#include #include #include @@ -141,75 +139,6 @@ WirePropertyDefinition IpcProperty::wireDef() const { return wire; } -WireSignalDefinition IpcSignal::wireDef() const { - WireSignalDefinition wire; - wire.name = this->signal.name(); - if (this->targetSlot != IpcSignalListener::SLOT_VOID) { - wire.retname = this->signal.parameterNames().value(0); - if (this->targetSlot == IpcSignalListener::SLOT_STRING) wire.rettype = "string"; - else if (this->targetSlot == IpcSignalListener::SLOT_INT) wire.rettype = "int"; - else if (this->targetSlot == IpcSignalListener::SLOT_BOOL) wire.rettype = "bool"; - else if (this->targetSlot == IpcSignalListener::SLOT_REAL) wire.rettype = "real"; - else if (this->targetSlot == IpcSignalListener::SLOT_COLOR) wire.rettype = "color"; - } - return wire; -} - -// NOLINTBEGIN (cppcoreguidelines-interfaces-global-init) -// clang-format off -const int IpcSignalListener::SLOT_VOID = IpcSignalListener::staticMetaObject.indexOfSlot("invokeVoid()"); -const int IpcSignalListener::SLOT_STRING = IpcSignalListener::staticMetaObject.indexOfSlot("invokeString(QString)"); -const int IpcSignalListener::SLOT_INT = IpcSignalListener::staticMetaObject.indexOfSlot("invokeInt(int)"); -const int IpcSignalListener::SLOT_BOOL = IpcSignalListener::staticMetaObject.indexOfSlot("invokeBool(bool)"); -const int IpcSignalListener::SLOT_REAL = IpcSignalListener::staticMetaObject.indexOfSlot("invokeReal(double)"); -const int IpcSignalListener::SLOT_COLOR = IpcSignalListener::staticMetaObject.indexOfSlot("invokeColor(QColor)"); -// clang-format on -// NOLINTEND - -bool IpcSignal::resolve(QString& error) { - if (this->signal.parameterCount() > 1) { - error = "Due to technical limitations, IPC signals can have at most one argument."; - return false; - } - - auto slot = IpcSignalListener::SLOT_VOID; - - if (this->signal.parameterCount() == 1) { - auto paramType = this->signal.parameterType(0); - if (paramType == QMetaType::QString) slot = IpcSignalListener::SLOT_STRING; - else if (paramType == QMetaType::Int) slot = IpcSignalListener::SLOT_INT; - else if (paramType == QMetaType::Bool) slot = IpcSignalListener::SLOT_BOOL; - else if (paramType == QMetaType::Double) slot = IpcSignalListener::SLOT_REAL; - else if (paramType == QMetaType::QColor) slot = IpcSignalListener::SLOT_COLOR; - else { - error = QString("Type of argument (%2: %3) cannot be used across IPC.") - .arg(this->signal.parameterNames().value(0)) - .arg(QMetaType(paramType).name()); - - return false; - } - } - - this->targetSlot = slot; - return true; -} - -void IpcSignal::connectListener(IpcHandler* handler) { - if (this->targetSlot == -1) { - qFatal() << "Tried to connect unresolved IPC signal"; - } - - this->listener = std::make_shared(this->signal.name()); - QMetaObject::connect(handler, this->signal.methodIndex(), this->listener.get(), this->targetSlot); - - QObject::connect( - this->listener.get(), - &IpcSignalListener::triggered, - handler, - &IpcHandler::onSignalTriggered - ); -} - IpcCallStorage::IpcCallStorage(const IpcFunction& function): returnSlot(function.returnType) { for (const auto& arg: function.argumentTypes) { this->argumentSlots.emplace_back(arg); @@ -243,28 +172,16 @@ void IpcHandler::onPostReload() { // which should handle inheritance on the qml side. for (auto i = smeta.methodCount(); i != meta->methodCount(); i++) { const auto& method = meta->method(i); - if (method.methodType() == QMetaMethod::Slot) { - auto ipcFunc = IpcFunction(method); - QString error; + if (method.methodType() != QMetaMethod::Slot) continue; - if (!ipcFunc.resolve(error)) { - qmlWarning(this).nospace().noquote() - << "Error parsing function \"" << method.name() << "\": " << error; - } else { - this->functionMap.insert(method.name(), ipcFunc); - } - } else if (method.methodType() == QMetaMethod::Signal) { - qmlDebug(this) << "Signal detected: " << method.name(); - auto ipcSig = IpcSignal(method); - QString error; + auto ipcFunc = IpcFunction(method); + QString error; - if (!ipcSig.resolve(error)) { - qmlWarning(this).nospace().noquote() - << "Error parsing signal \"" << method.name() << "\": " << error; - } else { - ipcSig.connectListener(this); - this->signalMap.emplace(method.name(), std::move(ipcSig)); - } + if (!ipcFunc.resolve(error)) { + qmlWarning(this).nospace().noquote() + << "Error parsing function \"" << method.name() << "\": " << error; + } else { + this->functionMap.insert(method.name(), ipcFunc); } } @@ -305,11 +222,6 @@ IpcHandlerRegistry* IpcHandlerRegistry::forGeneration(EngineGeneration* generati return dynamic_cast(ext); } -void IpcHandler::onSignalTriggered(const QString& signal, const QString& value) const { - emit IpcSignalRemoteListener::instance() - -> triggered(this->registeredState.target, signal, value); -} - void IpcHandler::updateRegistration(bool destroying) { if (!this->complete) return; @@ -412,10 +324,6 @@ WireTargetDefinition IpcHandler::wireDef() const { wire.properties += prop.wireDef(); } - for (const auto& sig: this->signalMap.values()) { - wire.signalFunctions += sig.wireDef(); - } - return wire; } @@ -460,13 +368,6 @@ IpcProperty* IpcHandler::findProperty(const QString& name) { else return &*itr; } -IpcSignal* IpcHandler::findSignal(const QString& name) { - auto itr = this->signalMap.find(name); - - if (itr == this->signalMap.end()) return nullptr; - else return &*itr; -} - IpcHandler* IpcHandlerRegistry::findHandler(const QString& target) { return this->handlers.value(target); } @@ -481,9 +382,4 @@ QVector IpcHandlerRegistry::wireTargets() const { return wire; } -IpcSignalRemoteListener* IpcSignalRemoteListener::instance() { - static auto* instance = new IpcSignalRemoteListener(); - return instance; -} - } // namespace qs::io::ipc diff --git a/src/io/ipchandler.hpp b/src/io/ipchandler.hpp index eb274e3..4c5d9bc 100644 --- a/src/io/ipchandler.hpp +++ b/src/io/ipchandler.hpp @@ -1,10 +1,8 @@ #pragma once #include -#include #include -#include #include #include #include @@ -69,54 +67,6 @@ public: const IpcType* type = nullptr; }; -class IpcSignalListener: public QObject { - Q_OBJECT; - -public: - IpcSignalListener(QString signal): signal(std::move(signal)) {} - - static const int SLOT_VOID; - static const int SLOT_STRING; - static const int SLOT_INT; - static const int SLOT_BOOL; - static const int SLOT_REAL; - static const int SLOT_COLOR; - -signals: - void triggered(const QString& signal, const QString& value); - -private slots: - void invokeVoid() { this->triggered(this->signal, "void"); } - void invokeString(const QString& value) { this->triggered(this->signal, value); } - void invokeInt(int value) { this->triggered(this->signal, QString::number(value)); } - void invokeBool(bool value) { this->triggered(this->signal, value ? "true" : "false"); } - void invokeReal(double value) { this->triggered(this->signal, QString::number(value)); } - void invokeColor(QColor value) { this->triggered(this->signal, value.name(QColor::HexArgb)); } - -private: - QString signal; -}; - -class IpcHandler; - -class IpcSignal { -public: - explicit IpcSignal(QMetaMethod signal): signal(signal) {} - - bool resolve(QString& error); - - [[nodiscard]] WireSignalDefinition wireDef() const; - - QMetaMethod signal; - int targetSlot = -1; - - void connectListener(IpcHandler* handler); - -private: - void connectListener(QObject* handler, IpcSignalListener* listener) const; - std::shared_ptr listener; -}; - class IpcHandlerRegistry; ///! Handler for IPC message calls. @@ -150,11 +100,6 @@ class IpcHandlerRegistry; /// - `real` will be converted to a string and returned. /// - `color` will be converted to a hex string in the form `#AARRGGBB` and returned. /// -/// #### Signals -/// IPC handler signals can be observed remotely using `qs ipc wait` (one call) -/// and `qs ipc listen` (many calls). IPC signals may have zero or one argument, where -/// the argument is one of the types listed above, or no arguments for void. -/// /// #### Example /// The following example creates ipc functions to control and retrieve the appearance /// of a Rectangle. @@ -174,18 +119,10 @@ class IpcHandlerRegistry; /// /// function setColor(color: color): void { rect.color = color; } /// function getColor(): color { return rect.color; } -/// /// function setAngle(angle: real): void { rect.rotation = angle; } /// function getAngle(): real { return rect.rotation; } -/// -/// function setRadius(radius: int): void { -/// rect.radius = radius; -/// this.radiusChanged(radius); -/// } -/// +/// function setRadius(radius: int): void { rect.radius = radius; } /// function getRadius(): int { return rect.radius; } -/// -/// signal radiusChanged(newRadius: int); /// } /// } /// ``` @@ -199,7 +136,6 @@ class IpcHandlerRegistry; /// function getAngle(): real /// function setRadius(radius: int): void /// function getRadius(): int -/// signal radiusChanged(newRadius: int) /// ``` /// /// and then invoked using `qs ipc call`. @@ -243,15 +179,14 @@ public: QString listMembers(qsizetype indent); [[nodiscard]] IpcFunction* findFunction(const QString& name); [[nodiscard]] IpcProperty* findProperty(const QString& name); - [[nodiscard]] IpcSignal* findSignal(const QString& name); [[nodiscard]] WireTargetDefinition wireDef() const; signals: void enabledChanged(); void targetChanged(); -public slots: - void onSignalTriggered(const QString& signal, const QString& value) const; +private slots: + //void handleIpcPropertyChange(); private: void updateRegistration(bool destroying = false); @@ -269,7 +204,6 @@ private: QHash functionMap; QHash propertyMap; - QHash signalMap; friend class IpcHandlerRegistry; }; @@ -293,14 +227,4 @@ private: QHash> knownHandlers; }; -class IpcSignalRemoteListener: public QObject { - Q_OBJECT; - -public: - static IpcSignalRemoteListener* instance(); - -signals: - void triggered(const QString& target, const QString& signal, const QString& value); -}; - } // namespace qs::io::ipc diff --git a/src/io/jsonadapter.cpp b/src/io/jsonadapter.cpp index 369ccbe..80ac091 100644 --- a/src/io/jsonadapter.cpp +++ b/src/io/jsonadapter.cpp @@ -1,13 +1,11 @@ #include "jsonadapter.hpp" -#include #include #include #include #include #include #include -#include #include #include #include @@ -16,7 +14,6 @@ #include #include #include -#include #include #include @@ -47,7 +44,7 @@ void JsonAdapter::deserializeAdapter(const QByteArray& data) { this->deserializeRec(json.object(), this, &JsonAdapter::staticMetaObject); - for (auto* object: this->oldCreatedObjects) { + for (auto* object: oldCreatedObjects) { delete object; // FIXME: QMetaType::destroy? } @@ -59,7 +56,7 @@ void JsonAdapter::deserializeAdapter(const QByteArray& data) { void JsonAdapter::connectNotifiers() { auto notifySlot = JsonAdapter::staticMetaObject.indexOfSlot("onPropertyChanged()"); - this->connectNotifiersRec(notifySlot, this, &JsonAdapter::staticMetaObject); + connectNotifiersRec(notifySlot, this, &JsonAdapter::staticMetaObject); } void JsonAdapter::connectNotifiersRec(int notifySlot, QObject* obj, const QMetaObject* base) { @@ -74,7 +71,7 @@ void JsonAdapter::connectNotifiersRec(int notifySlot, QObject* obj, const QMetaO auto val = prop.read(obj); if (val.canView()) { auto* pobj = prop.read(obj).view(); - if (pobj) this->connectNotifiersRec(notifySlot, pobj, &JsonObject::staticMetaObject); + if (pobj) connectNotifiersRec(notifySlot, pobj, &JsonObject::staticMetaObject); } else if (val.canConvert>()) { auto listVal = val.value>(); @@ -82,7 +79,7 @@ void JsonAdapter::connectNotifiersRec(int notifySlot, QObject* obj, const QMetaO for (auto i = 0; i != len; i++) { auto* pobj = listVal.at(&listVal, i); - if (pobj) this->connectNotifiersRec(notifySlot, pobj, &JsonObject::staticMetaObject); + if (pobj) connectNotifiersRec(notifySlot, pobj, &JsonObject::staticMetaObject); } } } @@ -114,7 +111,7 @@ QJsonObject JsonAdapter::serializeRec(const QObject* obj, const QMetaObject* bas auto* pobj = val.view(); if (pobj) { - json.insert(prop.name(), this->serializeRec(pobj, &JsonObject::staticMetaObject)); + json.insert(prop.name(), serializeRec(pobj, &JsonObject::staticMetaObject)); } else { json.insert(prop.name(), QJsonValue::Null); } @@ -127,29 +124,20 @@ QJsonObject JsonAdapter::serializeRec(const QObject* obj, const QMetaObject* bas auto* pobj = listVal.at(&listVal, i); if (pobj) { - array.push_back(this->serializeRec(pobj, &JsonObject::staticMetaObject)); + array.push_back(serializeRec(pobj, &JsonObject::staticMetaObject)); } else { array.push_back(QJsonValue::Null); } } json.insert(prop.name(), array); + } else if (val.canConvert()) { + auto variant = val.value().toVariant(); + auto jv = QJsonValue::fromVariant(variant); + json.insert(prop.name(), jv); } else { - if (val.canConvert()) val = val.value().toVariant(); - - auto jsonVal = QJsonValue::fromVariant(val); - - if (jsonVal.isNull() && !val.isNull() && val.isValid()) { - if (val.canConvert()) { - val.convert(QMetaType::fromType()); - } else if (val.canConvert()) { - val.convert(QMetaType::fromType()); - } - - jsonVal = QJsonValue::fromVariant(val); - } - - json.insert(prop.name(), jsonVal); + auto jv = QJsonValue::fromVariant(val); + json.insert(prop.name(), jv); } } } @@ -166,16 +154,14 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM auto jval = json.value(prop.name()); if (prop.metaType() == QMetaType::fromType()) { - auto newVariant = jval.toVariant(); - auto oldValue = prop.read(obj); - auto oldVariant = - oldValue.canConvert() ? oldValue.value().toVariant() : oldValue; + auto variant = jval.toVariant(); + auto oldValue = prop.read(this).value(); // Calling prop.write with a new QJSValue will cause a property update // even if content is identical. - if (newVariant != oldVariant) { - auto jsValue = qmlEngine(this)->fromVariant(newVariant); - prop.write(obj, QVariant::fromValue(jsValue)); + if (jval.toVariant() != oldValue.toVariant()) { + auto jsValue = qmlEngine(this)->fromVariant(jval.toVariant()); + prop.write(this, QVariant::fromValue(jsValue)); } } else if (QMetaType::canView(prop.metaType(), QMetaType::fromType())) { // FIXME: This doesn't support creating descendants of JsonObject, as QMetaType.metaObject() @@ -192,8 +178,8 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM currentValue->setParent(this); this->createdObjects.push_back(currentValue); - } else if (this->oldCreatedObjects.removeOne(currentValue)) { - this->createdObjects.push_back(currentValue); + } else if (oldCreatedObjects.removeOne(currentValue)) { + createdObjects.push_back(currentValue); } this->deserializeRec(jval.toObject(), currentValue, &JsonObject::staticMetaObject); @@ -210,7 +196,7 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM QMetaType::fromType>() )) { - auto pval = prop.read(obj); + auto pval = prop.read(this); if (pval.canConvert>()) { auto lp = pval.value>(); @@ -226,8 +212,8 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM if (jsonValue.isObject()) { if (isNew) { currentValue = lp.at(&lp, i); - if (this->oldCreatedObjects.removeOne(currentValue)) { - this->createdObjects.push_back(currentValue); + if (oldCreatedObjects.removeOne(currentValue)) { + createdObjects.push_back(currentValue); } } else { // FIXME: should be the type inside the QQmlListProperty but how can we get that? @@ -261,35 +247,12 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM } } else { auto variant = jval.toVariant(); - auto convVariant = variant; - if (convVariant.convert(prop.metaType())) { - prop.write(obj, convVariant); + if (variant.convert(prop.metaType())) { + prop.write(obj, variant); } else { - auto pval = prop.read(obj); - if (variant.canConvert() && pval.canView()) { - auto targetv = QVariant(pval.metaType()); - auto target = targetv.view().metaContainer(); - auto valueType = target.valueMetaType(); - auto i = 0; - - for (QVariant item: variant.value()) { - if (item.convert(valueType)) { - target.addValueAtEnd(targetv.data(), item.constData()); - } else { - qmlWarning(this) << "Failed to deserialize list member " << i << " of property " - << prop.name() << ": expected " << valueType.name() << " but got " - << item.typeName(); - } - - ++i; - } - prop.write(obj, targetv); - } else { - qmlWarning(this) << "Failed to deserialize property " << prop.name() << ": expected " - << prop.metaType().name() << " but got " - << jval.toVariant().typeName(); - } + qmlWarning(this) << "Failed to deserialize property " << prop.name() << ": expected " + << prop.metaType().name() << " but got " << jval.toVariant().typeName(); } } } diff --git a/src/io/process.hpp b/src/io/process.hpp index 3c55745..ab8763e 100644 --- a/src/io/process.hpp +++ b/src/io/process.hpp @@ -102,7 +102,7 @@ class Process: public PostReloadHook { /// If the process is already running changing this property will affect the next /// started process. If the property has been changed after starting a process it will /// return the new value, not the one for the currently running process. - Q_PROPERTY(QVariantHash environment READ environment WRITE setEnvironment NOTIFY environmentChanged); + Q_PROPERTY(QHash environment READ environment WRITE setEnvironment NOTIFY environmentChanged); /// If the process's environment should be cleared prior to applying @@environment. /// Defaults to false. /// diff --git a/src/io/processcore.hpp b/src/io/processcore.hpp index 8d566c9..37ec409 100644 --- a/src/io/processcore.hpp +++ b/src/io/processcore.hpp @@ -13,7 +13,7 @@ namespace qs::io::process { class ProcessContext { Q_PROPERTY(QList command MEMBER command WRITE setCommand); - Q_PROPERTY(QVariantHash environment MEMBER environment WRITE setEnvironment); + Q_PROPERTY(QHash environment MEMBER environment WRITE setEnvironment); Q_PROPERTY(bool clearEnvironment MEMBER clearEnvironment WRITE setClearEnvironment); Q_PROPERTY(QString workingDirectory MEMBER workingDirectory WRITE setWorkingDirectory); Q_PROPERTY(bool unbindStdout MEMBER unbindStdout WRITE setUnbindStdout); diff --git a/src/ipc/ipc.cpp b/src/ipc/ipc.cpp index 4bfea4c..bf66801 100644 --- a/src/ipc/ipc.cpp +++ b/src/ipc/ipc.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include #include @@ -37,8 +36,7 @@ void IpcServer::start() { auto path = run->filePath("ipc.sock"); new IpcServer(path); } else { - qCCritical( - logIpc + qCCritical(logIpc ) << "Could not start IPC server as the instance runtime path could not be created."; } } @@ -62,7 +60,6 @@ IpcServerConnection::IpcServerConnection(QLocalSocket* socket, IpcServer* server void IpcServerConnection::onDisconnected() { qCInfo(logIpc) << "IPC connection disconnected" << this; - this->deleteLater(); } void IpcServerConnection::onReadyRead() { @@ -86,11 +83,6 @@ void IpcServerConnection::onReadyRead() { ); if (!this->stream.commitTransaction()) return; - - // async connections reparent - if (dynamic_cast(this->parent()) != nullptr) { - this->deleteLater(); - } } IpcClient::IpcClient(const QString& path) { @@ -128,9 +120,7 @@ int IpcClient::connect(const QString& id, const std::functionquit(); - else QCoreApplication::exit(0); + EngineGeneration::currentGeneration()->quit(); } } // namespace qs::ipc diff --git a/src/ipc/ipccommand.hpp b/src/ipc/ipccommand.hpp index 105ce1e..b221b46 100644 --- a/src/ipc/ipccommand.hpp +++ b/src/ipc/ipccommand.hpp @@ -16,7 +16,6 @@ using IpcCommand = std::variant< IpcKillCommand, qs::io::ipc::comm::QueryMetadataCommand, qs::io::ipc::comm::StringCallCommand, - qs::io::ipc::comm::SignalListenCommand, qs::io::ipc::comm::StringPropReadCommand>; } // namespace qs::ipc diff --git a/src/launch/command.cpp b/src/launch/command.cpp index 807eb24..8a9c6de 100644 --- a/src/launch/command.cpp +++ b/src/launch/command.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -12,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -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 { @@ -89,9 +89,9 @@ int locateConfigFile(CommandState& cmd, QString& path) { } if (!manifestPath.isEmpty()) { - qWarning() - << "Config manifests (manifest.conf) are deprecated and will be removed in a future " - "release."; + qWarning( + ) << "Config manifests (manifest.conf) are deprecated and will be removed in a future " + "release."; qWarning() << "Consider using symlinks to a subfolder of quickshell's XDG config dirs."; auto file = QFile(manifestPath); @@ -109,7 +109,7 @@ int locateConfigFile(CommandState& cmd, QString& path) { } if (split[0].trimmed() == *cmd.config.name) { - path = QDir(QFileInfo(file).absolutePath()).filePath(split[1].trimmed()); + path = QDir(QFileInfo(file).canonicalPath()).filePath(split[1].trimmed()); break; } } @@ -129,8 +129,7 @@ int locateConfigFile(CommandState& cmd, QString& path) { if (path.isEmpty()) { if (name == "default") { - qCCritical( - logBare + qCCritical(logBare ) << "Could not find \"default\" config directory or shell.qml in any valid config path."; } else { qCCritical(logBare) << "Could not find" << name @@ -140,7 +139,8 @@ int locateConfigFile(CommandState& cmd, QString& path) { return -1; } - goto rpath; + path = QFileInfo(path).canonicalFilePath(); + return 0; } } @@ -153,8 +153,7 @@ int locateConfigFile(CommandState& cmd, QString& path) { return -1; } -rpath: - path = QFileInfo(path).absoluteFilePath(); + path = QFileInfo(path).canonicalFilePath(); return 0; } @@ -179,8 +178,7 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb } } else if (!cmd.instance.id->isEmpty()) { path = basePath->filePath("by-pid"); - auto [liveInstances, deadInstances] = - QsPaths::collectInstances(path, cmd.config.anyDisplay ? "" : getDisplayConnection()); + auto [liveInstances, deadInstances] = QsPaths::collectInstances(path); liveInstances.removeIf([&](const InstanceLockInfo& info) { return !info.instance.instanceId.startsWith(*cmd.instance.id); @@ -230,8 +228,7 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb path = QDir(basePath->filePath("by-path")).filePath(pathId); - auto [liveInstances, deadInstances] = - QsPaths::collectInstances(path, cmd.config.anyDisplay ? "" : getDisplayConnection()); + auto [liveInstances, deadInstances] = QsPaths::collectInstances(path); auto instances = liveInstances; if (instances.isEmpty() && deadFallback) { @@ -314,10 +311,7 @@ int listInstances(CommandState& cmd) { path = QDir(basePath->filePath("by-path")).filePath(pathId); } - auto [liveInstances, deadInstances] = QsPaths::collectInstances( - path, - cmd.config.anyDisplay || cmd.instance.all ? "" : getDisplayConnection() - ); + auto [liveInstances, deadInstances] = QsPaths::collectInstances(path); sortInstances(liveInstances, cmd.config.newest); @@ -379,7 +373,6 @@ int listInstances(CommandState& cmd) { << " Process ID: " << instance.instance.pid << '\n' << " Shell ID: " << instance.instance.shellId << '\n' << " Config path: " << instance.instance.configPath << '\n' - << " Display connection: " << instance.instance.display << '\n' << " Launch time: " << launchTimeStr << (isDead ? "" : " (running for " + runtimeStr + ")") << '\n' << (gray ? "\033[0m" : ""); @@ -411,10 +404,6 @@ int ipcCommand(CommandState& cmd) { return qs::io::ipc::comm::queryMetadata(&client, *cmd.ipc.target, *cmd.ipc.name); } else if (*cmd.ipc.getprop) { return qs::io::ipc::comm::getProperty(&client, *cmd.ipc.target, *cmd.ipc.name); - } else if (*cmd.ipc.wait) { - return qs::io::ipc::comm::listenToSignal(&client, *cmd.ipc.target, *cmd.ipc.name, true); - } else if (*cmd.ipc.listen) { - return qs::io::ipc::comm::listenToSignal(&client, *cmd.ipc.target, *cmd.ipc.name, false); } else { QVector arguments; for (auto& arg: cmd.ipc.arguments) { @@ -464,7 +453,7 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) { QTextStream(stdout) << "\033[31mCOMPATIBILITY WARNING: Quickshell was built against Qt " << QT_VERSION_STR << " but the system has updated to Qt " << qVersion() << " without rebuilding the package. This is likely to cause crashes, so " - "you must rebuild the quickshell package.\n\033[0m"; + "you must rebuild the quickshell package.\n"; return 1; } @@ -519,10 +508,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 0.2.0, 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); @@ -546,18 +545,4 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) { return 0; } -QString getDisplayConnection() { - auto platform = qEnvironmentVariable("QT_QPA_PLATFORM"); - auto wlDisplay = qEnvironmentVariable("WAYLAND_DISPLAY"); - auto xDisplay = qEnvironmentVariable("DISPLAY"); - - if (platform == "wayland" || (platform.isEmpty() && !wlDisplay.isEmpty())) { - return "wayland," + wlDisplay; - } else if (platform == "xcb" || (platform.isEmpty() && !xDisplay.isEmpty())) { - return "x11," + xDisplay; - } else { - return "unk," + QGuiApplication::platformName(); - } -} - } // namespace qs::launch diff --git a/src/launch/launch.cpp b/src/launch/launch.cpp index dcdefa7..fd6a0af 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 @@ -76,11 +76,8 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio bool useSystemStyle = false; QString iconTheme = qEnvironmentVariable("QS_ICON_THEME"); QHash envOverrides; - QString appId = qEnvironmentVariable("QS_APP_ID"); - bool dropExpensiveFonts = false; QString dataDir; QString stateDir; - QString cacheDir; } pragmas; auto stream = QTextStream(&file); @@ -93,7 +90,6 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio else if (pragma == "NativeTextRendering") pragmas.nativeTextRendering = true; else if (pragma == "IgnoreSystemSettings") pragmas.desktopSettingsAware = false; else if (pragma == "RespectSystemStyle") pragmas.useSystemStyle = true; - else if (pragma == "DropExpensiveFonts") pragmas.dropExpensiveFonts = true; else if (pragma.startsWith("IconTheme ")) pragmas.iconTheme = pragma.sliced(10); else if (pragma.startsWith("Env ")) { auto envPragma = pragma.sliced(4); @@ -107,18 +103,15 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio auto var = envPragma.sliced(0, splitIdx).trimmed(); auto val = envPragma.sliced(splitIdx + 1).trimmed(); pragmas.envOverrides.insert(var, val); - } else if (pragma.startsWith("AppId ")) { - pragmas.appId = pragma.sliced(6).trimmed(); } else if (pragma.startsWith("ShellId ")) { shellId = pragma.sliced(8).trimmed(); } else if (pragma.startsWith("DataDir ")) { pragmas.dataDir = pragma.sliced(8).trimmed(); } else if (pragma.startsWith("StateDir ")) { pragmas.stateDir = pragma.sliced(9).trimmed(); - } else if (pragma.startsWith("CacheDir ")) { - pragmas.cacheDir = pragma.sliced(9).trimmed(); } else { - qWarning() << "Unrecognized pragma" << pragma; + qCritical() << "Unrecognized pragma" << pragma; + return -1; } } else if (line.startsWith("import")) break; } @@ -132,26 +125,21 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio qInfo() << "Shell ID:" << shellId << "Path ID" << pathId; auto launchTime = qs::Common::LAUNCH_TIME.toSecsSinceEpoch(); - auto appId = pragmas.appId.isEmpty() ? QStringLiteral("org.quickshell") : pragmas.appId; - InstanceInfo::CURRENT = InstanceInfo { .instanceId = base36Encode(getpid()) + base36Encode(launchTime), .configPath = args.configPath, .shellId = shellId, - .appId = appId, .launchTime = qs::Common::LAUNCH_TIME, .pid = getpid(), - .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, @@ -162,7 +150,7 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio } #endif - QsPaths::init(shellId, pathId, pragmas.dataDir, pragmas.stateDir, pragmas.cacheDir); + QsPaths::init(shellId, pathId, pragmas.dataDir, pragmas.stateDir); QsPaths::instance()->linkRunDir(); QsPaths::instance()->linkPathDir(); LogManager::initFs(); @@ -178,48 +166,6 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio qputenv(var.toUtf8(), val.toUtf8()); } - pragmas.dropExpensiveFonts |= qEnvironmentVariableIntValue("QS_DROP_EXPENSIVE_FONTS") == 1; - - if (pragmas.dropExpensiveFonts) { - if (auto* runDir = QsPaths::instance()->instanceRunDir()) { - auto baseConfigPath = qEnvironmentVariable("FONTCONFIG_FILE"); - if (baseConfigPath.isEmpty()) baseConfigPath = "/etc/fonts/fonts.conf"; - - auto filterPath = runDir->filePath("fonts-override.conf"); - auto filterFile = QFile(filterPath); - if (filterFile.open(QFile::WriteOnly | QFile::Truncate | QFile::Text)) { - auto filterTemplate = QStringLiteral(R"( - - - %1 - - - - - woff - - - - - woff2 - - - - - -)"); - - QTextStream(&filterFile) << filterTemplate.arg(baseConfigPath); - filterFile.close(); - qputenv("FONTCONFIG_FILE", filterPath.toUtf8()); - } else { - qCritical() << "Could not write fontconfig filter to" << filterPath; - } - } else { - qCritical() << "Could not create fontconfig filter: instance run directory unavailable"; - } - } - // The qml engine currently refuses to cache non file (qsintercept) paths. // if (auto* cacheDir = QsPaths::instance()->cacheDir()) { @@ -280,7 +226,7 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio app = new QGuiApplication(qArgC, argv); } - QGuiApplication::setDesktopFileName(appId); + QGuiApplication::setDesktopFileName("org.quickshell"); if (args.debugPort != -1) { QQmlDebuggingEnabler::enableDebugging(true); diff --git a/src/launch/launch_p.hpp b/src/launch/launch_p.hpp index f666e7a..7b8fca6 100644 --- a/src/launch/launch_p.hpp +++ b/src/launch/launch_p.hpp @@ -50,7 +50,6 @@ struct CommandState { QStringOption manifest; QStringOption name; bool newest = false; - bool anyDisplay = false; } config; struct { @@ -74,8 +73,6 @@ struct CommandState { CLI::App* show = nullptr; CLI::App* call = nullptr; CLI::App* getprop = nullptr; - CLI::App* wait = nullptr; - CLI::App* listen = nullptr; bool showOld = false; QStringOption target; QStringOption name; @@ -109,8 +106,6 @@ void exitDaemon(int code); int parseCommand(int argc, char** argv, CommandState& state); int runCommand(int argc, char** argv, QCoreApplication* coreApplication); -QString getDisplayConnection(); - int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplication); } // namespace qs::launch diff --git a/src/launch/main.cpp b/src/launch/main.cpp index efd6628..2bcbebd 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,17 +25,14 @@ 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()) { auto lastInfoFd = lastInfoFdStr.toInt(); QFile file; - if (!file.open(lastInfoFd, QFile::ReadOnly, QFile::AutoCloseHandle)) { - qFatal() << "Failed to open crash info fd. Cannot restart."; - } - + file.open(lastInfoFd, QFile::ReadOnly, QFile::AutoCloseHandle); file.seek(0); auto ds = QDataStream(&file); @@ -84,35 +81,27 @@ void exitDaemon(int code) { close(DAEMON_PIPE); - auto fd = open("/dev/null", O_RDWR); - if (fd == -1) { - qCritical().nospace() << "Failed to open /dev/null for daemon stdio" << errno << ": " - << qt_error_string(); - return; + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + + if (open("/dev/null", O_RDONLY) != STDIN_FILENO) { // NOLINT + qFatal() << "Failed to open /dev/null on stdin"; } - if (dup2(fd, STDIN_FILENO) != STDIN_FILENO) { // NOLINT - qCritical().nospace() << "Failed to set daemon stdin to /dev/null" << errno << ": " - << qt_error_string(); + if (open("/dev/null", O_WRONLY) != STDOUT_FILENO) { // NOLINT + qFatal() << "Failed to open /dev/null on stdout"; } - if (dup2(fd, STDOUT_FILENO) != STDOUT_FILENO) { // NOLINT - qCritical().nospace() << "Failed to set daemon stdout to /dev/null" << errno << ": " - << qt_error_string(); + if (open("/dev/null", O_WRONLY) != STDERR_FILENO) { // NOLINT + qFatal() << "Failed to open /dev/null on stderr"; } - - if (dup2(fd, STDERR_FILENO) != STDERR_FILENO) { // NOLINT - qCritical().nospace() << "Failed to set daemon stderr to /dev/null" << errno << ": " - << qt_error_string(); - } - - close(fd); } int main(int argc, char** argv) { QCoreApplication::setApplicationName("quickshell"); -#if CRASH_HANDLER +#if CRASH_REPORTER qsCheckCrash(argc, argv); #endif diff --git a/src/launch/parsecommand.cpp b/src/launch/parsecommand.cpp index fc43b6b..fc16086 100644 --- a/src/launch/parsecommand.cpp +++ b/src/launch/parsecommand.cpp @@ -1,7 +1,6 @@ #include #include #include -#include #include #include // NOLINT: Need to include this for impls of some CLI11 classes @@ -17,7 +16,7 @@ int parseCommand(int argc, char** argv, CommandState& state) { .argv = argv, }; - auto addConfigSelection = [&](CLI::App* cmd, bool filtering = false) { + auto addConfigSelection = [&](CLI::App* cmd, bool withNewestOption = false) { auto* group = cmd->add_option_group("Config Selection") ->description( @@ -44,23 +43,15 @@ int parseCommand(int argc, char** argv, CommandState& state) { ->excludes(path); group->add_option("-m,--manifest", state.config.manifest) - ->description( - "[DEPRECATED] Path to a quickshell manifest.\n" - "If a manifest is specified, configs named by -c will point to its entries.\n" - "Defaults to $XDG_CONFIG_HOME/quickshell/manifest.conf" - ) + ->description("[DEPRECATED] Path to a quickshell manifest.\n" + "If a manifest is specified, configs named by -c will point to its entries.\n" + "Defaults to $XDG_CONFIG_HOME/quickshell/manifest.conf") ->envname("QS_MANIFEST") ->excludes(path); - if (filtering) { + if (withNewestOption) { group->add_flag("-n,--newest", state.config.newest) ->description("Operate on the most recently launched instance instead of the oldest"); - - group->add_flag("--any-display", state.config.anyDisplay) - ->description( - "If passed, instances will not be filtered by the display connection they " - "were launched on." - ); } return group; @@ -84,11 +75,9 @@ int parseCommand(int argc, char** argv, CommandState& state) { auto* group = noGroup ? cmd : cmd->add_option_group(noDisplay ? "" : "Logging"); group->add_flag("--no-color", state.log.noColor) - ->description( - "Disables colored logging.\n" - "Colored logging can also be disabled by specifying a non empty value " - "for the NO_COLOR environment variable." - ); + ->description("Disables colored logging.\n" + "Colored logging can also be disabled by specifying a non empty value " + "for the NO_COLOR environment variable."); group->add_flag("--log-times", state.log.timestamp) ->description("Log timestamps with each message."); @@ -97,11 +86,9 @@ int parseCommand(int argc, char** argv, CommandState& state) { ->description("Log rules to apply, in the format of QT_LOGGING_RULES."); group->add_flag("-v,--verbose", [&](size_t count) { state.log.verbosity = count; }) - ->description( - "Increases log verbosity.\n" - "-v will show INFO level internal logs.\n" - "-vv will show DEBUG level internal logs." - ); + ->description("Increases log verbosity.\n" + "-v will show INFO level internal logs.\n" + "-vv will show DEBUG level internal logs."); auto* hgroup = cmd->add_option_group(""); hgroup->add_flag("--no-detailed-logs", state.log.sparse); @@ -111,11 +98,9 @@ int parseCommand(int argc, char** argv, CommandState& state) { auto* group = cmd->add_option_group("Instance Selection"); group->add_option("-i,--id", state.instance.id) - ->description( - "The instance id to operate on.\n" - "You may also use a substring the id as long as it is unique, " - "for example \"abc\" will select \"abcdefg\"." - ); + ->description("The instance id to operate on.\n" + "You may also use a substring the id as long as it is unique, " + "for example \"abc\" will select \"abcdefg\"."); group->add_option("--pid", state.instance.pid) ->description("The process id of the instance to operate on."); @@ -172,11 +157,9 @@ int parseCommand(int argc, char** argv, CommandState& state) { auto* sub = cli->add_subcommand("list", "List running quickshell instances."); auto* all = sub->add_flag("-a,--all", state.instance.all) - ->description( - "List all instances.\n" - "If unspecified, only instances of" - "the selected config will be listed." - ); + ->description("List all instances.\n" + "If unspecified, only instances of" + "the selected config will be listed."); sub->add_flag("-j,--json", state.output.json, "Output the list as a json."); @@ -227,16 +210,6 @@ int parseCommand(int argc, char** argv, CommandState& state) { ->allow_extra_args(); } - auto signalCmd = [&](std::string cmd, std::string desc) { - auto* scmd = sub->add_subcommand(std::move(cmd), std::move(desc)); - scmd->add_option("target", state.ipc.target, "The target to listen on."); - scmd->add_option("signal", state.ipc.name, "The signal to listen for."); - return scmd; - }; - - state.ipc.wait = signalCmd("wait", "Wait for one IpcHandler signal."); - state.ipc.listen = signalCmd("listen", "Listen for IpcHandler signals."); - { auto* prop = sub->add_subcommand("prop", "Manipulate IpcHandler properties.")->require_subcommand(); @@ -262,10 +235,8 @@ int parseCommand(int argc, char** argv, CommandState& state) { ->allow_extra_args(); sub->add_flag("-s,--show", state.ipc.showOld) - ->description( - "Print information about a function or target if given, or all available " - "targets if not." - ); + ->description("Print information about a function or target if given, or all available " + "targets if not."); auto* instance = addInstanceSelection(sub); addConfigSelection(sub, true)->excludes(instance); diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt deleted file mode 100644 index 03ef86a..0000000 --- a/src/network/CMakeLists.txt +++ /dev/null @@ -1,25 +0,0 @@ -add_subdirectory(nm) - -qt_add_library(quickshell-network STATIC - network.cpp - device.cpp - wifi.cpp - enums.cpp -) - -target_include_directories(quickshell-network PRIVATE - ${CMAKE_CURRENT_BINARY_DIR} -) - -qt_add_qml_module(quickshell-network - URI Quickshell.Networking - VERSION 0.1 - DEPENDENCIES QtQml -) - -qs_add_module_deps_light(quickshell-network Quickshell) -install_qml_module(quickshell-network) -target_link_libraries(quickshell-network PRIVATE quickshell-network-nm Qt::Qml Qt::DBus) -qs_add_link_dependencies(quickshell-network quickshell-dbus) -target_link_libraries(quickshell PRIVATE quickshell-networkplugin) -qs_module_pch(quickshell-network SET dbus) diff --git a/src/network/device.cpp b/src/network/device.cpp deleted file mode 100644 index 5679e8d..0000000 --- a/src/network/device.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include "device.hpp" - -#include -#include -#include -#include -#include -#include - -#include "../core/logcat.hpp" -#include "enums.hpp" - -namespace qs::network { - -namespace { -QS_LOGGING_CATEGORY(logNetworkDevice, "quickshell.network.device", QtWarningMsg); -} // namespace - -NetworkDevice::NetworkDevice(DeviceType::Enum type, QObject* parent): QObject(parent), mType(type) { - this->bindableConnected().setBinding([this]() { - return this->bState == ConnectionState::Connected; - }); -}; - -void NetworkDevice::setAutoconnect(bool autoconnect) { - if (this->bAutoconnect == autoconnect) return; - emit this->requestSetAutoconnect(autoconnect); -} - -void NetworkDevice::setNmManaged(bool managed) { - if (this->bNmManaged == managed) return; - emit this->requestSetNmManaged(managed); -} - -void NetworkDevice::disconnect() { - if (this->bState == ConnectionState::Disconnected) { - qCCritical(logNetworkDevice) << "Device" << this << "is already disconnected"; - return; - } - if (this->bState == ConnectionState::Disconnecting) { - qCCritical(logNetworkDevice) << "Device" << this << "is already disconnecting"; - return; - } - qCDebug(logNetworkDevice) << "Disconnecting from device" << this; - this->requestDisconnect(); -} - -} // namespace qs::network diff --git a/src/network/device.hpp b/src/network/device.hpp deleted file mode 100644 index 8d914a1..0000000 --- a/src/network/device.hpp +++ /dev/null @@ -1,84 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "../core/doc.hpp" -#include "enums.hpp" - -namespace qs::network { - -///! A network device. -/// The @@type property may be used to determine if this device is a @@WifiDevice. -class NetworkDevice: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_UNCREATABLE("Devices can only be acquired through Network"); - // clang-format off - /// The device type. - /// - /// When the device type is `Wifi`, the device object is a @@WifiDevice which exposes wifi network - /// connection and scanning. - Q_PROPERTY(DeviceType::Enum type READ type CONSTANT); - /// The name of the device's control interface. - Q_PROPERTY(QString name READ name NOTIFY nameChanged BINDABLE bindableName); - /// The hardware address of the device in the XX:XX:XX:XX:XX:XX format. - Q_PROPERTY(QString address READ default NOTIFY addressChanged BINDABLE bindableAddress); - /// True if the device is connected. - Q_PROPERTY(bool connected READ default NOTIFY connectedChanged BINDABLE bindableConnected); - /// Connection state of the device. - Q_PROPERTY(qs::network::ConnectionState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState); - /// True if the device is managed by NetworkManager. - /// - /// > [!WARNING] Only valid for the NetworkManager backend. - Q_PROPERTY(bool nmManaged READ nmManaged WRITE setNmManaged NOTIFY nmManagedChanged) - /// True if the device is allowed to autoconnect to a network. - Q_PROPERTY(bool autoconnect READ autoconnect WRITE setAutoconnect NOTIFY autoconnectChanged); - // clang-format on - -public: - explicit NetworkDevice(DeviceType::Enum type, QObject* parent = nullptr); - - /// Disconnects the device and prevents it from automatically activating further connections. - Q_INVOKABLE void disconnect(); - - [[nodiscard]] DeviceType::Enum type() const { return this->mType; } - QBindable bindableName() { return &this->bName; } - [[nodiscard]] QString name() const { return this->bName; } - QBindable bindableAddress() { return &this->bAddress; } - QBindable bindableConnected() { return &this->bConnected; } - QBindable bindableState() { return &this->bState; } - QBindable bindableNmManaged() { return &this->bNmManaged; } - [[nodiscard]] bool nmManaged() { return this->bNmManaged; } - void setNmManaged(bool managed); - QBindable bindableAutoconnect() { return &this->bAutoconnect; } - [[nodiscard]] bool autoconnect() { return this->bAutoconnect; } - void setAutoconnect(bool autoconnect); - -signals: - QSDOC_HIDE void requestDisconnect(); - QSDOC_HIDE void requestSetAutoconnect(bool autoconnect); - QSDOC_HIDE void requestSetNmManaged(bool managed); - void nameChanged(); - void addressChanged(); - void connectedChanged(); - void stateChanged(); - void nmManagedChanged(); - void autoconnectChanged(); - -private: - DeviceType::Enum mType; - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, QString, bName, &NetworkDevice::nameChanged); - Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, QString, bAddress, &NetworkDevice::addressChanged); - Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, bool, bConnected, &NetworkDevice::connectedChanged); - Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, ConnectionState::Enum, bState, &NetworkDevice::stateChanged); - Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, bool, bNmManaged, &NetworkDevice::nmManagedChanged); - Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, bool, bAutoconnect, &NetworkDevice::autoconnectChanged); - // clang-format on -}; - -} // namespace qs::network diff --git a/src/network/enums.cpp b/src/network/enums.cpp deleted file mode 100644 index 2cf36c1..0000000 --- a/src/network/enums.cpp +++ /dev/null @@ -1,86 +0,0 @@ -#include "enums.hpp" - -#include - -namespace qs::network { - -QString NetworkConnectivity::toString(NetworkConnectivity::Enum conn) { - switch (conn) { - case Unknown: return QStringLiteral("Unknown"); - case None: return QStringLiteral("Not connected to a network"); - case Portal: return QStringLiteral("Connection intercepted by a captive portal"); - case Limited: return QStringLiteral("Partial internet connectivity"); - case Full: return QStringLiteral("Full internet connectivity"); - default: return QStringLiteral("Unknown"); - } -} - -QString NetworkBackendType::toString(NetworkBackendType::Enum type) { - switch (type) { - case NetworkBackendType::None: return "None"; - case NetworkBackendType::NetworkManager: return "NetworkManager"; - default: return "Unknown"; - } -} - -QString ConnectionState::toString(ConnectionState::Enum state) { - switch (state) { - case Unknown: return QStringLiteral("Unknown"); - case Connecting: return QStringLiteral("Connecting"); - case Connected: return QStringLiteral("Connected"); - case Disconnecting: return QStringLiteral("Disconnecting"); - case Disconnected: return QStringLiteral("Disconnected"); - default: return QStringLiteral("Unknown"); - } -} - -QString ConnectionFailReason::toString(ConnectionFailReason::Enum reason) { - switch (reason) { - case Unknown: return QStringLiteral("Unknown"); - case NoSecrets: return QStringLiteral("Secrets were required but not provided"); - case WifiClientDisconnected: return QStringLiteral("Wi-Fi supplicant diconnected"); - case WifiClientFailed: return QStringLiteral("Wi-Fi supplicant failed"); - case WifiAuthTimeout: return QStringLiteral("Wi-Fi connection took too long to authenticate"); - case WifiNetworkLost: return QStringLiteral("Wi-Fi network could not be found"); - default: return QStringLiteral("Unknown"); - } -} - -QString DeviceType::toString(DeviceType::Enum type) { - switch (type) { - case None: return QStringLiteral("None"); - case Wifi: return QStringLiteral("Wifi"); - default: return QStringLiteral("Unknown"); - } -} - -QString WifiSecurityType::toString(WifiSecurityType::Enum type) { - switch (type) { - case Unknown: return QStringLiteral("Unknown"); - case Wpa3SuiteB192: return QStringLiteral("WPA3 Suite B 192-bit"); - case Sae: return QStringLiteral("WPA3"); - case Wpa2Eap: return QStringLiteral("WPA2 Enterprise"); - case Wpa2Psk: return QStringLiteral("WPA2"); - case WpaEap: return QStringLiteral("WPA Enterprise"); - case WpaPsk: return QStringLiteral("WPA"); - case StaticWep: return QStringLiteral("WEP"); - case DynamicWep: return QStringLiteral("Dynamic WEP"); - case Leap: return QStringLiteral("LEAP"); - case Owe: return QStringLiteral("OWE"); - case Open: return QStringLiteral("Open"); - default: return QStringLiteral("Unknown"); - } -} - -QString WifiDeviceMode::toString(WifiDeviceMode::Enum mode) { - switch (mode) { - case Unknown: return QStringLiteral("Unknown"); - case AdHoc: return QStringLiteral("Ad-Hoc"); - case Station: return QStringLiteral("Station"); - case AccessPoint: return QStringLiteral("Access Point"); - case Mesh: return QStringLiteral("Mesh"); - default: return QStringLiteral("Unknown"); - }; -} - -} // namespace qs::network diff --git a/src/network/enums.hpp b/src/network/enums.hpp deleted file mode 100644 index 49c28ce..0000000 --- a/src/network/enums.hpp +++ /dev/null @@ -1,154 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace qs::network { - -///! The degree to which the host can reach the internet. -class NetworkConnectivity: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - /// Network connectivity is unknown. This means the connectivity checks are disabled or have not run yet. - Unknown = 0, - /// The host is not connected to any network. - None = 1, - /// The internet connection is hijacked by a captive portal gateway. - /// This indicates the shell should open a sandboxed web browser window for the purpose of authenticating to a gateway. - Portal = 2, - /// The host is connected to a network but does not appear to be able to reach the full internet. - Limited = 3, - /// The host is connected to a network and appears to be able to reach the full internet. - Full = 4, - }; - Q_ENUM(Enum); - Q_INVOKABLE static QString toString(NetworkConnectivity::Enum conn); -}; - -///! The backend supplying the Network service. -class NetworkBackendType: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - None = 0, - NetworkManager = 1, - }; - Q_ENUM(Enum); - Q_INVOKABLE static QString toString(NetworkBackendType::Enum type); -}; - -///! The connection state of a device or network. -class ConnectionState: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - Unknown = 0, - Connecting = 1, - Connected = 2, - Disconnecting = 3, - Disconnected = 4, - }; - Q_ENUM(Enum); - Q_INVOKABLE static QString toString(ConnectionState::Enum state); -}; - -///! The reason a connection failed. -class ConnectionFailReason: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - /// The connection failed for an unknown reason. - Unknown = 0, - /// Secrets were required, but not provided. - NoSecrets = 1, - /// The Wi-Fi supplicant disconnected. - WifiClientDisconnected = 2, - /// The Wi-Fi supplicant failed. - WifiClientFailed = 3, - /// The Wi-Fi connection took too long to authenticate. - WifiAuthTimeout = 4, - /// The Wi-Fi network could not be found. - WifiNetworkLost = 5, - }; - Q_ENUM(Enum); - Q_INVOKABLE static QString toString(ConnectionFailReason::Enum reason); -}; - -///! Type of a @@NetworkDevice. -class DeviceType: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - None = 0, - Wifi = 1, - }; - Q_ENUM(Enum); - Q_INVOKABLE static QString toString(DeviceType::Enum type); -}; - -///! The security type of a @@WifiNetwork. -class WifiSecurityType: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - Wpa3SuiteB192 = 0, - Sae = 1, - Wpa2Eap = 2, - Wpa2Psk = 3, - WpaEap = 4, - WpaPsk = 5, - StaticWep = 6, - DynamicWep = 7, - Leap = 8, - Owe = 9, - Open = 10, - Unknown = 11, - }; - Q_ENUM(Enum); - Q_INVOKABLE static QString toString(WifiSecurityType::Enum type); -}; - -///! The 802.11 mode of a @@WifiDevice. -class WifiDeviceMode: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - /// The device is part of an Ad-Hoc network without a central access point. - AdHoc = 0, - /// The device is a station that can connect to networks. - Station = 1, - /// The device is a local hotspot/access point. - AccessPoint = 2, - /// The device is an 802.11s mesh point. - Mesh = 3, - /// The device mode is unknown. - Unknown = 4, - }; - Q_ENUM(Enum); - Q_INVOKABLE static QString toString(WifiDeviceMode::Enum mode); -}; - -} // namespace qs::network diff --git a/src/network/module.md b/src/network/module.md deleted file mode 100644 index 91ff2f1..0000000 --- a/src/network/module.md +++ /dev/null @@ -1,15 +0,0 @@ -name = "Quickshell.Networking" -description = "Network API" -headers = [ - "network.hpp", - "device.hpp", - "wifi.hpp", - "enums.hpp", - "nm/settings.hpp", -] ------ -This module exposes Network management APIs provided by a supported network backend. -For now, the only backend available is the NetworkManager DBus interface. -Both DBus and NetworkManager must be running to use it. - -See the @@Quickshell.Networking.Networking singleton. diff --git a/src/network/network.cpp b/src/network/network.cpp deleted file mode 100644 index e66ffa6..0000000 --- a/src/network/network.cpp +++ /dev/null @@ -1,131 +0,0 @@ -#include "network.hpp" -#include - -#include -#include -#include -#include -#include -#include - -#include "../core/logcat.hpp" -#include "device.hpp" -#include "enums.hpp" -#include "nm/backend.hpp" -#include "nm/settings.hpp" - -namespace qs::network { - -namespace { -QS_LOGGING_CATEGORY(logNetwork, "quickshell.network", QtWarningMsg); -} // namespace - -Networking::Networking(QObject* parent): QObject(parent) { - // Try to create the NetworkManager backend and bind to it. - auto* nm = new NetworkManager(this); - if (nm->isAvailable()) { - // clang-format off - QObject::connect(nm, &NetworkManager::deviceAdded, this, &Networking::deviceAdded); - QObject::connect(nm, &NetworkManager::deviceRemoved, this, &Networking::deviceRemoved); - QObject::connect(this, &Networking::requestSetWifiEnabled, nm, &NetworkManager::setWifiEnabled); - QObject::connect(this, &Networking::requestSetConnectivityCheckEnabled, nm, &NetworkManager::setConnectivityCheckEnabled); - QObject::connect(this, &Networking::requestCheckConnectivity, nm, &NetworkManager::checkConnectivity); - this->bindableWifiEnabled().setBinding([nm]() { return nm->wifiEnabled(); }); - this->bindableWifiHardwareEnabled().setBinding([nm]() { return nm->wifiHardwareEnabled(); }); - this->bindableCanCheckConnectivity().setBinding([nm]() { return nm->connectivityCheckAvailable(); }); - this->bindableConnectivityCheckEnabled().setBinding([nm]() { return nm->connectivityCheckEnabled(); }); - this->bindableConnectivity().setBinding([nm]() { return static_cast(nm->connectivity()); }); - // clang-format on - - this->mBackend = nm; - this->mBackendType = NetworkBackendType::NetworkManager; - return; - } else { - delete nm; - } - qCCritical(logNetwork) << "Network will not work. Could not find an available backend."; -} - -Networking* Networking::instance() { - static Networking* instance = new Networking(); // NOLINT - return instance; -} - -void Networking::deviceAdded(NetworkDevice* dev) { this->mDevices.insertObject(dev); } -void Networking::deviceRemoved(NetworkDevice* dev) { this->mDevices.removeObject(dev); } - -void Networking::checkConnectivity() { - if (!this->bConnectivityCheckEnabled || !this->bCanCheckConnectivity) return; - emit this->requestCheckConnectivity(); -} - -void Networking::setWifiEnabled(bool enabled) { - if (this->bWifiEnabled == enabled) return; - emit this->requestSetWifiEnabled(enabled); -} - -void Networking::setConnectivityCheckEnabled(bool enabled) { - if (this->bConnectivityCheckEnabled == enabled) return; - emit this->requestSetConnectivityCheckEnabled(enabled); -} - -NetworkingQml::NetworkingQml(QObject* parent): QObject(parent) { - // clang-format off - QObject::connect(Networking::instance(), &Networking::wifiEnabledChanged, this, &NetworkingQml::wifiEnabledChanged); - QObject::connect(Networking::instance(), &Networking::wifiHardwareEnabledChanged, this, &NetworkingQml::wifiHardwareEnabledChanged); - QObject::connect(Networking::instance(), &Networking::canCheckConnectivityChanged, this, &NetworkingQml::canCheckConnectivityChanged); - QObject::connect(Networking::instance(), &Networking::connectivityCheckEnabledChanged, this, &NetworkingQml::connectivityCheckEnabledChanged); - QObject::connect(Networking::instance(), &Networking::connectivityChanged, this, &NetworkingQml::connectivityChanged); - // clang-format on -} - -void NetworkingQml::checkConnectivity() { Networking::instance()->checkConnectivity(); } - -Network::Network(QString name, QObject* parent): QObject(parent), mName(std::move(name)) { - this->bStateChanging.setBinding([this] { - auto state = this->bState.value(); - return state == ConnectionState::Connecting || state == ConnectionState::Disconnecting; - }); -}; - -void Network::connect() { - if (this->bConnected) { - qCCritical(logNetwork) << this << "is already connected."; - return; - } - this->requestConnect(); -} - -void Network::connectWithSettings(NMSettings* settings) { - if (this->bConnected) { - qCCritical(logNetwork) << this << "is already connected."; - return; - } - if (this->bNmSettings.value().indexOf(settings) == -1) return; - this->requestConnectWithSettings(settings); -} - -void Network::disconnect() { - if (!this->bConnected) { - qCCritical(logNetwork) << this << "is not currently connected"; - return; - } - this->requestDisconnect(); -} - -void Network::forget() { this->requestForget(); } - -void Network::settingsAdded(NMSettings* settings) { - auto list = this->bNmSettings.value(); - if (list.contains(settings)) return; - list.append(settings); - this->bNmSettings = list; -} - -void Network::settingsRemoved(NMSettings* settings) { - auto list = this->bNmSettings.value(); - list.removeOne(settings); - this->bNmSettings = list; -} - -} // namespace qs::network diff --git a/src/network/network.hpp b/src/network/network.hpp deleted file mode 100644 index f7734a2..0000000 --- a/src/network/network.hpp +++ /dev/null @@ -1,233 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "../core/doc.hpp" -#include "../core/model.hpp" -#include "device.hpp" -#include "enums.hpp" -#include "nm/settings.hpp" - -namespace qs::network { - -class NetworkBackend: public QObject { - Q_OBJECT; - -public: - [[nodiscard]] virtual bool isAvailable() const = 0; - -protected: - explicit NetworkBackend(QObject* parent = nullptr): QObject(parent) {}; -}; - -class Networking: public QObject { - Q_OBJECT; - -public: - static Networking* instance(); - - void checkConnectivity(); - - [[nodiscard]] ObjectModel* devices() { return &this->mDevices; } - [[nodiscard]] NetworkBackendType::Enum backend() const { return this->mBackendType; } - QBindable bindableWifiEnabled() { return &this->bWifiEnabled; } - [[nodiscard]] bool wifiEnabled() const { return this->bWifiEnabled; } - void setWifiEnabled(bool enabled); - QBindable bindableWifiHardwareEnabled() { return &this->bWifiHardwareEnabled; } - QBindable bindableCanCheckConnectivity() { return &this->bCanCheckConnectivity; } - QBindable bindableConnectivityCheckEnabled() { return &this->bConnectivityCheckEnabled; } - [[nodiscard]] bool connectivityCheckEnabled() const { return this->bConnectivityCheckEnabled; } - void setConnectivityCheckEnabled(bool enabled); - QBindable bindableConnectivity() { return &this->bConnectivity; } - -signals: - void requestSetWifiEnabled(bool enabled); - void requestSetConnectivityCheckEnabled(bool enabled); - void requestCheckConnectivity(); - - void wifiEnabledChanged(); - void wifiHardwareEnabledChanged(); - void canCheckConnectivityChanged(); - void connectivityCheckEnabledChanged(); - void connectivityChanged(); - -private slots: - void deviceAdded(NetworkDevice* dev); - void deviceRemoved(NetworkDevice* dev); - -private: - explicit Networking(QObject* parent = nullptr); - - ObjectModel mDevices {this}; - NetworkBackend* mBackend = nullptr; - NetworkBackendType::Enum mBackendType = NetworkBackendType::None; - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bWifiEnabled, &Networking::wifiEnabledChanged); - Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bWifiHardwareEnabled, &Networking::wifiHardwareEnabledChanged); - Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bCanCheckConnectivity, &Networking::canCheckConnectivityChanged); - Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bConnectivityCheckEnabled, &Networking::connectivityCheckEnabledChanged); - Q_OBJECT_BINDABLE_PROPERTY(Networking, NetworkConnectivity::Enum, bConnectivity, &Networking::connectivityChanged); - // clang-format on -}; - -///! The Network service. -/// An interface to a network backend (currently only NetworkManager), -/// which can be used to view, configure, and connect to various networks. -class NetworkingQml: public QObject { - Q_OBJECT; - QML_NAMED_ELEMENT(Networking); - QML_SINGLETON; - // clang-format off - /// A list of all network devices. Networks are exposed through their respective devices. - QSDOC_TYPE_OVERRIDE(ObjectModel*); - Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT); - /// The backend being used to power the Network service. - Q_PROPERTY(qs::network::NetworkBackendType::Enum backend READ backend CONSTANT); - /// Switch for the rfkill software block of all wireless devices. - Q_PROPERTY(bool wifiEnabled READ wifiEnabled WRITE setWifiEnabled NOTIFY wifiEnabledChanged); - /// State of the rfkill hardware block of all wireless devices. - Q_PROPERTY(bool wifiHardwareEnabled READ default NOTIFY wifiHardwareEnabledChanged BINDABLE bindableWifiHardwareEnabled); - /// True if the @@backend supports connectivity checks. - Q_PROPERTY(bool canCheckConnectivity READ default NOTIFY canCheckConnectivityChanged BINDABLE bindableCanCheckConnectivity); - /// True if connectivity checking is enabled. - Q_PROPERTY(bool connectivityCheckEnabled READ connectivityCheckEnabled WRITE setConnectivityCheckEnabled NOTIFY connectivityCheckEnabledChanged); - /// The result of the last connectivity check. - /// - /// Connectivity checks may require additional configuration depending on your distro. - /// - /// > [!NOTE] This property can be used to determine if network access is restricted - /// > or gated behind a captive portal. - /// > - /// > If checking for captive portals, @@checkConnectivity() should be called after - /// > the portal is dismissed to update this property. - Q_PROPERTY(qs::network::NetworkConnectivity::Enum connectivity READ default NOTIFY connectivityChanged BINDABLE bindableConnectivity); - // clang-format on - -public: - explicit NetworkingQml(QObject* parent = nullptr); - - /// Re-check the network connectivity state immediately. - /// > [!NOTE] This should be invoked after a user dismisses a web browser that was opened to authenticate via a captive portal. - Q_INVOKABLE static void checkConnectivity(); - - [[nodiscard]] static ObjectModel* devices() { - return Networking::instance()->devices(); - } - [[nodiscard]] static NetworkBackendType::Enum backend() { - return Networking::instance()->backend(); - } - [[nodiscard]] static bool wifiEnabled() { return Networking::instance()->wifiEnabled(); } - static void setWifiEnabled(bool enabled) { Networking::instance()->setWifiEnabled(enabled); } - [[nodiscard]] static QBindable bindableWifiHardwareEnabled() { - return Networking::instance()->bindableWifiHardwareEnabled(); - } - [[nodiscard]] static QBindable bindableWifiEnabled() { - return Networking::instance()->bindableWifiEnabled(); - } - [[nodiscard]] static QBindable bindableCanCheckConnectivity() { - return Networking::instance()->bindableCanCheckConnectivity(); - } - [[nodiscard]] static bool connectivityCheckEnabled() { - return Networking::instance()->connectivityCheckEnabled(); - } - static void setConnectivityCheckEnabled(bool enabled) { - Networking::instance()->setConnectivityCheckEnabled(enabled); - } - [[nodiscard]] static QBindable bindableConnectivity() { - return Networking::instance()->bindableConnectivity(); - } - -signals: - void wifiEnabledChanged(); - void wifiHardwareEnabledChanged(); - void canCheckConnectivityChanged(); - void connectivityCheckEnabledChanged(); - void connectivityChanged(); -}; - -///! A network. -/// A network. Networks derived from a @@WifiDevice are @@WifiNetwork instances. -class Network: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_UNCREATABLE("Network can only be aqcuired through networking devices"); - - // clang-format off - /// The name of the network. - Q_PROPERTY(QString name READ name CONSTANT); - /// A list of NetworkManager connnection settings profiles for this network. - /// - /// > [!WARNING] Only valid for the NetworkManager backend. - Q_PROPERTY(QList nmSettings READ nmSettings NOTIFY nmSettingsChanged BINDABLE bindableNmSettings); - /// True if the network is connected. - Q_PROPERTY(bool connected READ default NOTIFY connectedChanged BINDABLE bindableConnected); - /// True if the wifi network has known connection settings saved. - Q_PROPERTY(bool known READ default NOTIFY knownChanged BINDABLE bindableKnown); - /// The connectivity state of the network. - Q_PROPERTY(ConnectionState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState); - /// If the network is currently connecting or disconnecting. Shorthand for checking @@state. - Q_PROPERTY(bool stateChanging READ default NOTIFY stateChangingChanged BINDABLE bindableStateChanging); - // clang-format on - -public: - explicit Network(QString name, QObject* parent = nullptr); - /// Attempt to connect to the network. - /// - /// > [!NOTE] If the network is a @@WifiNetwork and requires secrets, a @@connectionFailed(s) - /// > signal will be emitted with `NoSecrets`. - /// > @@WifiNetwork.connectWithPsk() can be used to provide secrets. - Q_INVOKABLE void connect(); - /// Attempt to connect to the network with a specific @@nmSettings entry. - /// - /// > [!WARNING] Only valid for the NetworkManager backend. - Q_INVOKABLE void connectWithSettings(NMSettings* settings); - /// Disconnect from the network. - Q_INVOKABLE void disconnect(); - /// Forget all connection settings for this network. - Q_INVOKABLE void forget(); - - void settingsAdded(NMSettings* settings); - void settingsRemoved(NMSettings* settings); - - // clang-format off - [[nodiscard]] QString name() const { return this->mName; } - [[nodiscard]] const QList& nmSettings() const { return this->bNmSettings; } - QBindable> bindableNmSettings() const { return &this->bNmSettings; } - QBindable bindableConnected() { return &this->bConnected; } - QBindable bindableKnown() { return &this->bKnown; } - [[nodiscard]] ConnectionState::Enum state() const { return this->bState; } - QBindable bindableState() { return &this->bState; } - QBindable bindableStateChanging() { return &this->bStateChanging; } - // clang-format on - -signals: - /// Signals that a connection to the network has failed because of the given @@ConnectionFailReason. - void connectionFailed(ConnectionFailReason::Enum reason); - - void connectedChanged(); - void knownChanged(); - void stateChanged(); - void stateChangingChanged(); - void nmSettingsChanged(); - QSDOC_HIDE void requestConnect(); - QSDOC_HIDE void requestConnectWithSettings(NMSettings* settings); - QSDOC_HIDE void requestDisconnect(); - QSDOC_HIDE void requestForget(); - -protected: - QString mName; - - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bConnected, &Network::connectedChanged); - Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bKnown, &Network::knownChanged); - Q_OBJECT_BINDABLE_PROPERTY(Network, ConnectionState::Enum, bState, &Network::stateChanged); - Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bStateChanging, &Network::stateChangingChanged); - Q_OBJECT_BINDABLE_PROPERTY(Network, QList, bNmSettings, &Network::nmSettingsChanged); - // clang-format on -}; - -} // namespace qs::network diff --git a/src/network/nm/CMakeLists.txt b/src/network/nm/CMakeLists.txt deleted file mode 100644 index 61f7e66..0000000 --- a/src/network/nm/CMakeLists.txt +++ /dev/null @@ -1,81 +0,0 @@ -set_source_files_properties(org.freedesktop.NetworkManager.xml PROPERTIES - CLASSNAME DBusNetworkManagerProxy - NO_NAMESPACE TRUE - INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/dbus_types.hpp -) - -qt_add_dbus_interface(NM_DBUS_INTERFACES - org.freedesktop.NetworkManager.xml - dbus_nm_backend -) - -set_source_files_properties(org.freedesktop.NetworkManager.Device.xml PROPERTIES - CLASSNAME DBusNMDeviceProxy - NO_NAMESPACE TRUE -) - -qt_add_dbus_interface(NM_DBUS_INTERFACES - org.freedesktop.NetworkManager.Device.xml - dbus_nm_device -) - -set_source_files_properties(org.freedesktop.NetworkManager.Device.Wireless.xml PROPERTIES - CLASSNAME DBusNMWirelessProxy - NO_NAMESPACE TRUE -) - -qt_add_dbus_interface(NM_DBUS_INTERFACES - org.freedesktop.NetworkManager.Device.Wireless.xml - dbus_nm_wireless -) - -set_source_files_properties(org.freedesktop.NetworkManager.AccessPoint.xml PROPERTIES - CLASSNAME DBusNMAccessPointProxy - NO_NAMESPACE TRUE -) - -qt_add_dbus_interface(NM_DBUS_INTERFACES - org.freedesktop.NetworkManager.AccessPoint.xml - dbus_nm_accesspoint -) - -set_source_files_properties(org.freedesktop.NetworkManager.Settings.Connection.xml PROPERTIES - CLASSNAME DBusNMConnectionSettingsProxy - NO_NAMESPACE TRUE - INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/dbus_types.hpp -) - -qt_add_dbus_interface(NM_DBUS_INTERFACES - org.freedesktop.NetworkManager.Settings.Connection.xml - dbus_nm_connection_settings -) - -set_source_files_properties(org.freedesktop.NetworkManager.Connection.Active.xml PROPERTIES - CLASSNAME DBusNMActiveConnectionProxy - NO_NAMESPACE TRUE -) - -qt_add_dbus_interface(NM_DBUS_INTERFACES - org.freedesktop.NetworkManager.Connection.Active.xml - dbus_nm_active_connection -) - -qt_add_library(quickshell-network-nm STATIC - backend.cpp - device.cpp - active_connection.cpp - settings.cpp - accesspoint.cpp - wireless.cpp - utils.cpp - dbus_types.cpp - enums.hpp - ${NM_DBUS_INTERFACES} -) - -target_include_directories(quickshell-network-nm PUBLIC - ${CMAKE_CURRENT_BINARY_DIR} -) - -target_link_libraries(quickshell-network-nm PRIVATE Qt::Qml Qt::DBus) -qs_add_link_dependencies(quickshell-network-nm quickshell-dbus) diff --git a/src/network/nm/accesspoint.cpp b/src/network/nm/accesspoint.cpp deleted file mode 100644 index b6e3dfb..0000000 --- a/src/network/nm/accesspoint.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "accesspoint.hpp" - -#include -#include -#include -#include -#include -#include -#include - -#include "../../core/logcat.hpp" -#include "../../dbus/properties.hpp" -#include "dbus_nm_accesspoint.h" -#include "enums.hpp" - -namespace qs::network { -using namespace qs::dbus; - -namespace { -QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg); -} - -NMAccessPoint::NMAccessPoint(const QString& path, QObject* parent): QObject(parent) { - this->proxy = new DBusNMAccessPointProxy( - "org.freedesktop.NetworkManager", - path, - QDBusConnection::systemBus(), - this - ); - - if (!this->proxy->isValid()) { - qCWarning(logNetworkManager) << "Cannot create DBus interface for access point at" << path; - return; - } - - QObject::connect( - &this->accessPointProperties, - &DBusPropertyGroup::getAllFinished, - this, - &NMAccessPoint::loaded, - Qt::SingleShotConnection - ); - - this->accessPointProperties.setInterface(this->proxy); - this->accessPointProperties.updateAllViaGetAll(); -} - -bool NMAccessPoint::isValid() const { return this->proxy && this->proxy->isValid(); } -QString NMAccessPoint::address() const { return this->proxy ? this->proxy->service() : QString(); } -QString NMAccessPoint::path() const { return this->proxy ? this->proxy->path() : QString(); } - -} // namespace qs::network - -namespace qs::dbus { - -DBusResult -DBusDataTransform::fromWire(quint32 wire) { - return DBusResult(static_cast(wire)); -} - -DBusResult -DBusDataTransform::fromWire(quint32 wire) { - return DBusResult(static_cast(wire)); -} - -DBusResult -DBusDataTransform::fromWire(quint32 wire) { - return DBusResult(static_cast(wire)); -} - -} // namespace qs::dbus diff --git a/src/network/nm/accesspoint.hpp b/src/network/nm/accesspoint.hpp deleted file mode 100644 index 63e35ee..0000000 --- a/src/network/nm/accesspoint.hpp +++ /dev/null @@ -1,92 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "../../dbus/properties.hpp" -#include "../wifi.hpp" -#include "dbus_nm_accesspoint.h" -#include "enums.hpp" - -namespace qs::dbus { - -template <> -struct DBusDataTransform { - using Wire = quint32; - using Data = qs::network::NM80211ApFlags::Enum; - static DBusResult fromWire(Wire wire); -}; - -template <> -struct DBusDataTransform { - using Wire = quint32; - using Data = qs::network::NM80211ApSecurityFlags::Enum; - static DBusResult fromWire(Wire wire); -}; - -template <> -struct DBusDataTransform { - using Wire = quint32; - using Data = qs::network::NM80211Mode::Enum; - static DBusResult fromWire(Wire wire); -}; - -} // namespace qs::dbus - -namespace qs::network { - -/// Proxy of a /org/freedesktop/NetworkManager/AccessPoint/* object. -class NMAccessPoint: public QObject { - Q_OBJECT; - -public: - explicit NMAccessPoint(const QString& path, QObject* parent = nullptr); - - [[nodiscard]] bool isValid() const; - [[nodiscard]] QString path() const; - [[nodiscard]] QString address() const; - [[nodiscard]] QByteArray ssid() const { return this->bSsid; } - [[nodiscard]] quint8 signalStrength() const { return this->bSignalStrength; } - [[nodiscard]] NM80211ApFlags::Enum flags() const { return this->bFlags; } - [[nodiscard]] NM80211ApSecurityFlags::Enum wpaFlags() const { return this->bWpaFlags; } - [[nodiscard]] NM80211ApSecurityFlags::Enum rsnFlags() const { return this->bRsnFlags; } - [[nodiscard]] NM80211Mode::Enum mode() const { return this->bMode; } - [[nodiscard]] QBindable bindableSecurity() { return &this->bSecurity; } - [[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; } - -signals: - void loaded(); - void ssidChanged(const QByteArray& ssid); - void signalStrengthChanged(quint8 signal); - void flagsChanged(NM80211ApFlags::Enum flags); - void wpaFlagsChanged(NM80211ApSecurityFlags::Enum wpaFlags); - void rsnFlagsChanged(NM80211ApSecurityFlags::Enum rsnFlags); - void modeChanged(NM80211Mode::Enum mode); - void securityChanged(WifiSecurityType::Enum security); - -private: - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, QByteArray, bSsid, &NMAccessPoint::ssidChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, quint8, bSignalStrength, &NMAccessPoint::signalStrengthChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, NM80211ApFlags::Enum, bFlags, &NMAccessPoint::flagsChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, NM80211ApSecurityFlags::Enum, bWpaFlags, &NMAccessPoint::wpaFlagsChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, NM80211ApSecurityFlags::Enum, bRsnFlags, &NMAccessPoint::rsnFlagsChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, NM80211Mode::Enum, bMode, &NMAccessPoint::modeChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, WifiSecurityType::Enum, bSecurity, &NMAccessPoint::securityChanged); - - QS_DBUS_BINDABLE_PROPERTY_GROUP(NMAccessPointAdapter, accessPointProperties); - QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pSsid, bSsid, accessPointProperties, "Ssid"); - QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pSignalStrength, bSignalStrength, accessPointProperties, "Strength"); - QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pFlags, bFlags, accessPointProperties, "Flags"); - QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pWpaFlags, bWpaFlags, accessPointProperties, "WpaFlags"); - QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pRsnFlags, bRsnFlags, accessPointProperties, "RsnFlags"); - QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pMode, bMode, accessPointProperties, "Mode"); - // clang-format on - - DBusNMAccessPointProxy* proxy = nullptr; -}; - -} // namespace qs::network diff --git a/src/network/nm/active_connection.cpp b/src/network/nm/active_connection.cpp deleted file mode 100644 index cab0e52..0000000 --- a/src/network/nm/active_connection.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include "active_connection.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../core/logcat.hpp" -#include "../../dbus/properties.hpp" -#include "dbus_nm_active_connection.h" -#include "enums.hpp" - -namespace qs::network { -using namespace qs::dbus; - -namespace { -QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg); -} - -NMActiveConnection::NMActiveConnection(const QString& path, QObject* parent): QObject(parent) { - this->proxy = new DBusNMActiveConnectionProxy( - "org.freedesktop.NetworkManager", - path, - QDBusConnection::systemBus(), - this - ); - - if (!this->proxy->isValid()) { - qCWarning(logNetworkManager) << "Cannot create DBus interface for connection at" << path; - return; - } - - // clang-format off - QObject::connect(&this->activeConnectionProperties, &DBusPropertyGroup::getAllFinished, this, &NMActiveConnection::loaded, Qt::SingleShotConnection); - QObject::connect(this->proxy, &DBusNMActiveConnectionProxy::StateChanged, this, &NMActiveConnection::onStateChanged); - // clang-format on - - this->activeConnectionProperties.setInterface(this->proxy); - this->activeConnectionProperties.updateAllViaGetAll(); -} - -void NMActiveConnection::onStateChanged(quint32 /*state*/, quint32 reason) { - auto enumReason = static_cast(reason); - if (this->bStateReason == enumReason) return; - this->bStateReason = enumReason; -} - -bool NMActiveConnection::isValid() const { return this->proxy && this->proxy->isValid(); } -QString NMActiveConnection::address() const { - return this->proxy ? this->proxy->service() : QString(); -} -QString NMActiveConnection::path() const { return this->proxy ? this->proxy->path() : QString(); } - -} // namespace qs::network - -namespace qs::dbus { - -DBusResult -DBusDataTransform::fromWire(quint32 wire) { - return DBusResult(static_cast(wire)); -} - -} // namespace qs::dbus diff --git a/src/network/nm/active_connection.hpp b/src/network/nm/active_connection.hpp deleted file mode 100644 index 33426a1..0000000 --- a/src/network/nm/active_connection.hpp +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../dbus/properties.hpp" -#include "dbus_nm_active_connection.h" -#include "enums.hpp" - -namespace qs::dbus { - -template <> -struct DBusDataTransform { - using Wire = quint32; - using Data = qs::network::NMConnectionState::Enum; - static DBusResult fromWire(Wire wire); -}; - -} // namespace qs::dbus - -namespace qs::network { - -// Proxy of a /org/freedesktop/NetworkManager/ActiveConnection/* object. -class NMActiveConnection: public QObject { - Q_OBJECT; - -public: - explicit NMActiveConnection(const QString& path, QObject* parent = nullptr); - - [[nodiscard]] bool isValid() const; - [[nodiscard]] QString path() const; - [[nodiscard]] QString address() const; - [[nodiscard]] QDBusObjectPath connection() const { return this->bConnection; } - [[nodiscard]] NMConnectionState::Enum state() const { return this->bState; } - [[nodiscard]] NMConnectionStateReason::Enum stateReason() const { return this->bStateReason; } - -signals: - void loaded(); - void connectionChanged(QDBusObjectPath path); - void stateChanged(NMConnectionState::Enum state); - void stateReasonChanged(NMConnectionStateReason::Enum reason); - -private slots: - void onStateChanged(quint32 state, quint32 reason); - -private: - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(NMActiveConnection, QDBusObjectPath, bConnection, &NMActiveConnection::connectionChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMActiveConnection, NMConnectionState::Enum, bState, &NMActiveConnection::stateChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMActiveConnection, NMConnectionStateReason::Enum, bStateReason, &NMActiveConnection::stateReasonChanged); - - QS_DBUS_BINDABLE_PROPERTY_GROUP(NMActiveConnection, activeConnectionProperties); - QS_DBUS_PROPERTY_BINDING(NMActiveConnection, pConnection, bConnection, activeConnectionProperties, "Connection"); - QS_DBUS_PROPERTY_BINDING(NMActiveConnection, pState, bState, activeConnectionProperties, "State"); - // clang-format on - DBusNMActiveConnectionProxy* proxy = nullptr; -}; - -} // namespace qs::network diff --git a/src/network/nm/backend.cpp b/src/network/nm/backend.cpp deleted file mode 100644 index a46ccb2..0000000 --- a/src/network/nm/backend.cpp +++ /dev/null @@ -1,305 +0,0 @@ -#include "backend.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../core/logcat.hpp" -#include "../../dbus/properties.hpp" -#include "../device.hpp" -#include "../enums.hpp" -#include "../network.hpp" -#include "../wifi.hpp" -#include "dbus_nm_backend.h" -#include "dbus_nm_device.h" -#include "dbus_types.hpp" -#include "device.hpp" -#include "enums.hpp" -#include "wireless.hpp" - -namespace qs::network { - -namespace { -QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg); -} - -NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) { - qCDebug(logNetworkManager) << "Connecting to NetworkManager"; - qDBusRegisterMetaType(); - - auto bus = QDBusConnection::systemBus(); - if (!bus.isConnected()) { - qCWarning( - logNetworkManager - ) << "Could not connect to DBus. NetworkManager backend will not work."; - return; - } - - this->proxy = new DBusNetworkManagerProxy( - "org.freedesktop.NetworkManager", - "/org/freedesktop/NetworkManager", - bus, - this - ); - - if (!this->proxy->isValid()) { - qCDebug( - logNetworkManager - ) << "NetworkManager is not currently running. This network backend will not work"; - } else { - this->init(); - } -} - -void NetworkManager::init() { - // clang-format off - QObject::connect(this->proxy, &DBusNetworkManagerProxy::DeviceAdded, this, &NetworkManager::onDevicePathAdded); - QObject::connect(this->proxy, &DBusNetworkManagerProxy::DeviceRemoved, this, &NetworkManager::onDevicePathRemoved); - // clang-format on - - this->dbusProperties.setInterface(this->proxy); - this->dbusProperties.updateAllViaGetAll(); - - this->registerDevices(); -} - -void NetworkManager::checkConnectivity() { - auto pending = this->proxy->CheckConnectivity(); - auto* call = new QDBusPendingCallWatcher(pending, this); - - auto responseCallback = [](QDBusPendingCallWatcher* call) { - const QDBusPendingReply reply = *call; - - if (reply.isError()) { - qCInfo(logNetworkManager) << "Failed to check connectivity: " << reply.error().message(); - } - - delete call; - }; - - QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); -} - -void NetworkManager::registerDevices() { - auto pending = this->proxy->GetAllDevices(); - auto* call = new QDBusPendingCallWatcher(pending, this); - - auto responseCallback = [this](QDBusPendingCallWatcher* call) { - const QDBusPendingReply> reply = *call; - - if (reply.isError()) { - qCWarning(logNetworkManager) << "Failed to get devices: " << reply.error().message(); - } else { - for (const QDBusObjectPath& devicePath: reply.value()) { - this->registerDevice(devicePath.path()); - } - } - - delete call; - }; - - QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); -} - -void NetworkManager::registerDevice(const QString& path) { - if (this->mDevices.contains(path)) { - qCDebug(logNetworkManager) << "Skipping duplicate registration of device" << path; - return; - } - - auto* temp = new DBusNMDeviceProxy( - "org.freedesktop.NetworkManager", - path, - QDBusConnection::systemBus(), - this - ); - - auto callback = [this, path, temp](uint value, const QDBusError& error) { - if (error.isValid()) { - qCWarning(logNetworkManager) << "Failed to get device type:" << error; - } else { - auto type = static_cast(value); - NMDevice* dev = nullptr; - this->mDevices.insert(path, nullptr); - - switch (type) { - case NMDeviceType::Wifi: dev = new NMWirelessDevice(path); break; - default: break; - } - - if (dev) { - qCDebug(logNetworkManager) << "Device added:" << path; - if (!dev->isValid()) { - qCWarning(logNetworkManager) << "Ignoring invalid registration of" << path; - delete dev; - } else { - this->mDevices[path] = dev; - // clang-format off - QObject::connect(dev, &NMDevice::addAndActivateConnection, this, &NetworkManager::addAndActivateConnection); - QObject::connect(dev, &NMDevice::activateConnection, this, &NetworkManager::activateConnection); - // clang-format on - - this->registerFrontendDevice(type, dev); - } - } else { - qCDebug(logNetworkManager) << "Ignoring registration of unsupported device:" << path; - } - temp->deleteLater(); - } - }; - - qs::dbus::asyncReadProperty(*temp, "DeviceType", callback); -} - -void NetworkManager::registerFrontendDevice(NMDeviceType::Enum type, NMDevice* dev) { - NetworkDevice* frontendDev = nullptr; - switch (type) { - case NMDeviceType::Wifi: { - auto* frontendWifiDev = new WifiDevice(dev); - auto* wifiDev = qobject_cast(dev); - // Bind WifiDevice-specific properties - auto translateMode = [wifiDev]() { - switch (wifiDev->mode()) { - case NM80211Mode::Unknown: return WifiDeviceMode::Unknown; - case NM80211Mode::Adhoc: return WifiDeviceMode::AdHoc; - case NM80211Mode::Infra: return WifiDeviceMode::Station; - case NM80211Mode::Ap: return WifiDeviceMode::AccessPoint; - case NM80211Mode::Mesh: return WifiDeviceMode::Mesh; - } - }; - // clang-format off - frontendWifiDev->bindableMode().setBinding(translateMode); - wifiDev->bindableScanning().setBinding([frontendWifiDev]() { return frontendWifiDev->scannerEnabled(); }); - QObject::connect(wifiDev, &NMWirelessDevice::networkAdded, frontendWifiDev, &WifiDevice::networkAdded); - QObject::connect(wifiDev, &NMWirelessDevice::networkRemoved, frontendWifiDev, &WifiDevice::networkRemoved); - // clang-format on - frontendDev = frontendWifiDev; - break; - } - default: return; - } - - // Bind generic NetworkDevice properties - auto translateState = [dev]() { - switch (dev->state()) { - case 0 ... 20: return ConnectionState::Unknown; - case 30: return ConnectionState::Disconnected; - case 40 ... 90: return ConnectionState::Connecting; - case 100: return ConnectionState::Connected; - case 110 ... 120: return ConnectionState::Disconnecting; - } - }; - // clang-format off - frontendDev->bindableName().setBinding([dev]() { return dev->interface(); }); - frontendDev->bindableAddress().setBinding([dev]() { return dev->hwAddress(); }); - frontendDev->bindableState().setBinding(translateState); - frontendDev->bindableAutoconnect().setBinding([dev]() { return dev->autoconnect(); }); - frontendDev->bindableNmManaged().setBinding([dev]() { return dev->managed(); }); - QObject::connect(frontendDev, &WifiDevice::requestDisconnect, dev, &NMDevice::disconnect); - QObject::connect(frontendDev, &NetworkDevice::requestSetAutoconnect, dev, &NMDevice::setAutoconnect); - QObject::connect(frontendDev, &NetworkDevice::requestSetNmManaged, dev, &NMDevice::setManaged); - // clang-format on - - this->mFrontendDevices.insert(dev->path(), frontendDev); - emit this->deviceAdded(frontendDev); -} - -void NetworkManager::removeFrontendDevice(NMDevice* dev) { - auto* frontendDev = this->mFrontendDevices.take(dev->path()); - if (frontendDev) { - emit this->deviceRemoved(frontendDev); - frontendDev->deleteLater(); - } -} - -void NetworkManager::onDevicePathAdded(const QDBusObjectPath& path) { - this->registerDevice(path.path()); -} - -void NetworkManager::onDevicePathRemoved(const QDBusObjectPath& path) { - auto iter = this->mDevices.find(path.path()); - if (iter == this->mDevices.end()) { - qCWarning(logNetworkManager) << "Sent removal signal for" << path.path() - << "which is not registered."; - } else { - auto* dev = iter.value(); - this->mDevices.erase(iter); - if (dev) { - qCDebug(logNetworkManager) << "Device removed:" << path.path(); - this->removeFrontendDevice(dev); - delete dev; - } - } -} - -void NetworkManager::activateConnection( - const QDBusObjectPath& connPath, - const QDBusObjectPath& devPath -) { - auto pending = this->proxy->ActivateConnection(connPath, devPath, QDBusObjectPath("/")); - auto* call = new QDBusPendingCallWatcher(pending, this); - - auto responseCallback = [](QDBusPendingCallWatcher* call) { - const QDBusPendingReply reply = *call; - - if (reply.isError()) { - qCWarning(logNetworkManager) << "Failed to activate connection:" << reply.error().message(); - } - delete call; - }; - QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); -} - -void NetworkManager::addAndActivateConnection( - const NMSettingsMap& settings, - const QDBusObjectPath& devPath, - const QDBusObjectPath& specificObjectPath -) { - auto pending = this->proxy->AddAndActivateConnection(settings, devPath, specificObjectPath); - auto* call = new QDBusPendingCallWatcher(pending, this); - - auto responseCallback = [](QDBusPendingCallWatcher* call) { - const QDBusPendingReply reply = *call; - - if (reply.isError()) { - qCWarning(logNetworkManager) - << "Failed to add and activate connection:" << reply.error().message(); - } - delete call; - }; - QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); -} - -void NetworkManager::setConnectivityCheckEnabled(bool enabled) { - if (enabled == this->bConnectivityCheckEnabled) return; - this->bConnectivityCheckEnabled = enabled; - this->pConnectivityCheckEnabled.write(); -} - -void NetworkManager::setWifiEnabled(bool enabled) { - if (enabled == this->bWifiEnabled) return; - this->bWifiEnabled = enabled; - this->pWifiEnabled.write(); -} - -bool NetworkManager::isAvailable() const { return this->proxy && this->proxy->isValid(); }; - -} // namespace qs::network - -namespace qs::dbus { - -DBusResult -DBusDataTransform::fromWire(quint32 wire) { - return DBusResult(static_cast(wire)); -} - -} // namespace qs::dbus diff --git a/src/network/nm/backend.hpp b/src/network/nm/backend.hpp deleted file mode 100644 index 2825a17..0000000 --- a/src/network/nm/backend.hpp +++ /dev/null @@ -1,96 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "../../dbus/properties.hpp" -#include "../network.hpp" -#include "dbus_nm_backend.h" -#include "dbus_types.hpp" -#include "device.hpp" -#include "enums.hpp" - -namespace qs::dbus { - -template <> -struct DBusDataTransform { - using Wire = quint32; - using Data = qs::network::NMConnectivityState::Enum; - static DBusResult fromWire(Wire wire); -}; - -} // namespace qs::dbus - -namespace qs::network { - -class NetworkManager: public NetworkBackend { - Q_OBJECT; - -public: - explicit NetworkManager(QObject* parent = nullptr); - - [[nodiscard]] bool isAvailable() const override; - [[nodiscard]] bool wifiEnabled() const { return this->bWifiEnabled; } - [[nodiscard]] bool wifiHardwareEnabled() const { return this->bWifiHardwareEnabled; } - [[nodiscard]] bool connectivityCheckAvailable() const { - return this->bConnectivityCheckAvailable; - }; - [[nodiscard]] bool connectivityCheckEnabled() const { return this->bConnectivityCheckEnabled; } - [[nodiscard]] NMConnectivityState::Enum connectivity() const { return this->bConnectivity; } - -signals: - void deviceAdded(NetworkDevice* device); - void deviceRemoved(NetworkDevice* device); - void wifiEnabledChanged(bool enabled); - void wifiHardwareEnabledChanged(bool enabled); - void connectivityStateChanged(NMConnectivityState::Enum state); - void connectivityCheckAvailableChanged(bool available); - void connectivityCheckEnabledChanged(bool enabled); - -public slots: - void setWifiEnabled(bool enabled); - void setConnectivityCheckEnabled(bool enabled); - void checkConnectivity(); - -private slots: - void onDevicePathAdded(const QDBusObjectPath& path); - void onDevicePathRemoved(const QDBusObjectPath& path); - void activateConnection(const QDBusObjectPath& connPath, const QDBusObjectPath& devPath); - void addAndActivateConnection( - const NMSettingsMap& settings, - const QDBusObjectPath& devPath, - const QDBusObjectPath& specificObjectPath - ); - -private: - void init(); - void registerDevices(); - void registerDevice(const QString& path); - void registerFrontendDevice(NMDeviceType::Enum type, NMDevice* dev); - void removeFrontendDevice(NMDevice* dev); - - QHash mDevices; - QHash mFrontendDevices; - - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, bool, bWifiEnabled, &NetworkManager::wifiEnabledChanged); - Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, bool, bWifiHardwareEnabled, &NetworkManager::wifiHardwareEnabledChanged); - Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, NMConnectivityState::Enum, bConnectivity, &NetworkManager::connectivityStateChanged); - Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, bool, bConnectivityCheckAvailable, &NetworkManager::connectivityCheckAvailableChanged); - Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, bool, bConnectivityCheckEnabled, &NetworkManager::connectivityCheckEnabledChanged); - - QS_DBUS_BINDABLE_PROPERTY_GROUP(NetworkManager, dbusProperties); - QS_DBUS_PROPERTY_BINDING(NetworkManager, pWifiEnabled, bWifiEnabled, dbusProperties, "WirelessEnabled"); - QS_DBUS_PROPERTY_BINDING(NetworkManager, pWifiHardwareEnabled, bWifiHardwareEnabled, dbusProperties, "WirelessHardwareEnabled"); - QS_DBUS_PROPERTY_BINDING(NetworkManager, pConnectivity, bConnectivity, dbusProperties, "Connectivity"); - QS_DBUS_PROPERTY_BINDING(NetworkManager, pConnectivityCheckAvailable, bConnectivityCheckAvailable, dbusProperties, "ConnectivityCheckAvailable"); - QS_DBUS_PROPERTY_BINDING(NetworkManager, pConnectivityCheckEnabled, bConnectivityCheckEnabled, dbusProperties, "ConnectivityCheckEnabled"); - // clang-format on - DBusNetworkManagerProxy* proxy = nullptr; -}; - -} // namespace qs::network diff --git a/src/network/nm/dbus_types.cpp b/src/network/nm/dbus_types.cpp deleted file mode 100644 index e161f11..0000000 --- a/src/network/nm/dbus_types.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "dbus_types.hpp" - -#include -#include -#include -#include -#include -#include - -namespace qs::network { - -const QDBusArgument& operator>>(const QDBusArgument& argument, NMSettingsMap& map) { - argument.beginMap(); - while (!argument.atEnd()) { - argument.beginMapEntry(); - QString groupName; - argument >> groupName; - - QVariantMap group; - argument >> group; - - map.insert(groupName, group); - argument.endMapEntry(); - } - argument.endMap(); - return argument; -} - -const QDBusArgument& operator<<(QDBusArgument& argument, const NMSettingsMap& map) { - argument.beginMap(qMetaTypeId(), qMetaTypeId()); - for (auto it = map.constBegin(); it != map.constEnd(); ++it) { - argument.beginMapEntry(); - argument << it.key(); - argument << it.value(); - argument.endMapEntry(); - } - argument.endMap(); - return argument; -} - -const QDBusArgument& operator>>(const QDBusArgument& argument, NMIPv6Address& addr) { - argument.beginStructure(); - argument >> addr.address >> addr.prefix >> addr.gateway; - argument.endStructure(); - return argument; -} - -const QDBusArgument& operator<<(QDBusArgument& argument, const NMIPv6Address& addr) { - argument.beginStructure(); - argument << addr.address << addr.prefix << addr.gateway; - argument.endStructure(); - return argument; -} - -const QDBusArgument& operator>>(const QDBusArgument& argument, NMIPv6Route& route) { - argument.beginStructure(); - argument >> route.destination >> route.prefix >> route.nexthop >> route.metric; - argument.endStructure(); - return argument; -} - -const QDBusArgument& operator<<(QDBusArgument& argument, const NMIPv6Route& route) { - argument.beginStructure(); - argument << route.destination << route.prefix << route.nexthop << route.metric; - argument.endStructure(); - return argument; -} - -} // namespace qs::network diff --git a/src/network/nm/dbus_types.hpp b/src/network/nm/dbus_types.hpp deleted file mode 100644 index bf428e5..0000000 --- a/src/network/nm/dbus_types.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace qs::network { - -using NMSettingsMap = QMap; - -const QDBusArgument& operator>>(const QDBusArgument& argument, NMSettingsMap& map); -const QDBusArgument& operator<<(QDBusArgument& argument, const NMSettingsMap& map); - -struct NMIPv6Address { - QByteArray address; - quint32 prefix = 0; - QByteArray gateway; -}; - -const QDBusArgument& operator>>(const QDBusArgument& argument, qs::network::NMIPv6Address& addr); -const QDBusArgument& operator<<(QDBusArgument& argument, const qs::network::NMIPv6Address& addr); - -struct NMIPv6Route { - QByteArray destination; - quint32 prefix = 0; - QByteArray nexthop; - quint32 metric = 0; -}; - -const QDBusArgument& operator>>(const QDBusArgument& argument, qs::network::NMIPv6Route& route); -const QDBusArgument& operator<<(QDBusArgument& argument, const qs::network::NMIPv6Route& route); - -} // namespace qs::network - -Q_DECLARE_METATYPE(qs::network::NMSettingsMap); -Q_DECLARE_METATYPE(qs::network::NMIPv6Address); -Q_DECLARE_METATYPE(qs::network::NMIPv6Route); diff --git a/src/network/nm/device.cpp b/src/network/nm/device.cpp deleted file mode 100644 index 1f229c8..0000000 --- a/src/network/nm/device.cpp +++ /dev/null @@ -1,163 +0,0 @@ -#include "device.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../core/logcat.hpp" -#include "../../dbus/properties.hpp" -#include "active_connection.hpp" -#include "dbus_nm_device.h" -#include "enums.hpp" -#include "settings.hpp" - -namespace qs::network { -using namespace qs::dbus; - -namespace { -QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg); -} - -NMDevice::NMDevice(const QString& path, QObject* parent): QObject(parent) { - this->deviceProxy = new DBusNMDeviceProxy( - "org.freedesktop.NetworkManager", - path, - QDBusConnection::systemBus(), - this - ); - - if (!this->deviceProxy->isValid()) { - qCWarning(logNetworkManager) << "Cannot create DBus interface for device at" << path; - return; - } - - // clang-format off - QObject::connect(this, &NMDevice::availableSettingsPathsChanged, this, &NMDevice::onAvailableSettingsPathsChanged); - QObject::connect(this, &NMDevice::activeConnectionPathChanged, this, &NMDevice::onActiveConnectionPathChanged); - QObject::connect(this->deviceProxy, &DBusNMDeviceProxy::StateChanged, this, &NMDevice::onStateChanged); - // clang-format on - - this->deviceProperties.setInterface(this->deviceProxy); - this->deviceProperties.updateAllViaGetAll(); -} - -void NMDevice::onStateChanged(quint32 newState, quint32 /*oldState*/, quint32 reason) { - auto enumReason = static_cast(reason); - auto enumNewState = static_cast(newState); - if (enumNewState == NMDeviceState::Failed) this->bLastFailReason = enumReason; - if (this->bStateReason == enumReason) return; - this->bStateReason = enumReason; -} - -void NMDevice::onActiveConnectionPathChanged(const QDBusObjectPath& path) { - const QString stringPath = path.path(); - - // Remove old active connection - if (this->mActiveConnection) { - qCDebug(logNetworkManager) << "Active connection removed:" << this->mActiveConnection->path(); - QObject::disconnect(this->mActiveConnection, nullptr, this, nullptr); - delete this->mActiveConnection; - this->mActiveConnection = nullptr; - } - - // Create new active connection - if (stringPath != "/") { - auto* active = new NMActiveConnection(stringPath, this); - if (!active->isValid()) { - qCWarning(logNetworkManager) << "Ignoring invalid registration of" << stringPath; - delete active; - } else { - qCDebug(logNetworkManager) << "Active connection added:" << stringPath; - this->mActiveConnection = active; - QObject::connect( - active, - &NMActiveConnection::loaded, - this, - [this, active]() { emit this->activeConnectionLoaded(active); }, - Qt::SingleShotConnection - ); - } - } -} - -void NMDevice::onAvailableSettingsPathsChanged(const QList& paths) { - QSet newPathSet; - for (const QDBusObjectPath& path: paths) { - newPathSet.insert(path.path()); - } - const auto existingPaths = this->mSettings.keys(); - const QSet existingPathSet(existingPaths.begin(), existingPaths.end()); - - const auto addedSettings = newPathSet - existingPathSet; - const auto removedSettings = existingPathSet - newPathSet; - - for (const QString& path: addedSettings) { - this->registerSettings(path); - } - for (const QString& path: removedSettings) { - auto* connection = this->mSettings.take(path); - if (!connection) { - qCDebug(logNetworkManager) << "Sent removal signal for" << path << "which is not registered."; - } else { - qCDebug(logNetworkManager) << "Connection settings removed:" << path; - delete connection; - } - }; -} - -void NMDevice::registerSettings(const QString& path) { - auto* settings = new NMSettings(path, this); - if (!settings->isValid()) { - qCWarning(logNetworkManager) << "Ignoring invalid registration of" << path; - delete settings; - } else { - qCDebug(logNetworkManager) << "Connection settings added:" << path; - this->mSettings.insert(path, settings); - QObject::connect( - settings, - &NMSettings::loaded, - this, - [this, settings]() { emit this->settingsLoaded(settings); }, - Qt::SingleShotConnection - ); - } -} - -void NMDevice::disconnect() { this->deviceProxy->Disconnect(); } - -void NMDevice::setAutoconnect(bool autoconnect) { - if (autoconnect == this->bAutoconnect) return; - this->bAutoconnect = autoconnect; - this->pAutoconnect.write(); -} - -void NMDevice::setManaged(bool managed) { - if (managed == this->bManaged) return; - this->bManaged = managed; - this->pManaged.write(); -} - -bool NMDevice::isValid() const { return this->deviceProxy && this->deviceProxy->isValid(); } -QString NMDevice::address() const { - return this->deviceProxy ? this->deviceProxy->service() : QString(); -} -QString NMDevice::path() const { return this->deviceProxy ? this->deviceProxy->path() : QString(); } - -} // namespace qs::network - -namespace qs::dbus { - -DBusResult -DBusDataTransform::fromWire(quint32 wire) { - return DBusResult(static_cast(wire)); -} - -} // namespace qs::dbus diff --git a/src/network/nm/device.hpp b/src/network/nm/device.hpp deleted file mode 100644 index 963f574..0000000 --- a/src/network/nm/device.hpp +++ /dev/null @@ -1,110 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "../../dbus/properties.hpp" -#include "../enums.hpp" -#include "active_connection.hpp" -#include "dbus_nm_device.h" -#include "settings.hpp" - -namespace qs::dbus { - -template <> -struct DBusDataTransform { - using Wire = quint32; - using Data = qs::network::NMDeviceState::Enum; - static DBusResult fromWire(Wire wire); -}; - -} // namespace qs::dbus - -namespace qs::network { - -// Proxy of a /org/freedesktop/NetworkManager/Device/* object. -// Only the members from the org.freedesktop.NetworkManager.Device interface. -// Owns the lifetime of NMActiveConnection(s) and NMConnectionSetting(s). -class NMDevice: public QObject { - Q_OBJECT; - -public: - explicit NMDevice(const QString& path, QObject* parent = nullptr); - - [[nodiscard]] virtual bool isValid() const; - [[nodiscard]] QString path() const; - [[nodiscard]] QString address() const; - [[nodiscard]] QString interface() const { return this->bInterface; } - [[nodiscard]] QString hwAddress() const { return this->bHwAddress; } - [[nodiscard]] bool managed() const { return this->bManaged; } - [[nodiscard]] NMDeviceState::Enum state() const { return this->bState; } - [[nodiscard]] NMDeviceStateReason::Enum stateReason() const { return this->bStateReason; } - [[nodiscard]] NMDeviceStateReason::Enum lastFailReason() const { return this->bLastFailReason; } - [[nodiscard]] bool autoconnect() const { return this->bAutoconnect; } - [[nodiscard]] NMActiveConnection* activeConnection() const { return this->mActiveConnection; } - -signals: - void activateConnection(const QDBusObjectPath& connPath, const QDBusObjectPath& devPath); - void addAndActivateConnection( - const NMSettingsMap& settings, - const QDBusObjectPath& devPath, - const QDBusObjectPath& apPath - ); - void settingsLoaded(NMSettings* settings); - void settingsRemoved(NMSettings* settings); - void availableSettingsPathsChanged(QList paths); - void activeConnectionPathChanged(const QDBusObjectPath& connection); - void activeConnectionLoaded(NMActiveConnection* active); - void interfaceChanged(const QString& interface); - void hwAddressChanged(const QString& hwAddress); - void managedChanged(bool managed); - void stateChanged(NMDeviceState::Enum state); - void stateReasonChanged(NMDeviceStateReason::Enum reason); - void lastFailReasonChanged(NMDeviceStateReason::Enum reason); - void autoconnectChanged(bool autoconnect); - -public slots: - void disconnect(); - void setAutoconnect(bool autoconnect); - void setManaged(bool managed); - -private slots: - void onStateChanged(quint32 newState, quint32 oldState, quint32 reason); - void onAvailableSettingsPathsChanged(const QList& paths); - void onActiveConnectionPathChanged(const QDBusObjectPath& path); - -private: - void registerSettings(const QString& path); - - QHash mSettings; - NMActiveConnection* mActiveConnection = nullptr; - - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QString, bInterface, &NMDevice::interfaceChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QString, bHwAddress, &NMDevice::hwAddressChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMDevice, bool, bManaged, &NMDevice::managedChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMDevice, NMDeviceState::Enum, bState, &NMDevice::stateChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMDevice, NMDeviceStateReason::Enum, bStateReason, &NMDevice::stateReasonChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMDevice, NMDeviceStateReason::Enum, bLastFailReason, &NMDevice::lastFailReasonChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMDevice, bool, bAutoconnect, &NMDevice::autoconnectChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QList, bAvailableConnections, &NMDevice::availableSettingsPathsChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QDBusObjectPath, bActiveConnection, &NMDevice::activeConnectionPathChanged); - - QS_DBUS_BINDABLE_PROPERTY_GROUP(NMDeviceAdapter, deviceProperties); - QS_DBUS_PROPERTY_BINDING(NMDevice, pName, bInterface, deviceProperties, "Interface"); - QS_DBUS_PROPERTY_BINDING(NMDevice, pAddress, bHwAddress, deviceProperties, "HwAddress"); - QS_DBUS_PROPERTY_BINDING(NMDevice, pManaged, bManaged, deviceProperties, "Managed"); - QS_DBUS_PROPERTY_BINDING(NMDevice, pState, bState, deviceProperties, "State"); - QS_DBUS_PROPERTY_BINDING(NMDevice, pAutoconnect, bAutoconnect, deviceProperties, "Autoconnect"); - QS_DBUS_PROPERTY_BINDING(NMDevice, pAvailableConnections, bAvailableConnections, deviceProperties, "AvailableConnections"); - QS_DBUS_PROPERTY_BINDING(NMDevice, pActiveConnection, bActiveConnection, deviceProperties, "ActiveConnection"); - // clang-format on - - DBusNMDeviceProxy* deviceProxy = nullptr; -}; - -} // namespace qs::network diff --git a/src/network/nm/enums.hpp b/src/network/nm/enums.hpp deleted file mode 100644 index 18b1b8b..0000000 --- a/src/network/nm/enums.hpp +++ /dev/null @@ -1,314 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace qs::network { - -// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMConnectivityState -class NMConnectivityState: public QObject { - Q_OBJECT; - -public: - enum Enum : quint8 { - Unknown = 0, - None = 1, - Portal = 2, - Limited = 3, - Full = 4, - }; -}; - -// Indicates the type of hardware represented by a device object. -class NMDeviceType: public QObject { - Q_OBJECT; - -public: - enum Enum : quint8 { - Unknown = 0, - Ethernet = 1, - Wifi = 2, - Unused1 = 3, - Unused2 = 4, - Bluetooth = 5, - OlpcMesh = 6, - Wimax = 7, - Modem = 8, - InfiniBand = 9, - Bond = 10, - Vlan = 11, - Adsl = 12, - Bridge = 13, - Generic = 14, - Team = 15, - Tun = 16, - IpTunnel = 17, - MacVlan = 18, - VxLan = 19, - Veth = 20, - MacSec = 21, - Dummy = 22, - Ppp = 23, - OvsInterface = 24, - OvsPort = 25, - OvsBridge = 26, - Wpan = 27, - Lowpan = 28, - Wireguard = 29, - WifiP2P = 30, - Vrf = 31, - Loopback = 32, - Hsr = 33, - IpVlan = 34, - }; - Q_ENUM(Enum); -}; - -// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceState. -class NMDeviceState: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - Unknown = 0, - Unmanaged = 10, - Unavailable = 20, - Disconnected = 30, - Prepare = 40, - Config = 50, - NeedAuth = 60, - IPConfig = 70, - IPCheck = 80, - Secondaries = 90, - Activated = 100, - Deactivating = 110, - Failed = 120, - }; - Q_ENUM(Enum); -}; - -// Device state change reason codes. -// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceStateReason. -class NMDeviceStateReason: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - None = 0, - Unknown = 1, - NowManaged = 2, - NowUnmanaged = 3, - ConfigFailed = 4, - IpConfigUnavailable = 5, - IpConfigExpired = 6, - NoSecrets = 7, - SupplicantDisconnect = 8, - SupplicantConfigFailed = 9, - SupplicantFailed = 10, - SupplicantTimeout = 11, - PppStartFailed = 12, - PppDisconnect = 13, - PppFailed = 14, - DhcpStartFailed = 15, - DhcpError = 16, - DhcpFailed = 17, - SharedStartFailed = 18, - SharedFailed = 19, - AutoIpStartFailed = 20, - AutoIpError = 21, - AutoIpFailed = 22, - ModemBusy = 23, - ModemNoDialTone = 24, - ModemNoCarrier = 25, - ModemDialTimeout = 26, - ModemDialFailed = 27, - ModemInitFailed = 28, - GsmApnFailed = 29, - GsmRegistrationNotSearching = 30, - GsmRegistrationDenied = 31, - GsmRegistrationTimeout = 32, - GsmRegistrationFailed = 33, - GsmPinCheckFailed = 34, - FirmwareMissing = 35, - Removed = 36, - Sleeping = 37, - ConnectionRemoved = 38, - UserRequested = 39, - Carrier = 40, - ConnectionAssumed = 41, - SupplicantAvailable = 42, - ModemNotFound = 43, - BtFailed = 44, - GsmSimNotInserted = 45, - GsmSimPinRequired = 46, - GsmSimPukRequired = 47, - GsmSimWrong = 48, - InfinibandMode = 49, - DependencyFailed = 50, - Br2684Failed = 51, - ModemManagerUnavailable = 52, - SsidNotFound = 53, - SecondaryConnectionFailed = 54, - DcbFcoeFailed = 55, - TeamdControlFailed = 56, - ModemFailed = 57, - ModemAvailable = 58, - SimPinIncorrect = 59, - NewActivation = 60, - ParentChanged = 61, - ParentManagedChanged = 62, - OvsdbFailed = 63, - IpAddressDuplicate = 64, - IpMethodUnsupported = 65, - SriovConfigurationFailed = 66, - PeerNotFound = 67, - DeviceHandlerFailed = 68, - UnmanagedByDefault = 69, - UnmanagedExternalDown = 70, - UnmanagedLinkNotInit = 71, - UnmanagedQuitting = 72, - UnmanagedManagerDisabled = 73, - UnmanagedUserConf = 74, - UnmanagedUserExplicit = 75, - UnmanagedUserSettings = 76, - UnmanagedUserUdev = 77, - NetworkingOff = 78, - }; - Q_ENUM(Enum); -}; - -// 802.11 specific device encryption and authentication capabilities. -// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceWifiCapabilities. -class NMWirelessCapabilities: public QObject { - Q_OBJECT; - -public: - enum Enum : quint16 { - None = 0, - CipherWep40 = 1, - CipherWep104 = 2, - CipherTkip = 4, - CipherCcmp = 8, - Wpa = 16, - Rsn = 32, - Ap = 64, - Adhoc = 128, - FreqValid = 256, - Freq2Ghz = 512, - Freq5Ghz = 1024, - Freq6Ghz = 2048, - Mesh = 4096, - IbssRsn = 8192, - }; - Q_ENUM(Enum); -}; - -// Indicates the 802.11 mode an access point is currently in. -// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NM80211Mode. -class NM80211Mode: public QObject { - Q_OBJECT; - -public: - enum Enum : quint8 { - Unknown = 0, - Adhoc = 1, - Infra = 2, - Ap = 3, - Mesh = 4, - }; - Q_ENUM(Enum); -}; - -// 802.11 access point flags. -// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NM80211ApSecurityFlags. -class NM80211ApFlags: public QObject { - Q_OBJECT; - -public: - enum Enum : quint8 { - None = 0, - Privacy = 1, - Wps = 2, - WpsPbc = 4, - WpsPin = 8, - }; - Q_ENUM(Enum); -}; - -// 802.11 access point security and authentication flags. -// These flags describe the current system requirements of an access point as determined from the access point's beacon. -// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NM80211ApSecurityFlags. -class NM80211ApSecurityFlags: public QObject { - Q_OBJECT; - -public: - enum Enum : quint16 { - None = 0, - PairWep40 = 1, - PairWep104 = 2, - PairTkip = 4, - PairCcmp = 8, - GroupWep40 = 16, - GroupWep104 = 32, - GroupTkip = 64, - GroupCcmp = 128, - KeyMgmtPsk = 256, - KeyMgmt8021x = 512, - KeyMgmtSae = 1024, - KeyMgmtOwe = 2048, - KeyMgmtOweTm = 4096, - KeyMgmtEapSuiteB192 = 8192, - }; - Q_ENUM(Enum); -}; - -// Indicates the state of a connection to a specific network while it is starting, connected, or disconnected from that network. -// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMActiveConnectionState. -class NMConnectionState: public QObject { - Q_OBJECT; - -public: - enum Enum : quint8 { - Unknown = 0, - Activating = 1, - Activated = 2, - Deactivating = 3, - Deactivated = 4 - }; - Q_ENUM(Enum); -}; - -/// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMActiveConnectionStateReason. -class NMConnectionStateReason: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - Unknown = 0, - None = 1, - UserDisconnected = 2, - DeviceDisconnected = 3, - ServiceStopped = 4, - IpConfigInvalid = 5, - ConnectTimeout = 6, - ServiceStartTimeout = 7, - ServiceStartFailed = 8, - NoSecrets = 9, - LoginFailed = 10, - ConnectionRemoved = 11, - DependencyFailed = 12, - DeviceRealizeFailed = 13, - DeviceRemoved = 14 - }; - Q_ENUM(Enum); -}; - -} // namespace qs::network diff --git a/src/network/nm/org.freedesktop.NetworkManager.AccessPoint.xml b/src/network/nm/org.freedesktop.NetworkManager.AccessPoint.xml deleted file mode 100644 index c5e7737..0000000 --- a/src/network/nm/org.freedesktop.NetworkManager.AccessPoint.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/network/nm/org.freedesktop.NetworkManager.Connection.Active.xml b/src/network/nm/org.freedesktop.NetworkManager.Connection.Active.xml deleted file mode 100644 index fa0e778..0000000 --- a/src/network/nm/org.freedesktop.NetworkManager.Connection.Active.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/network/nm/org.freedesktop.NetworkManager.Device.Wireless.xml b/src/network/nm/org.freedesktop.NetworkManager.Device.Wireless.xml deleted file mode 100644 index ccfe333..0000000 --- a/src/network/nm/org.freedesktop.NetworkManager.Device.Wireless.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/network/nm/org.freedesktop.NetworkManager.Device.xml b/src/network/nm/org.freedesktop.NetworkManager.Device.xml deleted file mode 100644 index 414d24f..0000000 --- a/src/network/nm/org.freedesktop.NetworkManager.Device.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/src/network/nm/org.freedesktop.NetworkManager.Settings.Connection.xml b/src/network/nm/org.freedesktop.NetworkManager.Settings.Connection.xml deleted file mode 100644 index 81419b9..0000000 --- a/src/network/nm/org.freedesktop.NetworkManager.Settings.Connection.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/src/network/nm/org.freedesktop.NetworkManager.xml b/src/network/nm/org.freedesktop.NetworkManager.xml deleted file mode 100644 index 75c314a..0000000 --- a/src/network/nm/org.freedesktop.NetworkManager.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/network/nm/settings.cpp b/src/network/nm/settings.cpp deleted file mode 100644 index af36dae..0000000 --- a/src/network/nm/settings.cpp +++ /dev/null @@ -1,227 +0,0 @@ -#include "settings.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../core/logcat.hpp" -#include "../../dbus/properties.hpp" -#include "dbus_nm_connection_settings.h" -#include "dbus_types.hpp" -#include "utils.hpp" - -namespace qs::network { -using namespace qs::dbus; - -namespace { -QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network", QtWarningMsg); -QS_LOGGING_CATEGORY(logNMSettings, "quickshell.network.nm_settings", QtWarningMsg); -} // namespace - -NMSettings::NMSettings(const QString& path, QObject* parent): QObject(parent) { - qDBusRegisterMetaType>(); - qDBusRegisterMetaType>(); - qDBusRegisterMetaType>>(); - qDBusRegisterMetaType>(); - qDBusRegisterMetaType(); - qDBusRegisterMetaType>(); - qDBusRegisterMetaType(); - qDBusRegisterMetaType>(); - - this->proxy = new DBusNMConnectionSettingsProxy( - "org.freedesktop.NetworkManager", - path, - QDBusConnection::systemBus(), - this - ); - - if (!this->proxy->isValid()) { - qCWarning(logNetworkManager) << "Cannot create DBus interface for connection settings at" - << path; - return; - } - - QObject::connect( - this->proxy, - &DBusNMConnectionSettingsProxy::Updated, - this, - &NMSettings::getSettings - ); - - this->bId.setBinding([this]() { return this->bSettings.value()["connection"]["id"].toString(); }); - this->bUuid.setBinding([this]() { - return this->bSettings.value()["connection"]["uuid"].toString(); - }); - - this->settingsProperties.setInterface(this->proxy); - this->settingsProperties.updateAllViaGetAll(); - - this->getSettings(); -} - -void NMSettings::getSettings() { - auto pending = this->proxy->GetSettings(); - auto* call = new QDBusPendingCallWatcher(pending, this); - - auto responseCallback = [this](QDBusPendingCallWatcher* call) { - const QDBusPendingReply reply = *call; - - if (reply.isError()) { - qCWarning(logNetworkManager) - << "Failed to get settings for" << this->path() << ":" << reply.error().message(); - } else { - auto settings = reply.value(); - manualSettingDemarshall(settings); - this->bSettings = settings; - qCDebug(logNetworkManager) << "Settings map updated for" << this->path(); - - if (!this->mLoaded) { - emit this->loaded(); - this->mLoaded = true; - } - }; - - delete call; - }; - - QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); -} - -QDBusPendingCallWatcher* NMSettings::updateSettings( - const NMSettingsMap& settingsToChange, - const NMSettingsMap& settingsToRemove -) { - auto settings = removeSettingsInMap(this->bSettings, settingsToRemove); - settings = mergeSettingsMaps(settings, settingsToChange); - auto pending = this->proxy->Update(settings); - auto* call = new QDBusPendingCallWatcher(pending, this); - - return call; -} - -void NMSettings::clearSecrets() { - auto pending = this->proxy->ClearSecrets(); - auto* call = new QDBusPendingCallWatcher(pending, this); - - auto responseCallback = [this](QDBusPendingCallWatcher* call) { - const QDBusPendingReply<> reply = *call; - - if (reply.isError()) { - qCWarning(logNetworkManager) - << "Failed to clear secrets for" << this->path() << ":" << reply.error().message(); - } else { - qCDebug(logNetworkManager) << "Cleared secrets for" << this->path(); - } - delete call; - }; - - QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); -} - -void NMSettings::forget() { - auto pending = this->proxy->Delete(); - auto* call = new QDBusPendingCallWatcher(pending, this); - - auto responseCallback = [this](QDBusPendingCallWatcher* call) { - const QDBusPendingReply<> reply = *call; - - if (reply.isError()) { - qCWarning(logNetworkManager) - << "Failed to forget" << this->path() << ":" << reply.error().message(); - } else { - qCDebug(logNetworkManager) << "Successfully deletion of" << this->path(); - } - delete call; - }; - - QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); -} - -QVariantMap NMSettings::read() { - QVariantMap result; - const auto& settings = this->bSettings.value(); - for (auto it = settings.constBegin(); it != settings.constEnd(); ++it) { - QVariantMap group; - for (auto jt = it.value().constBegin(); jt != it.value().constEnd(); ++jt) { - group.insert(jt.key(), settingTypeToQml(jt.value())); - } - result.insert(it.key(), group); - } - return result; -} - -void NMSettings::write(const QVariantMap& settings) { - NMSettingsMap changedSettings; - NMSettingsMap removedSettings; - QStringList failedSettings; - - for (auto it = settings.constBegin(); it != settings.constEnd(); ++it) { - if (!it.value().canConvert()) continue; - - auto group = it.value().toMap(); - QVariantMap toChange; - QVariantMap toRemove; - for (auto jt = group.constBegin(); jt != group.constEnd(); ++jt) { - if (jt.value().isNull()) { - toRemove.insert(jt.key(), QVariant()); - } else { - auto converted = settingTypeFromQml(it.key(), jt.key(), jt.value()); - if (!converted.isValid()) failedSettings.append(it.key() + "." + jt.key()); - else toChange.insert(jt.key(), converted); - } - } - if (!toChange.isEmpty()) changedSettings.insert(it.key(), toChange); - if (!toRemove.isEmpty()) removedSettings.insert(it.key(), toRemove); - } - - if (!failedSettings.isEmpty()) { - qCWarning(logNMSettings) << "A write to" << this - << "has received bad types for the following settings:" - << failedSettings.join(", "); - } - - auto* call = this->updateSettings(changedSettings, removedSettings); - - auto responseCallback = [this](QDBusPendingCallWatcher* call) { - const QDBusPendingReply<> reply = *call; - - if (reply.isError()) { - qCWarning(logNetworkManager) - << "Failed to update settings for" << this->path() << ":" << reply.error().message(); - } else { - qCDebug(logNMSettings) << "Successful write to" << this; - } - delete call; - }; - - QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); -} - -bool NMSettings::isValid() const { return this->proxy && this->proxy->isValid(); } -QString NMSettings::address() const { return this->proxy ? this->proxy->service() : QString(); } -QString NMSettings::path() const { return this->proxy ? this->proxy->path() : QString(); } - -} // namespace qs::network - -QDebug operator<<(QDebug debug, const qs::network::NMSettings* settings) { - auto saver = QDebugStateSaver(debug); - - if (settings) { - debug.nospace() << "NMSettings(" << static_cast(settings) - << ", uuid=" << settings->uuid() << ")"; - } else { - debug << "WifiNetwork(nullptr)"; - } - - return debug; -} diff --git a/src/network/nm/settings.hpp b/src/network/nm/settings.hpp deleted file mode 100644 index 3a76c61..0000000 --- a/src/network/nm/settings.hpp +++ /dev/null @@ -1,82 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../dbus/properties.hpp" -#include "dbus_nm_connection_settings.h" -#include "dbus_types.hpp" - -namespace qs::network { - -// Proxy of a /org/freedesktop/NetworkManager/Settings/Connection/* object. -///! A NetworkManager connection settings profile. -class NMSettings: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_UNCREATABLE(""); - - /// The human-readable unique identifier for the connection. - Q_PROPERTY(QString id READ default NOTIFY idChanged BINDABLE bindableId); - /// A universally unique identifier for the connection. - Q_PROPERTY(QString uuid READ uuid NOTIFY uuidChanged BINDABLE bindableUuid); - -public: - explicit NMSettings(const QString& path, QObject* parent = nullptr); - - /// Clear all of the secrets belonging to the settings. - Q_INVOKABLE void clearSecrets(); - /// Delete the settings. - Q_INVOKABLE void forget(); - /// Update the connection with new settings and save the connection to disk. - /// Only changed fields need to be included. - /// Writing a setting to `null` will remove the setting or reset it to its default. - /// - /// > [!NOTE] Secrets may be part of the update request, - /// > and will be either stored in persistent storage or sent to a Secret Agent for storage, - /// > depending on the flags associated with each secret. - Q_INVOKABLE void write(const QVariantMap& settings); - /// Get the settings map describing this network configuration. - /// - /// > [!NOTE] This will never include any secrets required for connection to the network, as those are often protected. - Q_INVOKABLE QVariantMap read(); - - [[nodiscard]] bool isValid() const; - [[nodiscard]] QString path() const; - [[nodiscard]] QString address() const; - [[nodiscard]] NMSettingsMap map() { return this->bSettings; } - QDBusPendingCallWatcher* - updateSettings(const NMSettingsMap& settingsToChange, const NMSettingsMap& settingsToRemove = {}); - QBindable bindableId() { return &this->bId; } - [[nodiscard]] QString uuid() const { return this->bUuid; } - QBindable bindableUuid() { return &this->bUuid; } - -signals: - void loaded(); - void settingsChanged(NMSettingsMap settings); - void idChanged(QString id); - void uuidChanged(QString uuid); - -private: - bool mLoaded = false; - - void getSettings(); - - Q_OBJECT_BINDABLE_PROPERTY(NMSettings, NMSettingsMap, bSettings, &NMSettings::settingsChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMSettings, QString, bId, &NMSettings::idChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMSettings, QString, bUuid, &NMSettings::uuidChanged); - QS_DBUS_BINDABLE_PROPERTY_GROUP(NMSettings, settingsProperties); - DBusNMConnectionSettingsProxy* proxy = nullptr; -}; - -} // namespace qs::network - -QDebug operator<<(QDebug debug, const qs::network::NMSettings* settings); diff --git a/src/network/nm/utils.cpp b/src/network/nm/utils.cpp deleted file mode 100644 index afdc796..0000000 --- a/src/network/nm/utils.cpp +++ /dev/null @@ -1,525 +0,0 @@ -#include "utils.hpp" -#include -// We depend on non-std Linux extensions that ctime doesn't put in the global namespace -// NOLINTNEXTLINE(modernize-deprecated-headers) -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "../enums.hpp" -#include "dbus_types.hpp" -#include "enums.hpp" - -namespace qs::network { - -WifiSecurityType::Enum securityFromSettingsMap(const NMSettingsMap& settings) { - const QString mapName = "802-11-wireless-security"; - if (!settings.contains(mapName)) return WifiSecurityType::Unknown; - const QVariantMap& security = settings.value(mapName); - if (security.isEmpty()) return WifiSecurityType::Open; - - const QString keyMgmt = security["key-mgmt"].toString(); - const QString authAlg = security["auth-alg"].toString(); - const QList proto = security["proto"].toList(); - - if (keyMgmt == "none") { - return WifiSecurityType::StaticWep; - } else if (keyMgmt == "ieee8021x") { - if (authAlg == "leap") { - return WifiSecurityType::Leap; - } else { - return WifiSecurityType::DynamicWep; - } - } else if (keyMgmt == "wpa-psk") { - if (proto.contains("wpa") && proto.contains("rsn")) return WifiSecurityType::WpaPsk; - return WifiSecurityType::Wpa2Psk; - } else if (keyMgmt == "wpa-eap") { - if (proto.contains("wpa") && proto.contains("rsn")) return WifiSecurityType::WpaEap; - return WifiSecurityType::Wpa2Eap; - } else if (keyMgmt == "sae") { - return WifiSecurityType::Sae; - } else if (keyMgmt == "wpa-eap-suite-b-192") { - return WifiSecurityType::Wpa3SuiteB192; - } else if (keyMgmt == "owe") { - return WifiSecurityType::Owe; - } - return WifiSecurityType::Open; -} - -bool deviceSupportsApCiphers( - NMWirelessCapabilities::Enum caps, - NM80211ApSecurityFlags::Enum apFlags, - WifiSecurityType::Enum type -) { - bool havePair = false; - bool haveGroup = false; - // Device needs to support at least one pairwise and one group cipher - - if (type == WifiSecurityType::StaticWep) { - // Static WEP only uses group ciphers - havePair = true; - } else { - if (caps & NMWirelessCapabilities::CipherWep40 && apFlags & NM80211ApSecurityFlags::PairWep40) { - havePair = true; - } - if (caps & NMWirelessCapabilities::CipherWep104 && apFlags & NM80211ApSecurityFlags::PairWep104) - { - havePair = true; - } - if (caps & NMWirelessCapabilities::CipherTkip && apFlags & NM80211ApSecurityFlags::PairTkip) { - havePair = true; - } - if (caps & NMWirelessCapabilities::CipherCcmp && apFlags & NM80211ApSecurityFlags::PairCcmp) { - havePair = true; - } - } - - if (caps & NMWirelessCapabilities::CipherWep40 && apFlags & NM80211ApSecurityFlags::GroupWep40) { - haveGroup = true; - } - if (caps & NMWirelessCapabilities::CipherWep104 && apFlags & NM80211ApSecurityFlags::GroupWep104) - { - haveGroup = true; - } - if (type != WifiSecurityType::StaticWep) { - if (caps & NMWirelessCapabilities::CipherTkip && apFlags & NM80211ApSecurityFlags::GroupTkip) { - haveGroup = true; - } - if (caps & NMWirelessCapabilities::CipherCcmp && apFlags & NM80211ApSecurityFlags::GroupCcmp) { - haveGroup = true; - } - } - - return (havePair && haveGroup); -} - -bool securityIsValid( - WifiSecurityType::Enum type, - NMWirelessCapabilities::Enum caps, - bool adhoc, - NM80211ApFlags::Enum apFlags, - NM80211ApSecurityFlags::Enum apWpa, - NM80211ApSecurityFlags::Enum apRsn -) { - switch (type) { - case WifiSecurityType::Open: - if (apFlags & NM80211ApFlags::Privacy) return false; - if (apWpa || apRsn) return false; - break; - case WifiSecurityType::Leap: - if (adhoc) return false; - case WifiSecurityType::StaticWep: - if (!(apFlags & NM80211ApFlags::Privacy)) return false; - if (apWpa || apRsn) { - if (!deviceSupportsApCiphers(caps, apWpa, WifiSecurityType::StaticWep)) { - if (!deviceSupportsApCiphers(caps, apRsn, WifiSecurityType::StaticWep)) return false; - } - } - break; - case WifiSecurityType::DynamicWep: - if (adhoc) return false; - if (apRsn || !(apFlags & NM80211ApFlags::Privacy)) return false; - if (apWpa) { - if (!(apWpa & NM80211ApSecurityFlags::KeyMgmt8021x)) return false; - if (!deviceSupportsApCiphers(caps, apWpa, WifiSecurityType::DynamicWep)) return false; - } - break; - case WifiSecurityType::WpaPsk: - if (adhoc) return false; - if (!(caps & NMWirelessCapabilities::Wpa)) return false; - if (apWpa & NM80211ApSecurityFlags::KeyMgmtPsk) { - if (apWpa & NM80211ApSecurityFlags::PairTkip && caps & NMWirelessCapabilities::CipherTkip) { - return true; - } - if (apWpa & NM80211ApSecurityFlags::PairCcmp && caps & NMWirelessCapabilities::CipherCcmp) { - return true; - } - } - return false; - case WifiSecurityType::Wpa2Psk: - if (!(caps & NMWirelessCapabilities::Rsn)) return false; - if (adhoc) { - if (!(caps & NMWirelessCapabilities::IbssRsn)) return false; - if (apRsn & NM80211ApSecurityFlags::PairCcmp && caps & NMWirelessCapabilities::CipherCcmp) { - return true; - } - } else { - if (apRsn & NM80211ApSecurityFlags::KeyMgmtPsk) { - if (apRsn & NM80211ApSecurityFlags::PairTkip && caps & NMWirelessCapabilities::CipherTkip) { - return true; - } - if (apRsn & NM80211ApSecurityFlags::PairCcmp && caps & NMWirelessCapabilities::CipherCcmp) { - return true; - } - } - } - return false; - case WifiSecurityType::WpaEap: - if (adhoc) return false; - if (!(caps & NMWirelessCapabilities::Wpa)) return false; - if (!(apWpa & NM80211ApSecurityFlags::KeyMgmt8021x)) return false; - if (!deviceSupportsApCiphers(caps, apWpa, WifiSecurityType::WpaEap)) return false; - break; - case WifiSecurityType::Wpa2Eap: - if (adhoc) return false; - if (!(caps & NMWirelessCapabilities::Rsn)) return false; - if (!(apRsn & NM80211ApSecurityFlags::KeyMgmt8021x)) return false; - if (!deviceSupportsApCiphers(caps, apRsn, WifiSecurityType::Wpa2Eap)) return false; - break; - case WifiSecurityType::Sae: - if (!(caps & NMWirelessCapabilities::Rsn)) return false; - if (adhoc) { - if (!(caps & NMWirelessCapabilities::IbssRsn)) return false; - if (apRsn & NM80211ApSecurityFlags::PairCcmp && caps & NMWirelessCapabilities::CipherCcmp) { - return true; - } - } else { - if (apRsn & NM80211ApSecurityFlags::KeyMgmtSae) { - if (apRsn & NM80211ApSecurityFlags::PairTkip && caps & NMWirelessCapabilities::CipherTkip) { - return true; - } - if (apRsn & NM80211ApSecurityFlags::PairCcmp && caps & NMWirelessCapabilities::CipherCcmp) { - return true; - } - } - } - return false; - case WifiSecurityType::Owe: - if (adhoc) return false; - if (!(caps & NMWirelessCapabilities::Rsn)) return false; - if (!(apRsn & NM80211ApSecurityFlags::KeyMgmtOwe) - && !(apRsn & NM80211ApSecurityFlags::KeyMgmtOweTm)) - { - return false; - } - break; - case WifiSecurityType::Wpa3SuiteB192: - if (adhoc) return false; - if (!(caps & NMWirelessCapabilities::Rsn)) return false; - if (!(apRsn & NM80211ApSecurityFlags::KeyMgmtEapSuiteB192)) return false; - break; - default: return false; - } - return true; -} - -WifiSecurityType::Enum findBestWirelessSecurity( - NMWirelessCapabilities::Enum caps, - bool adHoc, - NM80211ApFlags::Enum apFlags, - NM80211ApSecurityFlags::Enum apWpa, - NM80211ApSecurityFlags::Enum apRsn -) { - // Loop through security types from most to least secure since the enum - // values are sequential and in priority order (0-10, excluding Unknown=11) - for (int i = WifiSecurityType::Wpa3SuiteB192; i <= WifiSecurityType::Open; ++i) { - auto type = static_cast(i); - if (securityIsValid(type, caps, adHoc, apFlags, apWpa, apRsn)) { - return type; - } - } - return WifiSecurityType::Unknown; -} - -NMSettingsMap mergeSettingsMaps(const NMSettingsMap& target, const NMSettingsMap& source) { - NMSettingsMap result = target; - for (auto iter = source.constBegin(); iter != source.constEnd(); ++iter) { - result[iter.key()].insert(iter.value()); - } - return result; -} - -NMSettingsMap removeSettingsInMap(const NMSettingsMap& target, const NMSettingsMap& toRemove) { - NMSettingsMap result = target; - for (auto iter = toRemove.constBegin(); iter != toRemove.constEnd(); ++iter) { - const QString& group = iter.key(); - const QVariantMap& keysToRemove = iter.value(); - - if (!result.contains(group)) continue; - - for (auto jt = keysToRemove.constBegin(); jt != keysToRemove.constEnd(); ++jt) { - result[group].remove(jt.key()); - } - - // Remove the group entirely if it's now empty - if (result[group].isEmpty()) { - result.remove(group); - } - } - return result; -} - -// Some NMSettingsMap settings remain QDBusArguments after autodemarshalling. -// Manually demarshall these for any complex signature we have registered. -void manualSettingDemarshall(NMSettingsMap& map) { - auto demarshallValue = [](const QVariant& value) -> QVariant { - if (value.userType() != qMetaTypeId()) { - return value; - } - - auto arg = value.value(); - auto signature = arg.currentSignature(); - - if (signature == "ay") return QVariant::fromValue(qdbus_cast(arg)); - if (signature == "aay") return QVariant::fromValue(qdbus_cast>(arg)); - if (signature == "au") return QVariant::fromValue(qdbus_cast>(arg)); - if (signature == "aau") return QVariant::fromValue(qdbus_cast>>(arg)); - if (signature == "aa{sv}") return QVariant::fromValue(qdbus_cast>(arg)); - if (signature == "a(ayuay)") return QVariant::fromValue(qdbus_cast>(arg)); - if (signature == "a(ayuayu)") return QVariant::fromValue(qdbus_cast>(arg)); - - return value; - }; - - for (auto it = map.begin(); it != map.end(); ++it) - for (auto jt = it.value().begin(); jt != it.value().end(); ++jt) - jt.value() = demarshallValue(jt.value()); -} - -// Some NMSettingsMap setting types can't be expressed in QML. -// Convert these settings to their correct type or return an invalid QVariant. -QVariant settingTypeFromQml(const QString& group, const QString& key, const QVariant& value) { - auto s = group + "." + key; - - // QString -> QByteArray - if (s == "802-1x.ca-cert" || s == "802-1x.client-cert" || s == "802-1x.private-key" - || s == "802-1x.password-raw" || s == "802-1x.phase2-ca-cert" - || s == "802-1x.phase2-client-cert" || s == "802-1x.phase2-private-key" - || s == "802-11-wireless.ssid") - { - if (value.typeId() == QMetaType::QString) { - return value.toString().toUtf8(); - } - if (value.typeId() == QMetaType::QByteArray) { - return value; - } - return QVariant(); - } - - // QVariantList -> QList - if (s == "ipv6.dns") { - if (value.typeId() == QMetaType::QVariantList) { - QList r; - for (const auto& v: value.toList()) { - if (v.typeId() == QMetaType::QString) { - r.append(v.toString().toUtf8()); - } else { - r.append(v.toByteArray()); - } - } - return QVariant::fromValue(r); - } - return QVariant(); - } - - // QVariantList -> QList - if (s == "ipv4.dns") { - if (value.typeId() == QMetaType::QVariantList) { - QList r; - for (const auto& v: value.toList()) { - r.append(v.value()); - } - return QVariant::fromValue(r); - } - return QVariant(); - } - - // QVariantList -> QList> - if (s == "ipv4.addresses" || s == "ipv4.routes") { - if (value.typeId() == QMetaType::QVariantList) { - QList> r; - for (const auto& v: value.toList()) { - if (v.typeId() != QMetaType::QVariantList) { - continue; - } - QList inner; - for (const auto& u: v.toList()) { - inner.append(u.value()); - } - r.append(inner); - } - return QVariant::fromValue(r); - } - return QVariant(); - } - - // QVariantList -> QList - if (s == "ipv4.address-data" || s == "ipv4.route-data" || s == "ipv4.routing-rules" - || s == "ipv6.address-data" || s == "ipv6.route-data" || s == "ipv6.routing-rules") - { - if (value.typeId() == QMetaType::QVariantList) { - QList r; - for (const auto& v: value.toList()) { - if (!v.canConvert()) { - continue; - } - r.append(v.toMap()); - } - return QVariant::fromValue(r); - } - return QVariant(); - } - - // QVariantList -> QList - if (s == "ipv6.addresses") { - if (value.typeId() == QMetaType::QVariantList) { - QList r; - for (const auto& v: value.toList()) { - if (v.typeId() != QMetaType::QVariantList) { - continue; - } - auto fields = v.toList(); - if (fields.size() != 3) { - continue; - } - const QByteArray address = fields[0].typeId() == QMetaType::QString - ? fields[0].toString().toUtf8() - : fields[0].toByteArray(); - const QByteArray gateway = fields[2].typeId() == QMetaType::QString - ? fields[2].toString().toUtf8() - : fields[2].toByteArray(); - r.append({.address = address, .prefix = fields[1].value(), .gateway = gateway}); - } - return QVariant::fromValue(r); - } - return QVariant(); - } - - // QVariantList -> QList - if (s == "ipv6.routes") { - if (value.typeId() == QMetaType::QVariantList) { - QList r; - for (const auto& v: value.toList()) { - if (v.typeId() != QMetaType::QVariantList) { - continue; - } - auto fields = v.toList(); - if (fields.size() != 4) { - continue; - } - const QByteArray destination = fields[0].typeId() == QMetaType::QString - ? fields[0].toString().toUtf8() - : fields[0].toByteArray(); - const QByteArray nexthop = fields[2].typeId() == QMetaType::QString - ? fields[2].toString().toUtf8() - : fields[2].toByteArray(); - r.append( - {.destination = destination, - .prefix = fields[1].value(), - .nexthop = nexthop, - .metric = fields[3].value()} - ); - } - return QVariant::fromValue(r); - } - return QVariant(); - } - - // QVariantList -> QStringList - if (s == "connection.permissions" || s == "ipv4.dns-search" || s == "ipv6.dns-search" - || s == "802-11-wireless.seen-bssids") - { - if (value.typeId() == QMetaType::QVariantList) { - QStringList stringList; - for (const auto& item: value.toList()) { - stringList.append(item.toString()); - } - return stringList; - } - return QVariant(); - } - - // double (whole number) -> qint32 - if (value.typeId() == QMetaType::Double) { - auto num = value.toDouble(); - if (std::isfinite(num) && num == std::trunc(num)) { - return QVariant::fromValue(static_cast(num)); - } - } - - return value; -} - -// Some NMSettingsMap setting types must be converted to a type that is supported by QML. -// Although QByteArrays can be represented in QML, we convert them to strings for convenience. -QVariant settingTypeToQml(const QVariant& value) { - // QByteArray -> QString - if (value.typeId() == QMetaType::QByteArray) { - return QString::fromUtf8(value.toByteArray()); - } - - // QList -> QVariantList - if (value.userType() == qMetaTypeId>()) { - QVariantList out; - for (const auto& ba: value.value>()) { - out.append(QString::fromUtf8(ba)); - } - return out; - } - - // QList -> QVariantList - if (value.userType() == qMetaTypeId>()) { - QVariantList out; - for (const auto& addr: value.value>()) { - out.append( - QVariant::fromValue( - QVariantList { - QString::fromUtf8(addr.address), - addr.prefix, - QString::fromUtf8(addr.gateway), - } - ) - ); - } - return out; - } - - // QList -> QVariantList - if (value.userType() == qMetaTypeId>()) { - QVariantList out; - for (const auto& route: value.value>()) { - out.append( - QVariant::fromValue( - QVariantList { - QString::fromUtf8(route.destination), - route.prefix, - QString::fromUtf8(route.nexthop), - route.metric, - } - ) - ); - } - return out; - } - - return value; -} - -// NOLINTBEGIN -QDateTime clockBootTimeToDateTime(qint64 clockBootTime) { - clockid_t clkId = CLOCK_BOOTTIME; - struct timespec tp {}; - - const QDateTime now = QDateTime::currentDateTime(); - int r = clock_gettime(clkId, &tp); - if (r == -1 && errno == EINVAL) { - clkId = CLOCK_MONOTONIC; - r = clock_gettime(clkId, &tp); - } - - // Convert to milliseconds - const qint64 nowInMs = tp.tv_sec * 1000 + tp.tv_nsec / 1000000; - - // Return a QDateTime of the millisecond diff - const qint64 offset = clockBootTime - nowInMs; - return QDateTime::fromMSecsSinceEpoch(now.toMSecsSinceEpoch() + offset); -} -// NOLINTEND - -} // namespace qs::network diff --git a/src/network/nm/utils.hpp b/src/network/nm/utils.hpp deleted file mode 100644 index 8c51423..0000000 --- a/src/network/nm/utils.hpp +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "../enums.hpp" -#include "dbus_types.hpp" -#include "enums.hpp" - -namespace qs::network { - -WifiSecurityType::Enum securityFromSettingsMap(const NMSettingsMap& settings); - -bool deviceSupportsApCiphers( - NMWirelessCapabilities::Enum caps, - NM80211ApSecurityFlags::Enum apFlags, - WifiSecurityType::Enum type -); - -// In sync with NetworkManager/libnm-core/nm-utils.c:nm_utils_security_valid() -// Given a set of device capabilities, and a desired security type to check -// against, determines whether the combination of device, desired security type, -// and AP capabilities intersect. -bool securityIsValid( - WifiSecurityType::Enum type, - NMWirelessCapabilities::Enum caps, - bool adhoc, - NM80211ApFlags::Enum apFlags, - NM80211ApSecurityFlags::Enum apWpa, - NM80211ApSecurityFlags::Enum apRsn -); - -WifiSecurityType::Enum findBestWirelessSecurity( - NMWirelessCapabilities::Enum caps, - bool adHoc, - NM80211ApFlags::Enum apFlags, - NM80211ApSecurityFlags::Enum apWpa, - NM80211ApSecurityFlags::Enum apRsn -); - -NMSettingsMap mergeSettingsMaps(const NMSettingsMap& target, const NMSettingsMap& source); - -NMSettingsMap removeSettingsInMap(const NMSettingsMap& target, const NMSettingsMap& toRemove); - -void manualSettingDemarshall(NMSettingsMap& map); - -QVariant settingTypeFromQml(const QString& group, const QString& key, const QVariant& value); - -QVariant settingTypeToQml(const QVariant& value); - -QDateTime clockBootTimeToDateTime(qint64 clockBootTime); - -} // namespace qs::network diff --git a/src/network/nm/wireless.cpp b/src/network/nm/wireless.cpp deleted file mode 100644 index 5f55bed..0000000 --- a/src/network/nm/wireless.cpp +++ /dev/null @@ -1,554 +0,0 @@ -#include "wireless.hpp" -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../core/logcat.hpp" -#include "../../dbus/properties.hpp" -#include "../enums.hpp" -#include "../wifi.hpp" -#include "accesspoint.hpp" -#include "active_connection.hpp" -#include "dbus_nm_wireless.h" -#include "dbus_types.hpp" -#include "device.hpp" -#include "enums.hpp" -#include "settings.hpp" -#include "utils.hpp" - -namespace qs::network { -using namespace qs::dbus; - -namespace { -QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg); -} - -NMWirelessNetwork::NMWirelessNetwork(QString ssid, QObject* parent) - : QObject(parent) - , mSsid(std::move(ssid)) - , bKnown(false) - , bSecurity(WifiSecurityType::Unknown) - , bReason(NMConnectionStateReason::None) - , bState(NMConnectionState::Deactivated) {} - -void NMWirelessNetwork::updateReferenceSettings() { - // If the network has no connections, the reference is nullptr. - if (this->mSettings.isEmpty()) { - this->mReferenceSettings = nullptr; - this->bSecurity = WifiSecurityType::Unknown; - if (this->mReferenceAp) { - this->bSecurity.setBinding([this]() { return this->mReferenceAp->security(); }); - } - return; - }; - - // If the network has an active connection, use its settings as the reference. - if (this->mActiveConnection) { - auto* settings = this->mSettings.value(this->mActiveConnection->connection().path()); - if (settings && settings != this->mReferenceSettings) { - this->mReferenceSettings = settings; - this->bSecurity.setBinding([settings]() { return securityFromSettingsMap(settings->map()); }); - } - return; - } - - // Otherwise, choose the settings responsible for the last successful connection. - NMSettings* selectedSettings = nullptr; - quint64 selectedTimestamp = 0; - for (auto* settings: this->mSettings.values()) { - const quint64 timestamp = settings->map()["connection"]["timestamp"].toULongLong(); - if (!selectedSettings || timestamp > selectedTimestamp) { - selectedSettings = settings; - selectedTimestamp = timestamp; - } - } - - if (this->mReferenceSettings != selectedSettings) { - this->mReferenceSettings = selectedSettings; - this->bSecurity.setBinding([selectedSettings]() { - return securityFromSettingsMap(selectedSettings->map()); - }); - } -} - -void NMWirelessNetwork::updateReferenceAp() { - // If the network has no APs, the reference is a nullptr. - if (this->mAccessPoints.isEmpty()) { - this->mReferenceAp = nullptr; - this->bSignalStrength = 0; - return; - } - - // Otherwise, choose the AP with the strongest signal. - NMAccessPoint* selectedAp = nullptr; - for (auto* ap: this->mAccessPoints.values()) { - // Always prefer the active AP. - if (ap->path() == this->bActiveApPath) { - selectedAp = ap; - break; - } - if (!selectedAp || ap->signalStrength() > selectedAp->signalStrength()) { - selectedAp = ap; - } - } - if (this->mReferenceAp != selectedAp) { - this->mReferenceAp = selectedAp; - this->bSignalStrength.setBinding([selectedAp]() { return selectedAp->signalStrength(); }); - // Reference AP is used for security when there's no connection settings. - if (!this->mReferenceSettings) { - this->bSecurity.setBinding([selectedAp]() { return selectedAp->security(); }); - } - } -} - -void NMWirelessNetwork::addAccessPoint(NMAccessPoint* ap) { - if (this->mAccessPoints.contains(ap->path())) return; - this->mAccessPoints.insert(ap->path(), ap); - auto onDestroyed = [this, ap]() { - if (this->mAccessPoints.take(ap->path())) { - this->updateReferenceAp(); - if (this->mAccessPoints.isEmpty() && this->mSettings.isEmpty()) emit this->disappeared(); - } - }; - // clang-format off - QObject::connect(ap, &NMAccessPoint::signalStrengthChanged, this, &NMWirelessNetwork::updateReferenceAp); - QObject::connect(ap, &NMAccessPoint::destroyed, this, onDestroyed); - // clang-format on - this->updateReferenceAp(); -}; - -void NMWirelessNetwork::addSettings(NMSettings* settings) { - if (this->mSettings.contains(settings->path())) return; - this->mSettings.insert(settings->path(), settings); - - auto onDestroyed = [this, settings]() { - if (this->mSettings.take(settings->path())) { - emit this->settingsRemoved(settings); - this->updateReferenceSettings(); - if (this->mSettings.isEmpty()) this->bKnown = false; - if (this->mAccessPoints.isEmpty() && this->mSettings.isEmpty()) emit this->disappeared(); - } - }; - QObject::connect(settings, &NMSettings::destroyed, this, onDestroyed); - this->bKnown = true; - this->updateReferenceSettings(); - emit this->settingsAdded(settings); -}; - -void NMWirelessNetwork::addActiveConnection(NMActiveConnection* active) { - if (this->mActiveConnection) return; - this->mActiveConnection = active; - - this->bState.setBinding([active]() { return active->state(); }); - this->bReason.setBinding([active]() { return active->stateReason(); }); - auto onDestroyed = [this, active]() { - if (this->mActiveConnection && this->mActiveConnection == active) { - this->mActiveConnection = nullptr; - this->updateReferenceSettings(); - this->bState = NMConnectionState::Deactivated; - this->bReason = NMConnectionStateReason::None; - } - }; - QObject::connect(active, &NMActiveConnection::destroyed, this, onDestroyed); - this->updateReferenceSettings(); -}; - -void NMWirelessNetwork::forget() { - if (this->mSettings.isEmpty()) return; - for (auto* conn: this->mSettings.values()) { - conn->forget(); - } -} - -NMWirelessDevice::NMWirelessDevice(const QString& path, QObject* parent) - : NMDevice(path, parent) - , mScanTimer(this) { - this->wirelessProxy = new DBusNMWirelessProxy( - "org.freedesktop.NetworkManager", - path, - QDBusConnection::systemBus(), - this - ); - - if (!this->wirelessProxy->isValid()) { - qCWarning(logNetworkManager) << "Cannot create DBus interface for wireless device at" << path; - return; - } - - QObject::connect( - &this->wirelessProperties, - &DBusPropertyGroup::getAllFinished, - this, - &NMWirelessDevice::initWireless, - Qt::SingleShotConnection - ); - - QObject::connect(&this->mScanTimer, &QTimer::timeout, this, &NMWirelessDevice::onScanTimeout); - this->mScanTimer.setSingleShot(true); - - this->wirelessProperties.setInterface(this->wirelessProxy); - this->wirelessProperties.updateAllViaGetAll(); -} - -void NMWirelessDevice::initWireless() { - // clang-format off - QObject::connect(this->wirelessProxy, &DBusNMWirelessProxy::AccessPointAdded, this, &NMWirelessDevice::onAccessPointAdded); - QObject::connect(this->wirelessProxy, &DBusNMWirelessProxy::AccessPointRemoved, this, &NMWirelessDevice::onAccessPointRemoved); - QObject::connect(this, &NMWirelessDevice::accessPointLoaded, this, &NMWirelessDevice::onAccessPointLoaded); - QObject::connect(this, &NMWirelessDevice::settingsLoaded, this, &NMWirelessDevice::onSettingsLoaded); - QObject::connect(this, &NMWirelessDevice::activeConnectionLoaded, this, &NMWirelessDevice::onActiveConnectionLoaded); - QObject::connect(this, &NMWirelessDevice::scanningChanged, this, &NMWirelessDevice::onScanningChanged); - // clang-format on - this->registerAccessPoints(); -} - -void NMWirelessDevice::onAccessPointAdded(const QDBusObjectPath& path) { - this->registerAccessPoint(path.path()); -} - -void NMWirelessDevice::onAccessPointRemoved(const QDBusObjectPath& path) { - auto* ap = this->mAccessPoints.take(path.path()); - if (!ap) { - qCDebug(logNetworkManager) << "Sent removal signal for" << path.path() - << "which is not registered."; - return; - } - qCDebug(logNetworkManager) << "Access point removed:" << path.path(); - delete ap; -} - -void NMWirelessDevice::onAccessPointLoaded(NMAccessPoint* ap) { - const QString ssid = ap->ssid(); - if (!ssid.isEmpty()) { - auto mode = ap->mode(); - if (mode == NM80211Mode::Infra) { - auto* net = this->mNetworks.value(ssid); - if (!net) net = this->registerNetwork(ssid); - net->addAccessPoint(ap); - } - } -} - -void NMWirelessDevice::onSettingsLoaded(NMSettings* settings) { - const NMSettingsMap& map = settings->map(); - // Filter connections that aren't wireless or have missing settings - if (map["connection"]["id"].toString().isEmpty() || map["connection"]["uuid"].toString().isEmpty() - || !map.contains("802-11-wireless") || map["802-11-wireless"]["ssid"].toString().isEmpty()) - { - return; - } - - const auto ssid = map["802-11-wireless"]["ssid"].toString(); - const auto mode = map["802-11-wireless"]["mode"].toString(); - - if (mode == "infrastructure") { - auto* net = this->mNetworks.value(ssid); - if (!net) net = this->registerNetwork(ssid); - net->addSettings(settings); - - // Check for active connections that loaded before their respective connection settings - auto* active = this->activeConnection(); - if (active && settings->path() == active->connection().path()) { - net->addActiveConnection(active); - } - } - // TODO: Create hotspots when mode == "ap" -} - -void NMWirelessDevice::onActiveConnectionLoaded(NMActiveConnection* active) { - // Find an exisiting network with connection settings that matches the active - const QString activeConnPath = active->connection().path(); - for (const auto& net: this->mNetworks.values()) { - for (auto* settings: net->settings()) { - if (activeConnPath == settings->path()) { - net->addActiveConnection(active); - return; - } - } - } -} - -void NMWirelessDevice::onScanTimeout() { - const QDateTime now = QDateTime::currentDateTime(); - const QDateTime lastScan = this->bLastScan; - const QDateTime lastScanRequest = this->mLastScanRequest; - - if (lastScan.isValid() && lastScan.msecsTo(now) < this->mScanIntervalMs) { - // Rate limit if backend last scan property updated within the interval - auto diff = static_cast(this->mScanIntervalMs - lastScan.msecsTo(now)); - this->mScanTimer.start(diff); - } else if (lastScanRequest.isValid() && lastScanRequest.msecsTo(now) < this->mScanIntervalMs) { - // Rate limit if frontend changes scanner state within the interval - auto diff = static_cast(this->mScanIntervalMs - lastScanRequest.msecsTo(now)); - this->mScanTimer.start(diff); - } else { - this->wirelessProxy->RequestScan({}); - this->mLastScanRequest = now; - this->mScanTimer.start(this->mScanIntervalMs); - } -} - -void NMWirelessDevice::onScanningChanged(bool scanning) { - scanning ? this->onScanTimeout() : this->mScanTimer.stop(); -} - -void NMWirelessDevice::registerAccessPoints() { - auto pending = this->wirelessProxy->GetAllAccessPoints(); - auto* call = new QDBusPendingCallWatcher(pending, this); - - auto responseCallback = [this](QDBusPendingCallWatcher* call) { - const QDBusPendingReply> reply = *call; - - if (reply.isError()) { - qCWarning(logNetworkManager) - << "Failed to get all access points: " << reply.error().message(); - } else { - for (const QDBusObjectPath& devicePath: reply.value()) { - this->registerAccessPoint(devicePath.path()); - } - } - - delete call; - }; - - QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); -} - -void NMWirelessDevice::registerAccessPoint(const QString& path) { - if (this->mAccessPoints.contains(path)) { - qCDebug(logNetworkManager) << "Skipping duplicate registration of access point" << path; - return; - } - - auto* ap = new NMAccessPoint(path, this); - - if (!ap->isValid()) { - qCWarning(logNetworkManager) << "Ignoring invalid registration of" << path; - delete ap; - return; - } - - qCDebug(logNetworkManager) << "Access point added:" << path; - this->mAccessPoints.insert(path, ap); - QObject::connect( - ap, - &NMAccessPoint::loaded, - this, - [this, ap]() { emit this->accessPointLoaded(ap); }, - Qt::SingleShotConnection - ); - ap->bindableSecurity().setBinding([this, ap]() { - return findBestWirelessSecurity( - this->bCapabilities, - ap->mode() == NM80211Mode::Adhoc, - ap->flags(), - ap->wpaFlags(), - ap->rsnFlags() - ); - }); -} - -NMWirelessNetwork* NMWirelessDevice::registerNetwork(const QString& ssid) { - auto* net = new NMWirelessNetwork(ssid, this); - - auto visible = [this, net]() { - return this->bScanning || net->state() == NMConnectionState::Activated || net->known(); - }; - - net->bindableVisible().setBinding(visible); - net->bindableActiveApPath().setBinding([this]() { return this->activeApPath().path(); }); - net->bindableDeviceFailReason().setBinding([this]() { return this->lastFailReason(); }); - QObject::connect(net, &NMWirelessNetwork::disappeared, this, &NMWirelessDevice::removeNetwork); - - qCDebug(logNetworkManager) << "Registered network for SSID" << ssid; - this->mNetworks.insert(ssid, net); - this->registerFrontendNetwork(net); - return net; -} - -void NMWirelessDevice::registerFrontendNetwork(NMWirelessNetwork* net) { - auto ssid = net->ssid(); - auto* frontendNet = new WifiNetwork(ssid, net); - - // Bind WifiNetwork to NMWirelessNetwork - auto translateSignal = [net]() { return net->signalStrength() / 100.0; }; - auto translateState = [net]() { return net->state() == NMConnectionState::Activated; }; - frontendNet->bindableSignalStrength().setBinding(translateSignal); - frontendNet->bindableConnected().setBinding(translateState); - frontendNet->bindableKnown().setBinding([net]() { return net->known(); }); - frontendNet->bindableSecurity().setBinding([net]() { return net->security(); }); - frontendNet->bindableState().setBinding([net]() { - return static_cast(net->state()); - }); - - QObject::connect(net, &NMWirelessNetwork::reasonChanged, this, [net, frontendNet]() { - if (net->reason() == NMConnectionStateReason::DeviceDisconnected) { - auto deviceReason = net->deviceFailReason(); - if (deviceReason == NMDeviceStateReason::NoSecrets) - emit frontendNet->connectionFailed(ConnectionFailReason::NoSecrets); - if (deviceReason == NMDeviceStateReason::SupplicantDisconnect) - emit frontendNet->connectionFailed(ConnectionFailReason::WifiClientDisconnected); - if (deviceReason == NMDeviceStateReason::SupplicantFailed) - emit frontendNet->connectionFailed(ConnectionFailReason::WifiClientFailed); - if (deviceReason == NMDeviceStateReason::SupplicantTimeout) - emit frontendNet->connectionFailed(ConnectionFailReason::WifiAuthTimeout); - if (deviceReason == NMDeviceStateReason::SsidNotFound) - emit frontendNet->connectionFailed(ConnectionFailReason::WifiNetworkLost); - } - }); - - QObject::connect(frontendNet, &WifiNetwork::requestConnect, this, [this, net]() { - if (net->referenceSettings()) { - emit this->activateConnection( - QDBusObjectPath(net->referenceSettings()->path()), - QDBusObjectPath(this->path()) - ); - return; - } - if (net->referenceAp()) { - emit this->addAndActivateConnection( - NMSettingsMap(), - QDBusObjectPath(this->path()), - QDBusObjectPath(net->referenceAp()->path()) - ); - return; - } - qCInfo(logNetworkManager) << "Failed to connect to" - << this->path() + ": The network disappeared."; - }); - - QObject::connect( - frontendNet, - &WifiNetwork::requestConnectWithPsk, - this, - [this, net](const QString& psk) { - NMSettingsMap settings; - settings["802-11-wireless-security"]["psk"] = psk; - if (const QPointer ref = net->referenceSettings()) { - auto* call = ref->updateSettings(settings); - QObject::connect( - call, - &QDBusPendingCallWatcher::finished, - this, - [this, ref](QDBusPendingCallWatcher* call) { - const QDBusPendingReply<> reply = *call; - - if (reply.isError()) { - qCInfo(logNetworkManager) - << "Failed to write PSK for" << this->path() + ":" << reply.error().message(); - } else { - if (!ref) { - qCInfo(logNetworkManager) << "Failed to connectWithPsk to" - << this->path() + ": The settings disappeared."; - } else { - emit this->activateConnection( - QDBusObjectPath(ref->path()), - QDBusObjectPath(this->path()) - ); - } - } - delete call; - } - ); - return; - } - if (net->referenceAp()) { - emit this->addAndActivateConnection( - settings, - QDBusObjectPath(this->path()), - QDBusObjectPath(net->referenceAp()->path()) - ); - return; - } - qCInfo(logNetworkManager) << "Failed to connectWithPsk to" - << this->path() + ": The network disappeared."; - } - ); - - QObject::connect( - frontendNet, - &WifiNetwork::requestConnectWithSettings, - this, - [this](NMSettings* settings) { - if (settings) { - emit this->activateConnection( - QDBusObjectPath(settings->path()), - QDBusObjectPath(this->path()) - ); - return; - } - qCInfo(logNetworkManager) << "Failed to connectWithSettings to" - << this->path() + ": The provided settings no longer exist."; - } - ); - - QObject::connect( - net, - &NMWirelessNetwork::visibilityChanged, - this, - [this, frontendNet](bool visible) { - if (visible) this->networkAdded(frontendNet); - else this->networkRemoved(frontendNet); - } - ); - - // clang-format off - QObject::connect(frontendNet, &WifiNetwork::requestDisconnect, this, &NMWirelessDevice::disconnect); - QObject::connect(frontendNet, &WifiNetwork::requestForget, net, &NMWirelessNetwork::forget); - QObject::connect(net, &NMWirelessNetwork::settingsAdded, frontendNet, &WifiNetwork::settingsAdded); - QObject::connect(net, &NMWirelessNetwork::settingsRemoved, frontendNet, &WifiNetwork::settingsRemoved); - // clang-format on - - this->mFrontendNetworks.insert(ssid, frontendNet); - if (net->visible()) emit this->networkAdded(frontendNet); -} - -void NMWirelessDevice::removeFrontendNetwork(NMWirelessNetwork* net) { - auto* frontendNet = this->mFrontendNetworks.take(net->ssid()); - if (frontendNet) { - if (net->visible()) emit this->networkRemoved(frontendNet); - frontendNet->deleteLater(); - } -} - -void NMWirelessDevice::removeNetwork() { - auto* net = qobject_cast(this->sender()); - if (this->mNetworks.take(net->ssid())) { - this->removeFrontendNetwork(net); - delete net; - }; -} - -bool NMWirelessDevice::isValid() const { - return this->NMDevice::isValid() && (this->wirelessProxy && this->wirelessProxy->isValid()); -} - -} // namespace qs::network - -namespace qs::dbus { - -DBusResult -DBusDataTransform::fromWire(quint32 wire) { - return DBusResult(static_cast(wire)); -} - -DBusResult DBusDataTransform::fromWire(qint64 wire) { - return DBusResult(qs::network::clockBootTimeToDateTime(wire)); -} - -} // namespace qs::dbus diff --git a/src/network/nm/wireless.hpp b/src/network/nm/wireless.hpp deleted file mode 100644 index 94ce754..0000000 --- a/src/network/nm/wireless.hpp +++ /dev/null @@ -1,173 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "../wifi.hpp" -#include "accesspoint.hpp" -#include "active_connection.hpp" -#include "dbus_nm_wireless.h" -#include "device.hpp" -#include "enums.hpp" -#include "settings.hpp" - -namespace qs::dbus { -template <> -struct DBusDataTransform { - using Wire = quint32; - using Data = qs::network::NMWirelessCapabilities::Enum; - static DBusResult fromWire(Wire wire); -}; - -template <> -struct DBusDataTransform { - using Wire = qint64; - using Data = QDateTime; - static DBusResult fromWire(Wire wire); -}; - -} // namespace qs::dbus -namespace qs::network { - -// NMWirelessNetwork aggregates all related NMActiveConnection, NMAccessPoint, and NMSettings objects. -class NMWirelessNetwork: public QObject { - Q_OBJECT; - -public: - explicit NMWirelessNetwork(QString ssid, QObject* parent = nullptr); - - void addAccessPoint(NMAccessPoint* ap); - void addSettings(NMSettings* settings); - void addActiveConnection(NMActiveConnection* active); - void forget(); - - // clang-format off - [[nodiscard]] QString ssid() const { return this->mSsid; } - [[nodiscard]] quint8 signalStrength() const { return this->bSignalStrength; } - [[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; } - [[nodiscard]] NMConnectionState::Enum state() const { return this->bState; } - [[nodiscard]] bool known() const { return this->bKnown; } - [[nodiscard]] NMConnectionStateReason::Enum reason() const { return this->bReason; } - QBindable bindableDeviceFailReason() { return &this->bDeviceFailReason; } - [[nodiscard]] NMDeviceStateReason::Enum deviceFailReason() const { return this->bDeviceFailReason; } - [[nodiscard]] NMAccessPoint* referenceAp() const { return this->mReferenceAp; } - [[nodiscard]] QList accessPoints() const { return this->mAccessPoints.values(); } - [[nodiscard]] QList settings() const { return this->mSettings.values(); } - [[nodiscard]] NMSettings* referenceSettings() const { return this->mReferenceSettings; } - QBindable bindableActiveApPath() { return &this->bActiveApPath; } - QBindable bindableVisible() { return &this->bVisible; } - bool visible() const { return this->bVisible; } - // clang-format on - -signals: - void disappeared(); - void settingsAdded(NMSettings* settings); - void settingsRemoved(NMSettings* settings); - void visibilityChanged(bool visible); - void signalStrengthChanged(quint8 signal); - void stateChanged(NMConnectionState::Enum state); - void knownChanged(bool known); - void securityChanged(WifiSecurityType::Enum security); - void reasonChanged(NMConnectionStateReason::Enum reason); - void deviceFailReasonChanged(NMDeviceStateReason::Enum reason); - void capabilitiesChanged(NMWirelessCapabilities::Enum caps); - void activeApPathChanged(QString path); - -private: - void updateReferenceAp(); - void updateReferenceSettings(); - - QString mSsid; - QHash mAccessPoints; - QHash mSettings; - NMAccessPoint* mReferenceAp = nullptr; - NMSettings* mReferenceSettings = nullptr; - NMActiveConnection* mActiveConnection = nullptr; - - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, bool, bVisible, &NMWirelessNetwork::visibilityChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, bool, bKnown, &NMWirelessNetwork::knownChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, WifiSecurityType::Enum, bSecurity, &NMWirelessNetwork::securityChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, NMConnectionStateReason::Enum, bReason, &NMWirelessNetwork::reasonChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, NMConnectionState::Enum, bState, &NMWirelessNetwork::stateChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, NMDeviceStateReason::Enum, bDeviceFailReason, &NMWirelessNetwork::deviceFailReasonChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, quint8, bSignalStrength, &NMWirelessNetwork::signalStrengthChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, QString, bActiveApPath, &NMWirelessNetwork::activeApPathChanged); - // clang-format on -}; - -// Proxy of a /org/freedesktop/NetworkManager/Device/* object. -// Extends NMDevice to also include members from the org.freedesktop.NetworkManager.Device.Wireless interface -// Owns the lifetime of NMAccessPoints(s), NMWirelessNetwork(s), frontend WifiNetwork(s). -class NMWirelessDevice: public NMDevice { - Q_OBJECT; - -public: - explicit NMWirelessDevice(const QString& path, QObject* parent = nullptr); - - [[nodiscard]] bool isValid() const override; - [[nodiscard]] NMWirelessCapabilities::Enum capabilities() { return this->bCapabilities; } - [[nodiscard]] const QDBusObjectPath& activeApPath() { return this->bActiveAccessPoint; } - [[nodiscard]] NM80211Mode::Enum mode() { return this->bMode; } - [[nodiscard]] QBindable bindableScanning() { return &this->bScanning; } - -signals: - void accessPointLoaded(NMAccessPoint* ap); - void accessPointRemoved(NMAccessPoint* ap); - void networkAdded(WifiNetwork* net); - void networkRemoved(WifiNetwork* net); - void lastScanChanged(QDateTime lastScan); - void scanningChanged(bool scanning); - void capabilitiesChanged(NMWirelessCapabilities::Enum caps); - void activeAccessPointChanged(const QDBusObjectPath& path); - void modeChanged(NM80211Mode::Enum mode); - -private slots: - void onAccessPointAdded(const QDBusObjectPath& path); - void onAccessPointRemoved(const QDBusObjectPath& path); - void onAccessPointLoaded(NMAccessPoint* ap); - void onSettingsLoaded(NMSettings* settings); - void onActiveConnectionLoaded(NMActiveConnection* active); - void onScanTimeout(); - void onScanningChanged(bool scanning); - -private: - void registerAccessPoint(const QString& path); - void registerFrontendNetwork(NMWirelessNetwork* net); - void removeFrontendNetwork(NMWirelessNetwork* net); - void removeNetwork(); - bool checkVisibility(WifiNetwork* net); - void registerAccessPoints(); - void initWireless(); - NMWirelessNetwork* registerNetwork(const QString& ssid); - - QHash mAccessPoints; - QHash mNetworks; - QHash mFrontendNetworks; - - QDateTime mLastScanRequest; - QTimer mScanTimer; - qint32 mScanIntervalMs = 10001; - - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessDevice, bool, bScanning, &NMWirelessDevice::scanningChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessDevice, QDateTime, bLastScan, &NMWirelessDevice::lastScanChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessDevice, NMWirelessCapabilities::Enum, bCapabilities, &NMWirelessDevice::capabilitiesChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessDevice, QDBusObjectPath, bActiveAccessPoint, &NMWirelessDevice::activeAccessPointChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessDevice, NM80211Mode::Enum, bMode, &NMWirelessDevice::modeChanged); - - QS_DBUS_BINDABLE_PROPERTY_GROUP(NMWireless, wirelessProperties); - QS_DBUS_PROPERTY_BINDING(NMWirelessDevice, pLastScan, bLastScan, wirelessProperties, "LastScan"); - QS_DBUS_PROPERTY_BINDING(NMWirelessDevice, pCapabilities, bCapabilities, wirelessProperties, "WirelessCapabilities"); - QS_DBUS_PROPERTY_BINDING(NMWirelessDevice, pActiveAccessPoint, bActiveAccessPoint, wirelessProperties, "ActiveAccessPoint"); - QS_DBUS_PROPERTY_BINDING(NMWirelessDevice, pMode, bMode, wirelessProperties, "Mode"); - // clang-format on - - DBusNMWirelessProxy* wirelessProxy = nullptr; -}; - -} // namespace qs::network diff --git a/src/network/test/manual/network.qml b/src/network/test/manual/network.qml deleted file mode 100644 index eadc159..0000000 --- a/src/network/test/manual/network.qml +++ /dev/null @@ -1,360 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import Quickshell -import Quickshell.Widgets -import Quickshell.Networking - -Scope { - Component { - id: editorComponent - FloatingWindow { - id: editorWindow - required property var nmSettings - color: contentItem.palette.window - - Component.onCompleted: editorArea.text = JSON.stringify(nmSettings.read(), null, 2) - - ColumnLayout { - anchors.fill: parent - anchors.margins: 10 - - Label { - text: "Editing " + nmSettings?.id + " (" + nmSettings?.uuid + ")" - font.bold: true - font.pointSize: 12 - } - - ScrollView { - Layout.fillWidth: true - Layout.fillHeight: true - TextArea { - id: editorArea - wrapMode: TextEdit.Wrap - selectByMouse: true - } - } - - RowLayout { - Layout.fillWidth: true - Label { - id: statusLabel - Layout.fillWidth: true - color: palette.placeholderText - } - Button { - text: "Reload" - onClicked: { - editorArea.text = JSON.stringify(editorWindow.nmSettings.read(), null, 2); - statusLabel.text = "Reloaded"; - } - } - Button { - text: "Save" - onClicked: { - try { - const parsed = JSON.parse(editorArea.text); - nmSettings.write(parsed); - statusLabel.text = "Saved"; - } catch (e) { - statusLabel.text = "Parse error: " + e.message; - } - } - } - Button { - text: "Close" - onClicked: { - editorArea.focus = false; - editorWindow.destroy(); - } - } - } - } - } - } - - FloatingWindow { - color: contentItem.palette.window - - ColumnLayout { - anchors.fill: parent - anchors.margins: 5 - - ColumnLayout { - Label { - text: `Networking (${NetworkBackendType.toString(Networking.backend)} backend)` - font.bold: true - font.pointSize: 12 - } - RowLayout { - Label { - text: `Connectivity` - font.bold: true - } - Label { - text: `${NetworkConnectivity.toString(Networking.connectivity)}` - visible: Networking.canCheckConnectivity - } - Button { - text: "Re-check" - visible: Networking.canCheckConnectivity && Networking.connectivityCheckEnabled - onClicked: Networking.checkConnectivity() - } - CheckBox { - text: "Checking enabled" - checked: Networking.connectivityCheckEnabled - onClicked: Networking.connectivityCheckEnabled = !Networking.connectivityCheckEnabled - visible: Networking.canCheckConnectivity - } - CheckBox { - enabled: false - text: "Supported" - checked: Networking.canCheckConnectivity - } - } - } - - Column { - Layout.fillWidth: true - RowLayout { - Label { - text: "WiFi" - font.bold: true - } - CheckBox { - text: "Software" - checked: Networking.wifiEnabled - onClicked: Networking.wifiEnabled = !Networking.wifiEnabled - } - CheckBox { - enabled: false - text: "Hardware" - checked: Networking.wifiHardwareEnabled - } - } - } - - ListView { - clip: true - Layout.fillWidth: true - Layout.fillHeight: true - model: Networking.devices - - delegate: WrapperRectangle { - width: parent.width - color: "transparent" - border.color: palette.button - border.width: 1 - margin: 5 - - ColumnLayout { - RowLayout { - Label { - text: modelData.name - font.bold: true - } - Label { - text: modelData.address - } - Label { - text: `(Type: ${DeviceType.toString(modelData.type)})` - } - CheckBox { - text: `Managed` - checked: modelData.nmManaged - onClicked: modelData.nmManaged = !modelData.nmManaged - } - } - RowLayout { - Label { - text: ConnectionState.toString(modelData.state) - color: modelData.connected ? palette.link : palette.placeholderText - } - Button { - visible: modelData.state == ConnectionState.Connected - text: "Disconnect" - onClicked: modelData.disconnect() - } - CheckBox { - text: "Autoconnect" - checked: modelData.autoconnect - onClicked: modelData.autoconnect = !modelData.autoconnect - } - Label { - text: `Mode: ${WifiDeviceMode.toString(modelData.mode)}` - visible: modelData.type == DeviceType.Wifi - } - CheckBox { - text: "Scanner" - checked: modelData.scannerEnabled - onClicked: modelData.scannerEnabled = !modelData.scannerEnabled - visible: modelData.type === DeviceType.Wifi - } - } - - Repeater { - Layout.fillWidth: true - model: ScriptModel { - values: [...modelData.networks.values].sort((a, b) => { - if (a.connected !== b.connected) { - return b.connected - a.connected; - } - return b.signalStrength - a.signalStrength; - }) - } - - WrapperRectangle { - property var chosenSettings: { - const settings = modelData.nmSettings; - if (!settings || settings.length === 0) { - return null; - } - if (settings.length === 1) { - return settings[0]; - } - return settings[settingsComboBox.currentIndex]; - } - - Connections { - target: modelData - function onConnectionFailed(reason) { - failLoader.sourceComponent = failComponent; - failLoader.item.failReason = reason; - } - function onStateChanged() { - if (modelData.state == ConnectionState.Connecting) { - failLoader.sourceComponent = null; - } - } - } - - Component { - id: failComponent - RowLayout { - property var failReason - Label { - text: ConnectionFailReason.toString(failReason) - } - RowLayout { - TextField { - id: pskField - placeholderText: "PSK" - } - Button { - text: "Set" - visible: pskField.visible - onClicked: { - modelData.connectWithPsk(pskField.text); - failLoader.sourceComponent = null; - } - } - visible: modelData.security === WifiSecurityType.WpaPsk || modelData.security === WifiSecurityType.Wpa2Psk || modelData.security === WifiSecurityType.Sae - } - Button { - text: "Close" - onClicked: failLoader.sourceComponent = null - } - } - } - - Layout.fillWidth: true - color: modelData.connected ? palette.highlight : palette.button - border.color: palette.mid - border.width: 1 - margin: 5 - - RowLayout { - ColumnLayout { - Layout.fillWidth: true - RowLayout { - Label { - text: modelData.name - font.bold: true - } - Label { - text: modelData.known ? "Known" : "" - color: palette.placeholderText - } - } - RowLayout { - Label { - text: `Security: ${WifiSecurityType.toString(modelData.security)}` - color: palette.placeholderText - } - Label { - text: `| Signal strength: ${Math.round(modelData.signalStrength * 100)}%` - color: palette.placeholderText - } - } - } - ColumnLayout { - Layout.alignment: Qt.AlignRight - RowLayout { - Layout.alignment: Qt.AlignRight - BusyIndicator { - implicitHeight: 30 - implicitWidth: 30 - running: modelData.stateChanging - visible: modelData.stateChanging - } - Label { - text: ConnectionState.toString(modelData.state) - color: modelData.connected ? palette.link : palette.placeholderText - } - RowLayout { - Label { - text: "Choose settings:" - } - ComboBox { - id: settingsComboBox - model: modelData.nmSettings.map(s => s?.read()?.connection?.id) - currentIndex: 0 - } - visible: modelData.nmSettings.length > 1 - } - Button { - text: "Connect" - onClicked: { - if (chosenSettings) - modelData.connectWithSettings(chosenSettings); - else - modelData.connect(); - } - visible: !modelData.connected - } - Button { - text: "Disconnect" - onClicked: modelData.disconnect() - visible: modelData.connected - } - Button { - text: "Forget" - onClicked: modelData.forget() - visible: modelData.known - } - Button { - text: "Edit" - visible: modelData.known - onClicked: { - if (chosenSettings) - editorComponent.createObject(null, { - nmSettings: chosenSettings - }); - } - } - } - Loader { - id: failLoader - Layout.alignment: Qt.AlignRight - visible: sourceComponent !== null - } - } - } - } - } - } - } - } - } - } -} diff --git a/src/network/wifi.cpp b/src/network/wifi.cpp deleted file mode 100644 index e9939c2..0000000 --- a/src/network/wifi.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include "wifi.hpp" -#include - -#include -#include -#include -#include -#include -#include - -#include "../core/logcat.hpp" -#include "device.hpp" -#include "enums.hpp" -#include "network.hpp" - -namespace qs::network { - -namespace { -QS_LOGGING_CATEGORY(logWifiNetwork, "quickshell.wifinetwork", QtWarningMsg); -} - -WifiNetwork::WifiNetwork(QString ssid, QObject* parent): Network(std::move(ssid), parent) {}; - -void WifiNetwork::connectWithPsk(const QString& psk) { - if (this->bConnected) { - qCCritical(logWifiNetwork) << this << "is already connected."; - return; - } - if (this->bSecurity != WifiSecurityType::WpaPsk && this->bSecurity != WifiSecurityType::Wpa2Psk - && this->bSecurity != WifiSecurityType::Sae) - { - qCCritical(logWifiNetwork) << this << "has the wrong security type for a PSK."; - return; - } - emit this->requestConnectWithPsk(psk); -} - -WifiDevice::WifiDevice(QObject* parent): NetworkDevice(DeviceType::Wifi, parent) {}; - -void WifiDevice::setScannerEnabled(bool enabled) { - if (this->bScannerEnabled == enabled) return; - this->bScannerEnabled = enabled; -} - -void WifiDevice::networkAdded(WifiNetwork* net) { this->mNetworks.insertObject(net); } -void WifiDevice::networkRemoved(WifiNetwork* net) { this->mNetworks.removeObject(net); } - -} // namespace qs::network - -QDebug operator<<(QDebug debug, const qs::network::WifiNetwork* network) { - auto saver = QDebugStateSaver(debug); - - if (network) { - debug.nospace() << "WifiNetwork(" << static_cast(network) - << ", name=" << network->name() << ")"; - } else { - debug << "WifiNetwork(nullptr)"; - } - - return debug; -} - -QDebug operator<<(QDebug debug, const qs::network::WifiDevice* device) { - auto saver = QDebugStateSaver(debug); - - if (device) { - debug.nospace() << "WifiDevice(" << static_cast(device) - << ", name=" << device->name() << ")"; - } else { - debug << "WifiDevice(nullptr)"; - } - - return debug; -} diff --git a/src/network/wifi.hpp b/src/network/wifi.hpp deleted file mode 100644 index c0091f7..0000000 --- a/src/network/wifi.hpp +++ /dev/null @@ -1,99 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "../core/doc.hpp" -#include "../core/model.hpp" -#include "device.hpp" -#include "enums.hpp" -#include "network.hpp" - -namespace qs::network { - -///! WiFi subtype of @@Network. -class WifiNetwork: public Network { - Q_OBJECT; - QML_ELEMENT; - QML_UNCREATABLE("WifiNetwork can only be acquired through WifiDevice"); - // clang-format off - /// The current signal strength of the network, from 0.0 to 1.0. - Q_PROPERTY(qreal signalStrength READ default NOTIFY signalStrengthChanged BINDABLE bindableSignalStrength); - /// The security type of the wifi network. - Q_PROPERTY(WifiSecurityType::Enum security READ default NOTIFY securityChanged BINDABLE bindableSecurity); - // clang-format on - -public: - explicit WifiNetwork(QString ssid, QObject* parent = nullptr); - /// Attempt to connect to the network with the given PSK. If the PSK is wrong, - /// a @@Network.connectionFailed(s) signal will be emitted with `NoSecrets`. - /// - /// The networking backend may store the PSK for future use with @@Network.connect(). - /// As such, calling that function first is recommended to avoid having to show a - /// prompt if not required. - /// - /// > [!NOTE] PSKs should only be provided when the @@security is one of - /// > `WpaPsk`, `Wpa2Psk`, or `Sae`. - Q_INVOKABLE void connectWithPsk(const QString& psk); - - QBindable bindableSignalStrength() { return &this->bSignalStrength; } - QBindable bindableSecurity() { return &this->bSecurity; } - -signals: - QSDOC_HIDE void requestConnectWithPsk(QString psk); - void signalStrengthChanged(); - void securityChanged(); - -private: - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(WifiNetwork, qreal, bSignalStrength, &WifiNetwork::signalStrengthChanged); - Q_OBJECT_BINDABLE_PROPERTY(WifiNetwork, WifiSecurityType::Enum, bSecurity, &WifiNetwork::securityChanged); - // clang-format on -}; - -///! WiFi variant of a @@NetworkDevice. -class WifiDevice: public NetworkDevice { - Q_OBJECT; - QML_ELEMENT; - QML_UNCREATABLE(""); - - // clang-format off - /// A list of this available or connected wifi networks. - QSDOC_TYPE_OVERRIDE(ObjectModel*); - Q_PROPERTY(UntypedObjectModel* networks READ networks CONSTANT); - /// True when currently scanning for networks. - /// When enabled, the scanner populates the device with an active list of available wifi networks. - Q_PROPERTY(bool scannerEnabled READ scannerEnabled WRITE setScannerEnabled NOTIFY scannerEnabledChanged BINDABLE bindableScannerEnabled); - /// The 802.11 mode the device is in. - Q_PROPERTY(WifiDeviceMode::Enum mode READ default NOTIFY modeChanged BINDABLE bindableMode); - // clang-format on - -public: - explicit WifiDevice(QObject* parent = nullptr); - - void networkAdded(WifiNetwork* net); - void networkRemoved(WifiNetwork* net); - - [[nodiscard]] ObjectModel* networks() { return &this->mNetworks; } - QBindable bindableScannerEnabled() { return &this->bScannerEnabled; } - [[nodiscard]] bool scannerEnabled() const { return this->bScannerEnabled; } - void setScannerEnabled(bool enabled); - QBindable bindableMode() { return &this->bMode; } - -signals: - void modeChanged(); - void scannerEnabledChanged(bool enabled); - -private: - ObjectModel mNetworks {this}; - Q_OBJECT_BINDABLE_PROPERTY(WifiDevice, bool, bScannerEnabled, &WifiDevice::scannerEnabledChanged); - Q_OBJECT_BINDABLE_PROPERTY(WifiDevice, WifiDeviceMode::Enum, bMode, &WifiDevice::modeChanged); -}; - -}; // namespace qs::network - -QDebug operator<<(QDebug debug, const qs::network::WifiNetwork* network); -QDebug operator<<(QDebug debug, const qs::network::WifiDevice* device); diff --git a/src/services/CMakeLists.txt b/src/services/CMakeLists.txt index f3912a9..5ab5c55 100644 --- a/src/services/CMakeLists.txt +++ b/src/services/CMakeLists.txt @@ -14,10 +14,6 @@ if (SERVICE_PAM) add_subdirectory(pam) endif() -if (SERVICE_POLKIT) - add_subdirectory(polkit) -endif() - if (SERVICE_GREETD) add_subdirectory(greetd) 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..bf0d1fd 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,77 @@ 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); + } 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/mpris/player.cpp b/src/services/mpris/player.cpp index fe8b349..751a4e7 100644 --- a/src/services/mpris/player.cpp +++ b/src/services/mpris/player.cpp @@ -100,7 +100,7 @@ MprisPlayer::MprisPlayer(const QString& address, QObject* parent): QObject(paren } else return static_cast(-1); }); - this->bLengthSupported.setBinding([this]() { return this->bInternalLength.value() != -1; }); + this->bLengthSupported.setBinding([this]() { return this->bInternalLength != -1; }); this->bIsPlaying.setBinding([this]() { return this->bPlaybackState == MprisPlaybackState::Playing; @@ -378,7 +378,7 @@ void MprisPlayer::onPlaybackStatusUpdated() { // For exceptionally bad players that update playback timestamps at an indeterminate time AFTER // updating playback state. (Youtube) - QTimer::singleShot(100, this, [this]() { this->pPosition.requestUpdate(); }); + QTimer::singleShot(100, this, [&]() { this->pPosition.requestUpdate(); }); // For exceptionally bad players that don't update length (or other metadata) until a new track actually // starts playing, and then don't trigger a metadata update when they do. (Jellyfin) diff --git a/src/services/notifications/notification.cpp b/src/services/notifications/notification.cpp index d048bde..c5269f3 100644 --- a/src/services/notifications/notification.cpp +++ b/src/services/notifications/notification.cpp @@ -127,7 +127,7 @@ void Notification::updateProperties( if (appIcon.isEmpty() && !this->bDesktopEntry.value().isEmpty()) { if (auto* entry = DesktopEntryManager::instance()->byId(this->bDesktopEntry.value())) { - appIcon = entry->bIcon.value(); + appIcon = entry->mIcon; } } diff --git a/src/services/notifications/server.cpp b/src/services/notifications/server.cpp index d2b55d0..3f2469d 100644 --- a/src/services/notifications/server.cpp +++ b/src/services/notifications/server.cpp @@ -117,12 +117,10 @@ void NotificationServer::tryRegister() { if (success) { qCInfo(logNotifications) << "Registered notification server with dbus."; } else { - qCWarning( - logNotifications + qCWarning(logNotifications ) << "Could not register notification server at org.freedesktop.Notifications, presumably " "because one is already registered."; - qCWarning( - logNotifications + qCWarning(logNotifications ) << "Registration will be attempted again if the active service is unregistered."; } } diff --git a/src/services/pam/conversation.cpp b/src/services/pam/conversation.cpp index 1fb4c04..6d27978 100644 --- a/src/services/pam/conversation.cpp +++ b/src/services/pam/conversation.cpp @@ -6,11 +6,7 @@ #include #include #include -#include #include -#ifdef __FreeBSD__ -#include -#endif #include "../../core/logcat.hpp" #include "ipc.hpp" diff --git a/src/services/pam/qml.hpp b/src/services/pam/qml.hpp index a36184e..a8ffcc3 100644 --- a/src/services/pam/qml.hpp +++ b/src/services/pam/qml.hpp @@ -6,11 +6,7 @@ #include #include #include -#ifdef __FreeBSD__ -#include -#else #include -#endif #include #include "conversation.hpp" @@ -39,8 +35,6 @@ class PamContext /// /// The configuration directory is resolved relative to the current file if not an absolute path. /// - /// On FreeBSD this property is ignored as the pam configuration directory cannot be changed. - /// /// This property may not be set while @@active is true. Q_PROPERTY(QString configDirectory READ configDirectory WRITE setConfigDirectory NOTIFY configDirectoryChanged); /// The user to authenticate as. If unset the current user will be used. diff --git a/src/services/pam/subprocess.cpp b/src/services/pam/subprocess.cpp index dc36228..f99b279 100644 --- a/src/services/pam/subprocess.cpp +++ b/src/services/pam/subprocess.cpp @@ -7,11 +7,7 @@ #include #include #include -#ifdef __FreeBSD__ -#include -#else #include -#endif #include #include @@ -87,11 +83,7 @@ PamIpcExitCode PamSubprocess::exec(const char* configDir, const char* config, co logIf(this->log) << "Starting pam session for user \"" << user << "\" with config \"" << config << "\" in dir \"" << configDir << "\"" << std::endl; -#ifdef __FreeBSD__ - auto result = pam_start(config, user, &conv, &handle); -#else auto result = pam_start_confdir(config, user, &conv, configDir, &handle); -#endif if (result != PAM_SUCCESS) { logIf(true) << "Unable to start pam conversation with error \"" << pam_strerror(handle, result) diff --git a/src/services/pipewire/CMakeLists.txt b/src/services/pipewire/CMakeLists.txt index fe894c9..fddca6f 100644 --- a/src/services/pipewire/CMakeLists.txt +++ b/src/services/pipewire/CMakeLists.txt @@ -3,7 +3,6 @@ pkg_check_modules(pipewire REQUIRED IMPORTED_TARGET libpipewire-0.3) qt_add_library(quickshell-service-pipewire STATIC qml.cpp - peak.cpp core.cpp connection.cpp registry.cpp diff --git a/src/services/pipewire/connection.cpp b/src/services/pipewire/connection.cpp index c2f505f..ac4c5e6 100644 --- a/src/services/pipewire/connection.cpp +++ b/src/services/pipewire/connection.cpp @@ -1,135 +1,13 @@ #include "connection.hpp" -#include -#include -#include -#include -#include #include -#include -#include - -#include "../../core/logcat.hpp" -#include "core.hpp" namespace qs::service::pipewire { -namespace { -QS_LOGGING_CATEGORY(logConnection, "quickshell.service.pipewire.connection", QtWarningMsg); -} - PwConnection::PwConnection(QObject* parent): QObject(parent) { - this->runtimeDir = PwConnection::resolveRuntimeDir(); - - QObject::connect(&this->core, &PwCore::fatalError, this, &PwConnection::queueFatalError); - - if (!this->tryConnect(false) - && qEnvironmentVariableIntValue("QS_PIPEWIRE_IMMEDIATE_RECONNECT") == 1) - { - this->beginReconnect(); - } -} - -QString PwConnection::resolveRuntimeDir() { - auto runtimeDir = qEnvironmentVariable("PIPEWIRE_RUNTIME_DIR"); - if (runtimeDir.isEmpty()) { - runtimeDir = qEnvironmentVariable("XDG_RUNTIME_DIR"); - } - - if (runtimeDir.isEmpty()) { - runtimeDir = QString("/run/user/%1").arg(getuid()); - } - - return runtimeDir; -} - -void PwConnection::beginReconnect() { if (this->core.isValid()) { - this->stopSocketWatcher(); - return; + this->registry.init(this->core); } - - if (!qEnvironmentVariableIsEmpty("PIPEWIRE_REMOTE")) return; - - if (this->runtimeDir.isEmpty()) { - qCWarning( - logConnection - ) << "Cannot watch runtime dir for pipewire reconnects: runtime dir is empty."; - return; - } - - this->startSocketWatcher(); - this->tryConnect(true); -} - -bool PwConnection::tryConnect(bool retry) { - if (this->core.isValid()) return true; - - qCDebug(logConnection) << "Attempting reconnect..."; - if (!this->core.start(retry)) { - return false; - } - - qCInfo(logConnection) << "Connection established"; - this->stopSocketWatcher(); - - this->registry.init(this->core); - return true; -} - -void PwConnection::startSocketWatcher() { - if (this->socketWatcher != nullptr) return; - if (!qEnvironmentVariableIsEmpty("PIPEWIRE_REMOTE")) return; - - auto dir = QDir(this->runtimeDir); - if (!dir.exists()) { - qCWarning(logConnection) << "Cannot wait for a new pipewire socket, runtime dir does not exist:" - << this->runtimeDir; - return; - } - - this->socketWatcher = new QFileSystemWatcher(this); - this->socketWatcher->addPath(this->runtimeDir); - - QObject::connect( - this->socketWatcher, - &QFileSystemWatcher::directoryChanged, - this, - &PwConnection::onRuntimeDirChanged - ); -} - -void PwConnection::stopSocketWatcher() { - if (this->socketWatcher == nullptr) return; - - this->socketWatcher->deleteLater(); - this->socketWatcher = nullptr; -} - -void PwConnection::queueFatalError() { - if (this->fatalErrorQueued) return; - - this->fatalErrorQueued = true; - QMetaObject::invokeMethod(this, &PwConnection::onFatalError, Qt::QueuedConnection); -} - -void PwConnection::onFatalError() { - this->fatalErrorQueued = false; - - this->defaults.reset(); - this->registry.reset(); - this->core.shutdown(); - - this->beginReconnect(); -} - -void PwConnection::onRuntimeDirChanged(const QString& /*path*/) { - if (this->core.isValid()) { - this->stopSocketWatcher(); - return; - } - - this->tryConnect(true); } PwConnection* PwConnection::instance() { diff --git a/src/services/pipewire/connection.hpp b/src/services/pipewire/connection.hpp index d0374f8..2b3e860 100644 --- a/src/services/pipewire/connection.hpp +++ b/src/services/pipewire/connection.hpp @@ -1,13 +1,9 @@ #pragma once -#include - #include "core.hpp" #include "defaults.hpp" #include "registry.hpp" -class QFileSystemWatcher; - namespace qs::service::pipewire { class PwConnection: public QObject { @@ -22,23 +18,6 @@ public: static PwConnection* instance(); private: - static QString resolveRuntimeDir(); - - void beginReconnect(); - bool tryConnect(bool retry); - void startSocketWatcher(); - void stopSocketWatcher(); - -private slots: - void queueFatalError(); - void onFatalError(); - void onRuntimeDirChanged(const QString& path); - -private: - QString runtimeDir; - QFileSystemWatcher* socketWatcher = nullptr; - bool fatalErrorQueued = false; - // init/destroy order is important. do not rearrange. PwCore core; }; diff --git a/src/services/pipewire/core.cpp b/src/services/pipewire/core.cpp index 5077abe..22445aa 100644 --- a/src/services/pipewire/core.cpp +++ b/src/services/pipewire/core.cpp @@ -27,7 +27,7 @@ const pw_core_events PwCore::EVENTS = { .info = nullptr, .done = &PwCore::onSync, .ping = nullptr, - .error = &PwCore::onError, + .error = nullptr, .remove_id = nullptr, .bound_id = nullptr, .add_mem = nullptr, @@ -36,46 +36,26 @@ const pw_core_events PwCore::EVENTS = { }; PwCore::PwCore(QObject* parent): QObject(parent), notifier(QSocketNotifier::Read) { - pw_init(nullptr, nullptr); -} - -bool PwCore::start(bool retry) { - if (this->core != nullptr) return true; - qCInfo(logLoop) << "Creating pipewire event loop."; + pw_init(nullptr, nullptr); this->loop = pw_loop_new(nullptr); if (this->loop == nullptr) { - if (retry) { - qCInfo(logLoop) << "Failed to create pipewire event loop."; - } else { - qCCritical(logLoop) << "Failed to create pipewire event loop."; - } - this->shutdown(); - return false; + qCCritical(logLoop) << "Failed to create pipewire event loop."; + return; } this->context = pw_context_new(this->loop, nullptr, 0); if (this->context == nullptr) { - if (retry) { - qCInfo(logLoop) << "Failed to create pipewire context."; - } else { - qCCritical(logLoop) << "Failed to create pipewire context."; - } - this->shutdown(); - return false; + qCCritical(logLoop) << "Failed to create pipewire context."; + return; } qCInfo(logLoop) << "Connecting to pipewire server."; this->core = pw_context_connect(this->context, nullptr, 0); if (this->core == nullptr) { - if (retry) { - qCInfo(logLoop) << "Failed to connect pipewire context. Errno:" << errno; - } else { - qCCritical(logLoop) << "Failed to connect pipewire context. Errno:" << errno; - } - this->shutdown(); - return false; + qCCritical(logLoop) << "Failed to connect pipewire context. Errno:" << errno; + return; } pw_core_add_listener(this->core, &this->listener.hook, &PwCore::EVENTS, this); @@ -86,34 +66,22 @@ bool PwCore::start(bool retry) { this->notifier.setSocket(fd); QObject::connect(&this->notifier, &QSocketNotifier::activated, this, &PwCore::poll); this->notifier.setEnabled(true); - - return true; -} - -void PwCore::shutdown() { - if (this->core != nullptr) { - this->listener.remove(); - pw_core_disconnect(this->core); - this->core = nullptr; - } - - if (this->context != nullptr) { - pw_context_destroy(this->context); - this->context = nullptr; - } - - if (this->loop != nullptr) { - pw_loop_destroy(this->loop); - this->loop = nullptr; - } - - this->notifier.setEnabled(false); - QObject::disconnect(&this->notifier, nullptr, this, nullptr); } PwCore::~PwCore() { qCInfo(logLoop) << "Destroying PwCore."; - this->shutdown(); + + if (this->loop != nullptr) { + if (this->context != nullptr) { + if (this->core != nullptr) { + pw_core_disconnect(this->core); + } + + pw_context_destroy(this->context); + } + + pw_loop_destroy(this->loop); + } } bool PwCore::isValid() const { @@ -122,7 +90,6 @@ bool PwCore::isValid() const { } void PwCore::poll() { - if (this->loop == nullptr) return; qCDebug(logLoop) << "Pipewire event loop received new events, iterating."; // Spin pw event loop. pw_loop_iterate(this->loop, 0); @@ -140,23 +107,6 @@ void PwCore::onSync(void* data, quint32 id, qint32 seq) { emit self->synced(id, 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; - } - - qCWarning(logLoop) << "Pipewire error on object" << id << "with code" << res << message; - - emit self->fatalError(); -} - SpaHook::SpaHook() { // NOLINT spa_zero(this->hook); } diff --git a/src/services/pipewire/core.hpp b/src/services/pipewire/core.hpp index 967efaf..262e2d3 100644 --- a/src/services/pipewire/core.hpp +++ b/src/services/pipewire/core.hpp @@ -30,9 +30,6 @@ public: ~PwCore() override; Q_DISABLE_COPY_MOVE(PwCore); - bool start(bool retry); - void shutdown(); - [[nodiscard]] bool isValid() const; [[nodiscard]] qint32 sync(quint32 id) const; @@ -43,7 +40,6 @@ public: signals: void polled(); void synced(quint32 id, qint32 seq); - void fatalError(); private slots: void poll(); @@ -52,7 +48,6 @@ private: static const pw_core_events EVENTS; static void onSync(void* data, quint32 id, qint32 seq); - static void onError(void* data, quint32 id, qint32 seq, qint32 res, const char* message); QSocketNotifier notifier; SpaHook listener; diff --git a/src/services/pipewire/defaults.cpp b/src/services/pipewire/defaults.cpp index b9c8e35..b3d8bfc 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" @@ -30,22 +31,6 @@ PwDefaultTracker::PwDefaultTracker(PwRegistry* registry): registry(registry) { QObject::connect(registry, &PwRegistry::nodeAdded, this, &PwDefaultTracker::onNodeAdded); } -void PwDefaultTracker::reset() { - if (auto* meta = this->defaultsMetadata.object()) { - QObject::disconnect(meta, nullptr, this, nullptr); - } - - this->defaultsMetadata.setObject(nullptr); - this->setDefaultSink(nullptr); - this->setDefaultSinkName(QString()); - this->setDefaultSource(nullptr); - this->setDefaultSourceName(QString()); - this->setDefaultConfiguredSink(nullptr); - this->setDefaultConfiguredSinkName(QString()); - this->setDefaultConfiguredSource(nullptr); - this->setDefaultConfiguredSourceName(QString()); -} - void PwDefaultTracker::onMetadataAdded(PwMetadata* metadata) { if (metadata->name() == "default") { qCDebug(logDefaults) << "Got new defaults metadata object" << metadata; @@ -137,6 +122,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)) { @@ -190,8 +201,7 @@ bool PwDefaultTracker::setConfiguredDefault(const char* key, const QString& valu } if (!meta->hasSetPermission()) { - qCCritical( - logDefaults + qCCritical(logDefaults ) << "Cannot set default node as write+execute permissions are missing for" << meta; return false; @@ -213,34 +223,10 @@ void PwDefaultTracker::setDefaultSink(PwNode* node) { if (node == this->mDefaultSink) return; qCInfo(logDefaults) << "Default sink changed to" << node; - if (this->mDefaultSink != nullptr) { - // Targeted disconnect is used because this can also be the default configured sink. - QObject::disconnect( - this->mDefaultSink, - &PwBindableObject::destroying, - this, - &PwDefaultTracker::onDefaultSinkDestroyed - ); - } - - this->mDefaultSink = node; - - if (node != nullptr) { - QObject::connect( - node, - &PwBindableObject::destroying, - 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) { @@ -254,34 +240,10 @@ void PwDefaultTracker::setDefaultSource(PwNode* node) { if (node == this->mDefaultSource) return; qCInfo(logDefaults) << "Default source changed to" << node; - if (this->mDefaultSource != nullptr) { - // Targeted disconnect is used because this can also be the default configured source. - QObject::disconnect( - this->mDefaultSource, - &PwBindableObject::destroying, - this, - &PwDefaultTracker::onDefaultSourceDestroyed - ); - } - - this->mDefaultSource = node; - - if (node != nullptr) { - QObject::connect( - node, - &PwBindableObject::destroying, - 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) { @@ -295,34 +257,10 @@ void PwDefaultTracker::setDefaultConfiguredSink(PwNode* node) { if (node == this->mDefaultConfiguredSink) return; qCInfo(logDefaults) << "Default configured sink changed to" << node; - if (this->mDefaultConfiguredSink != nullptr) { - // Targeted disconnect is used because this can also be the default sink. - QObject::disconnect( - this->mDefaultConfiguredSink, - &PwBindableObject::destroying, - this, - &PwDefaultTracker::onDefaultConfiguredSinkDestroyed - ); - } - - this->mDefaultConfiguredSink = node; - - if (node != nullptr) { - QObject::connect( - node, - &PwBindableObject::destroying, - 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) { @@ -336,34 +274,10 @@ void PwDefaultTracker::setDefaultConfiguredSource(PwNode* node) { if (node == this->mDefaultConfiguredSource) return; qCInfo(logDefaults) << "Default configured source changed to" << node; - if (this->mDefaultConfiguredSource != nullptr) { - // Targeted disconnect is used because this can also be the default source. - QObject::disconnect( - this->mDefaultConfiguredSource, - &PwBindableObject::destroying, - this, - &PwDefaultTracker::onDefaultConfiguredSourceDestroyed - ); - } - - this->mDefaultConfiguredSource = node; - - if (node != nullptr) { - QObject::connect( - node, - &PwBindableObject::destroying, - 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..f3a8e3f 100644 --- a/src/services/pipewire/defaults.hpp +++ b/src/services/pipewire/defaults.hpp @@ -12,7 +12,6 @@ class PwDefaultTracker: public QObject { public: explicit PwDefaultTracker(PwRegistry* registry); - void reset(); [[nodiscard]] PwNode* defaultSink() const; [[nodiscard]] PwNode* defaultSource() const; @@ -44,10 +43,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/services/pipewire/device.cpp b/src/services/pipewire/device.cpp index 61079a1..616e7d0 100644 --- a/src/services/pipewire/device.cpp +++ b/src/services/pipewire/device.cpp @@ -107,7 +107,7 @@ void PwDevice::addDeviceIndexPairs(const spa_pod* param) { qint32 device = 0; qint32 index = 0; - const spa_pod* props = nullptr; + spa_pod* props = nullptr; // clang-format off quint32 id = SPA_PARAM_Route; @@ -125,32 +125,18 @@ void PwDevice::addDeviceIndexPairs(const spa_pod* param) { // Insert into the main map as well, staging's purpose is to remove old entries. this->routeDeviceIndexes.insert(device, index); - // Used for initial node volume if the device is bound before the node - // (e.g. multiple nodes pointing to the same device) - this->routeDeviceVolumes.insert(device, volumeProps); - qCDebug(logDevice).nospace() << "Registered device/index pair for " << this << ": [device: " << device << ", index: " << index << ']'; emit this->routeVolumesChanged(device, volumeProps); } -bool PwDevice::tryLoadVolumeProps(qint32 routeDevice, PwVolumeProps& volumeProps) { - if (!this->routeDeviceVolumes.contains(routeDevice)) return false; - volumeProps = this->routeDeviceVolumes.value(routeDevice); - return true; -} - -bool PwDevice::hasRouteDevice(qint32 routeDevice) const { - return this->routeDeviceIndexes.contains(routeDevice); -} - void PwDevice::polled() { // It is far more likely that the list content has not come in yet than it having no entries, // and there isn't a way to check in the case that there *aren't* actually any entries. if (!this->stagingIndexes.isEmpty()) { - this->routeDeviceIndexes.removeIf([&, this](const std::pair& entry) { - if (!this->stagingIndexes.contains(entry.first)) { + this->routeDeviceIndexes.removeIf([&](const std::pair& entry) { + if (!stagingIndexes.contains(entry.first)) { qCDebug(logDevice).nospace() << "Removed device/index pair [device: " << entry.first << ", index: " << entry.second << "] for" << this; return true; diff --git a/src/services/pipewire/device.hpp b/src/services/pipewire/device.hpp index cd61709..1a1f705 100644 --- a/src/services/pipewire/device.hpp +++ b/src/services/pipewire/device.hpp @@ -12,15 +12,13 @@ #include #include "core.hpp" +#include "node.hpp" #include "registry.hpp" namespace qs::service::pipewire { class PwDevice; -// Forward declare to avoid circular dependency with node.hpp -struct PwVolumeProps; - class PwDevice: public PwBindable { Q_OBJECT; @@ -34,9 +32,6 @@ public: void waitForDevice(); [[nodiscard]] bool waitingForDevice() const; - [[nodiscard]] bool tryLoadVolumeProps(qint32 routeDevice, PwVolumeProps& volumeProps); - [[nodiscard]] bool hasRouteDevice(qint32 routeDevice) const; - signals: void deviceReady(); void routeVolumesChanged(qint32 routeDevice, const PwVolumeProps& volumeProps); @@ -51,7 +46,6 @@ private: onParam(void* data, qint32 seq, quint32 id, quint32 index, quint32 next, const spa_pod* param); QHash routeDeviceIndexes; - QHash routeDeviceVolumes; QList stagingIndexes; void addDeviceIndexPairs(const spa_pod* param); diff --git a/src/services/pipewire/module.md b/src/services/pipewire/module.md index e34f77d..d109f05 100644 --- a/src/services/pipewire/module.md +++ b/src/services/pipewire/module.md @@ -2,7 +2,6 @@ name = "Quickshell.Services.Pipewire" description = "Pipewire API" headers = [ "qml.hpp", - "peak.hpp", "link.hpp", "node.hpp", ] diff --git a/src/services/pipewire/node.cpp b/src/services/pipewire/node.cpp index 075a7ec..3e68149 100644 --- a/src/services/pipewire/node.cpp +++ b/src/services/pipewire/node.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include #include @@ -23,7 +23,6 @@ #include #include #include -#include #include #include "../../core/logcat.hpp" @@ -161,24 +160,6 @@ void PwNode::initProps(const spa_dict* props) { this->nick = nodeNick; } - if (const auto* nodeCategory = spa_dict_lookup(props, PW_KEY_MEDIA_CATEGORY)) { - if (strcmp(nodeCategory, "Monitor") == 0 || strcmp(nodeCategory, "Manager") == 0) { - this->isMonitor = true; - } - } - - if (const auto* serial = spa_dict_lookup(props, PW_KEY_OBJECT_SERIAL)) { - auto ok = false; - auto value = QString::fromUtf8(serial).toULongLong(&ok); - if (!ok) { - qCWarning(logNode) << this - << "has an object.serial property but the value is not valid. Value:" - << serial; - } else { - this->objectSerial = value; - } - } - if (const auto* deviceId = spa_dict_lookup(props, PW_KEY_DEVICE_ID)) { auto ok = false; auto id = QString::fromUtf8(deviceId).toInt(&ok); @@ -190,8 +171,7 @@ void PwNode::initProps(const spa_dict* props) { this->device = this->registry->devices.value(id); if (this->device == nullptr) { - qCCritical( - logNode + qCCritical(logNode ) << this << "has a device.id property that does not corrospond to a device object. Id:" << id; } @@ -215,36 +195,21 @@ void PwNode::onInfo(void* data, const pw_node_info* info) { if ((info->change_mask & PW_NODE_CHANGE_MASK_PROPS) != 0) { auto properties = QMap(); - bool proAudio = false; - if (const auto* proAudioStr = spa_dict_lookup(info->props, "device.profile.pro")) { - proAudio = spa_atob(proAudioStr); - } - - if (proAudio != self->proAudio) { - qCDebug(logNode) << self << "pro audio state changed:" << proAudio; - self->proAudio = proAudio; - } - if (self->device) { if (const auto* routeDevice = spa_dict_lookup(info->props, "card.profile.device")) { auto ok = false; auto id = QString::fromUtf8(routeDevice).toInt(&ok); if (!ok) { - qCCritical( - logNode + qCCritical(logNode ) << self << "has a card.profile.device property but the value is not an integer. Value:" << id; } self->routeDevice = id; - if (self->boundData) self->boundData->onDeviceChanged(); } else { - qCDebug( - logNode - ) << self - << "has attached device" << self->device - << "but no card.profile.device property. Node volume control will be used."; + qCCritical(logNode) << self << "has attached device" << self->device + << "but no card.profile.device property."; } } @@ -301,15 +266,6 @@ PwNodeBoundAudio::PwNodeBoundAudio(PwNode* node): QObject(node), node(node) { } } -void PwNodeBoundAudio::onDeviceChanged() { - PwVolumeProps volumeProps; - if (this->node->device->tryLoadVolumeProps(this->node->routeDevice, volumeProps)) { - qCDebug(logNode) << "Initializing volume props for" << this->node - << "with known values from backing device."; - this->updateVolumeProps(volumeProps); - } -} - void PwNodeBoundAudio::onInfo(const pw_node_info* info) { if ((info->change_mask & PW_NODE_CHANGE_MASK_PARAMS) != 0) { for (quint32 i = 0; i < info->n_params; i++) { @@ -330,10 +286,9 @@ void PwNodeBoundAudio::onInfo(const pw_node_info* info) { void PwNodeBoundAudio::onSpaParam(quint32 id, quint32 index, const spa_pod* param) { if (id == SPA_PARAM_Props && index == 0) { - if (this->node->shouldUseDevice()) { + if (this->node->device) { qCDebug(logNode) << "Skipping node volume props update for" << this->node - << "in favor of device updates from routeDevice" << this->node->routeDevice - << "of" << this->node->device; + << "in favor of device updates."; return; } @@ -349,8 +304,6 @@ void PwNodeBoundAudio::updateVolumeProps(const PwVolumeProps& volumeProps) { return; } - this->volumeStep = volumeProps.volumeStep; - // It is important that the lengths of channels and volumes stay in sync whenever you read them. auto channelsChanged = false; auto volumesChanged = false; @@ -403,7 +356,7 @@ void PwNodeBoundAudio::setMuted(bool muted) { if (muted == this->mMuted) return; - if (this->node->shouldUseDevice()) { + if (this->node->device) { qCInfo(logNode) << "Changing muted state of" << this->node << "to" << muted << "via device"; if (!this->node->device->setMuted(this->node->routeDevice, muted)) { return; @@ -429,10 +382,6 @@ void PwNodeBoundAudio::setMuted(bool muted) { } float PwNodeBoundAudio::averageVolume() const { - if (this->mVolumes.isEmpty()) { - return 0.0f; - } - float total = 0; for (auto volume: this->mVolumes) { @@ -480,41 +429,37 @@ void PwNodeBoundAudio::setVolumes(const QVector& volumes) { return; } - if (this->node->shouldUseDevice()) { + if (this->node->device) { if (this->node->device->waitingForDevice()) { qCInfo(logNode) << "Waiting to change volumes of" << this->node << "to" << realVolumes << "via device"; this->waitingVolumes = realVolumes; } else { - if (this->volumeStep != -1) { - auto significantChange = this->mServerVolumes.isEmpty(); - for (auto i = 0; i < this->mServerVolumes.length(); i++) { - auto serverVolume = this->mServerVolumes.value(i); - auto targetVolume = realVolumes.value(i); - if (targetVolume == 0 || abs(targetVolume - serverVolume) >= this->volumeStep) { - significantChange = true; - break; - } + auto significantChange = this->mServerVolumes.isEmpty(); + for (auto i = 0; i < this->mServerVolumes.length(); i++) { + auto serverVolume = this->mServerVolumes.value(i); + auto targetVolume = realVolumes.value(i); + if (targetVolume == 0 || abs(targetVolume - serverVolume) >= 0.0001) { + significantChange = true; + break; + } + } + + if (significantChange) { + qCInfo(logNode) << "Changing volumes of" << this->node << "to" << realVolumes + << "via device"; + if (!this->node->device->setVolumes(this->node->routeDevice, realVolumes)) { + return; } - if (significantChange) { - qCInfo(logNode) << "Changing volumes of" << this->node << "to" << realVolumes - << "via device"; - if (!this->node->device->setVolumes(this->node->routeDevice, realVolumes)) { - return; - } - - this->mDeviceVolumes = realVolumes; - this->node->device->waitForDevice(); - } else { - // Insignificant changes won't cause an info event on the device, leaving qs hung in the - // "waiting for acknowledgement" state forever. - qCInfo(logNode).nospace() - << "Ignoring volume change for " << this->node << " to " << realVolumes << " from " - << this->mServerVolumes - << " as it is a device node and the change is too small (min step: " - << this->volumeStep << ")."; - } + this->mDeviceVolumes = realVolumes; + this->node->device->waitForDevice(); + } else { + // Insignificant changes won't cause an info event on the device, leaving qs hung in the + // "waiting for acknowledgement" state forever. + qCInfo(logNode) << "Ignoring volume change for" << this->node << "to" << realVolumes + << "from" << this->mServerVolumes + << "as it is a device node and the change is too small."; } } } else { @@ -560,7 +505,7 @@ void PwNodeBoundAudio::onDeviceVolumesChanged( qint32 routeDevice, const PwVolumeProps& volumeProps ) { - if (this->node->shouldUseDevice() && this->node->routeDevice == routeDevice) { + if (this->node->device && this->node->routeDevice == routeDevice) { qCDebug(logNode) << "Got updated device volume props for" << this->node << "via" << this->node->device; @@ -574,36 +519,23 @@ PwVolumeProps PwVolumeProps::parseSpaPod(const spa_pod* param) { const auto* volumesProp = spa_pod_find_prop(param, nullptr, SPA_PROP_channelVolumes); const auto* channelsProp = spa_pod_find_prop(param, nullptr, SPA_PROP_channelMap); const auto* muteProp = spa_pod_find_prop(param, nullptr, SPA_PROP_mute); - const auto* volumeStepProp = spa_pod_find_prop(param, nullptr, SPA_PROP_volumeStep); - if (volumesProp) { - const auto* volumes = reinterpret_cast(&volumesProp->value); - spa_pod* iter = nullptr; - SPA_POD_ARRAY_FOREACH(volumes, iter) { - // Cubing behavior found in MPD source, and appears to corrospond to everyone else's measurements correctly. - auto linear = *reinterpret_cast(iter); - auto visual = std::cbrt(linear); - props.volumes.push_back(visual); - } + const auto* volumes = reinterpret_cast(&volumesProp->value); + const auto* channels = reinterpret_cast(&channelsProp->value); + + spa_pod* iter = nullptr; + SPA_POD_ARRAY_FOREACH(volumes, iter) { + // Cubing behavior found in MPD source, and appears to corrospond to everyone else's measurements correctly. + auto linear = *reinterpret_cast(iter); + auto visual = std::cbrt(linear); + props.volumes.push_back(visual); } - if (channelsProp) { - const auto* channels = reinterpret_cast(&channelsProp->value); - spa_pod* iter = nullptr; - SPA_POD_ARRAY_FOREACH(channels, iter) { - props.channels.push_back(*reinterpret_cast(iter)); - } + SPA_POD_ARRAY_FOREACH(channels, iter) { + props.channels.push_back(*reinterpret_cast(iter)); } - if (muteProp) { - spa_pod_get_bool(&muteProp->value, &props.mute); - } - - if (volumeStepProp) { - spa_pod_get_float(&volumeStepProp->value, &props.volumeStep); - } else { - props.volumeStep = -1; - } + spa_pod_get_bool(&muteProp->value, &props.mute); return props; } diff --git a/src/services/pipewire/node.hpp b/src/services/pipewire/node.hpp index efc819c..0d4c92e 100644 --- a/src/services/pipewire/node.hpp +++ b/src/services/pipewire/node.hpp @@ -15,7 +15,6 @@ #include #include "core.hpp" -#include "device.hpp" #include "registry.hpp" namespace qs::service::pipewire { @@ -159,7 +158,6 @@ struct PwVolumeProps { QVector channels; QVector volumes; bool mute = false; - float volumeStep = -1; static PwVolumeProps parseSpaPod(const spa_pod* param); }; @@ -170,7 +168,6 @@ public: virtual ~PwNodeBoundData() = default; Q_DISABLE_COPY_MOVE(PwNodeBoundData); - virtual void onDeviceChanged() {}; virtual void onInfo(const pw_node_info* /*info*/) {} virtual void onSpaParam(quint32 /*id*/, quint32 /*index*/, const spa_pod* /*param*/) {} virtual void onUnbind() {} @@ -184,7 +181,6 @@ class PwNodeBoundAudio public: explicit PwNodeBoundAudio(PwNode* node); - void onDeviceChanged() override; void onInfo(const pw_node_info* info) override; void onSpaParam(quint32 id, quint32 index, const spa_pod* param) override; void onUnbind() override; @@ -200,8 +196,6 @@ public: [[nodiscard]] QVector volumes() const; void setVolumes(const QVector& volumes); - [[nodiscard]] QVector server() const; - signals: void volumesChanged(); void channelsChanged(); @@ -220,7 +214,6 @@ private: QVector mServerVolumes; QVector mDeviceVolumes; QVector waitingVolumes; - float volumeStep = -1; PwNode* node; }; @@ -236,8 +229,6 @@ public: QString description; QString nick; QMap properties; - quint64 objectSerial = 0; - bool isMonitor = false; PwNodeType::Flags type = PwNodeType::Untracked; @@ -247,13 +238,6 @@ public: PwDevice* device = nullptr; qint32 routeDevice = -1; - bool proAudio = false; - - [[nodiscard]] bool shouldUseDevice() const { - if (!this->device || this->proAudio || this->routeDevice == -1) return false; - // Only use device control if the device actually has route indexes for this routeDevice - return this->device->hasRouteDevice(this->routeDevice); - } signals: void propertiesChanged(); diff --git a/src/services/pipewire/peak.cpp b/src/services/pipewire/peak.cpp deleted file mode 100644 index 64b5c42..0000000 --- a/src/services/pipewire/peak.cpp +++ /dev/null @@ -1,404 +0,0 @@ -#include "peak.hpp" -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../core/logcat.hpp" -#include "connection.hpp" -#include "core.hpp" -#include "node.hpp" -#include "qml.hpp" - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmissing-designated-field-initializers" - -namespace qs::service::pipewire { - -namespace { -QS_LOGGING_CATEGORY(logPeak, "quickshell.service.pipewire.peak", QtWarningMsg); -} - -class PwPeakStream { -public: - PwPeakStream(PwNodePeakMonitor* monitor, PwNode* node): monitor(monitor), node(node) {} - ~PwPeakStream() { this->destroy(); } - Q_DISABLE_COPY_MOVE(PwPeakStream); - - bool start(); - void destroy(); - -private: - static const pw_stream_events EVENTS; - static void onProcess(void* data); - static void onParamChanged(void* data, uint32_t id, const spa_pod* param); - static void - onStateChanged(void* data, pw_stream_state oldState, pw_stream_state state, const char* error); - static void onDestroy(void* data); - - void handleProcess(); - void handleParamChanged(uint32_t id, const spa_pod* param); - void handleStateChanged(pw_stream_state oldState, pw_stream_state state, const char* error); - void resetFormat(); - - PwNodePeakMonitor* monitor = nullptr; - PwNode* node = nullptr; - pw_stream* stream = nullptr; - SpaHook listener; - spa_audio_info_raw format = SPA_AUDIO_INFO_RAW_INIT(.format = SPA_AUDIO_FORMAT_UNKNOWN); - bool formatReady = false; - QVector channelPeaks; -}; - -const pw_stream_events PwPeakStream::EVENTS = { - .version = PW_VERSION_STREAM_EVENTS, - .destroy = &PwPeakStream::onDestroy, - .state_changed = &PwPeakStream::onStateChanged, - .param_changed = &PwPeakStream::onParamChanged, - .process = &PwPeakStream::onProcess, -}; - -bool PwPeakStream::start() { - auto* core = PwConnection::instance()->registry.core; - if (core == nullptr || !core->isValid()) { - qCWarning(logPeak) << "Cannot start peak monitor stream: pipewire core is not ready."; - return false; - } - - auto target = - QByteArray::number(this->node->objectSerial ? this->node->objectSerial : this->node->id); - - // clang-format off - auto* props = pw_properties_new( - PW_KEY_MEDIA_TYPE, "Audio", - PW_KEY_MEDIA_CATEGORY, "Monitor", - PW_KEY_MEDIA_NAME, "Peak detect", - PW_KEY_APP_NAME, "Quickshell Peak Detect", - PW_KEY_STREAM_MONITOR, "true", - PW_KEY_STREAM_CAPTURE_SINK, this->node->type.testFlags(PwNodeType::Sink) ? "true" : "false", - PW_KEY_TARGET_OBJECT, target.constData(), - nullptr - ); - // clang-format on - - if (props == nullptr) { - qCWarning(logPeak) << "Failed to create properties for peak monitor stream."; - return false; - } - - this->stream = pw_stream_new(core->core, "quickshell-peak-monitor", props); - if (this->stream == nullptr) { - qCWarning(logPeak) << "Failed to create peak monitor stream."; - return false; - } - - pw_stream_add_listener(this->stream, &this->listener.hook, &PwPeakStream::EVENTS, this); - - auto buffer = std::array {}; - auto builder = SPA_POD_BUILDER_INIT(buffer.data(), buffer.size()); // NOLINT - - auto params = std::array {}; - auto raw = SPA_AUDIO_INFO_RAW_INIT(.format = SPA_AUDIO_FORMAT_F32); - params[0] = spa_format_audio_raw_build(&builder, SPA_PARAM_EnumFormat, &raw); - - auto flags = - static_cast(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS); - auto res = - pw_stream_connect(this->stream, PW_DIRECTION_INPUT, PW_ID_ANY, flags, params.data(), 1); - - if (res < 0) { - qCWarning(logPeak) << "Failed to connect peak monitor stream:" << res; - this->destroy(); - return false; - } - - return true; -} - -void PwPeakStream::destroy() { - if (this->stream == nullptr) return; - this->listener.remove(); - pw_stream_destroy(this->stream); - this->stream = nullptr; - this->resetFormat(); -} - -void PwPeakStream::onProcess(void* data) { - static_cast(data)->handleProcess(); // NOLINT -} - -void PwPeakStream::onParamChanged(void* data, uint32_t id, const spa_pod* param) { - static_cast(data)->handleParamChanged(id, param); // NOLINT -} - -void PwPeakStream::onStateChanged( - void* data, - pw_stream_state oldState, - pw_stream_state state, - const char* error -) { - static_cast(data)->handleStateChanged(oldState, state, error); // NOLINT -} - -void PwPeakStream::onDestroy(void* data) { - auto* self = static_cast(data); // NOLINT - self->stream = nullptr; - self->listener.remove(); - self->resetFormat(); -} - -void PwPeakStream::handleStateChanged( - pw_stream_state oldState, - pw_stream_state state, - const char* error -) { - if (state == PW_STREAM_STATE_ERROR) { - if (error != nullptr) { - qCWarning(logPeak) << "Peak monitor stream error:" << error; - } else { - qCWarning(logPeak) << "Peak monitor stream error."; - } - } - - if (state == PW_STREAM_STATE_PAUSED && oldState != PW_STREAM_STATE_PAUSED) { - auto peakCount = this->monitor->mChannels.length(); - if (peakCount == 0) { - peakCount = this->monitor->mPeaks.length(); - } - if (peakCount == 0 && this->formatReady) { - peakCount = static_cast(this->format.channels); - } - - if (peakCount > 0) { - auto zeros = QVector(peakCount, 0.0f); - this->monitor->updatePeaks(zeros, 0.0f); - } - } -} - -void PwPeakStream::handleParamChanged(uint32_t id, const spa_pod* param) { - if (param == nullptr || id != SPA_PARAM_Format) return; - - auto info = spa_audio_info {}; - if (spa_format_parse(param, &info.media_type, &info.media_subtype) < 0) return; - - if (info.media_type != SPA_MEDIA_TYPE_audio || info.media_subtype != SPA_MEDIA_SUBTYPE_raw) - return; - - auto raw = SPA_AUDIO_INFO_RAW_INIT(.format = SPA_AUDIO_FORMAT_UNKNOWN); // NOLINT - if (spa_format_audio_raw_parse(param, &raw) < 0) return; - - if (raw.format != SPA_AUDIO_FORMAT_F32) { - qCWarning(logPeak) << "Unsupported peak monitor format for" << this->node << ":" << raw.format; - this->resetFormat(); - return; - } - - this->format = raw; - this->formatReady = raw.channels > 0; - - auto channels = QVector(); - channels.reserve(static_cast(raw.channels)); - - for (quint32 i = 0; i < raw.channels; i++) { - if ((raw.flags & SPA_AUDIO_FLAG_UNPOSITIONED) != 0) { - channels.push_back(PwAudioChannel::Unknown); - } else { - channels.push_back(static_cast(raw.position[i])); - } - } - - this->channelPeaks.fill(0.0f, channels.size()); - this->monitor->updateChannels(channels); - this->monitor->updatePeaks(this->channelPeaks, 0.0f); -} - -void PwPeakStream::resetFormat() { - this->format = SPA_AUDIO_INFO_RAW_INIT(.format = SPA_AUDIO_FORMAT_UNKNOWN); - this->formatReady = false; - this->channelPeaks.clear(); - this->monitor->clearPeaks(); -} - -void PwPeakStream::handleProcess() { - if (!this->formatReady || this->stream == nullptr) return; - - auto* buffer = pw_stream_dequeue_buffer(this->stream); - auto requeue = qScopeGuard([&, this] { pw_stream_queue_buffer(this->stream, buffer); }); - - if (buffer == nullptr) { - qCWarning(logPeak) << "Peak monitor ran out of buffers."; - return; - } - - auto* spaBuffer = buffer->buffer; - if (spaBuffer == nullptr || spaBuffer->n_datas < 1) { - return; - } - - auto* data = &spaBuffer->datas[0]; // NOLINT - if (data->data == nullptr || data->chunk == nullptr) { - return; - } - - auto channelCount = static_cast(this->format.channels); - if (channelCount <= 0) { - return; - } - - const auto* base = static_cast(data->data) + data->chunk->offset; // NOLINT - const auto* samples = reinterpret_cast(base); - auto sampleCount = static_cast(data->chunk->size / sizeof(float)); - - if (sampleCount < channelCount) { - return; - } - - QVector volumes; - if (auto* audioData = dynamic_cast(this->node->boundData)) { - if (!this->node->shouldUseDevice()) volumes = audioData->volumes(); - } - - this->channelPeaks.fill(0.0f, channelCount); - - auto maxPeak = 0.0f; - for (auto channel = 0; channel < channelCount; channel++) { - auto peak = 0.0f; - for (auto sample = channel; sample < sampleCount; sample += channelCount) { - peak = std::max(peak, std::abs(samples[sample])); // NOLINT - } - - auto visualPeak = std::cbrt(peak); - if (!volumes.isEmpty() && volumes[channel] != 0.0f) visualPeak *= 1.0f / volumes[channel]; - - this->channelPeaks[channel] = visualPeak; - maxPeak = std::max(maxPeak, visualPeak); - } - - this->monitor->updatePeaks(this->channelPeaks, maxPeak); -} - -PwNodePeakMonitor::PwNodePeakMonitor(QObject* parent): QObject(parent) {} - -PwNodePeakMonitor::~PwNodePeakMonitor() { - delete this->mStream; - this->mStream = nullptr; -} - -PwNodeIface* PwNodePeakMonitor::node() const { return this->mNode; } - -void PwNodePeakMonitor::setNode(PwNodeIface* node) { - if (node == this->mNode) return; - - if (this->mNode != nullptr) { - QObject::disconnect(this->mNode, nullptr, this, nullptr); - } - - if (node != nullptr) { - QObject::connect(node, &QObject::destroyed, this, &PwNodePeakMonitor::onNodeDestroyed); - } - - this->mNode = node; - this->mNodeRef.setObject(node != nullptr ? node->node() : nullptr); - this->rebuildStream(); - emit this->nodeChanged(); -} - -bool PwNodePeakMonitor::isEnabled() const { return this->mEnabled; } - -void PwNodePeakMonitor::setEnabled(bool enabled) { - if (enabled == this->mEnabled) return; - this->mEnabled = enabled; - this->rebuildStream(); - emit this->enabledChanged(); -} - -void PwNodePeakMonitor::onNodeDestroyed() { - this->mNode = nullptr; - this->mNodeRef.setObject(nullptr); - this->rebuildStream(); - emit this->nodeChanged(); -} - -void PwNodePeakMonitor::updatePeaks(const QVector& peaks, float peak) { - if (this->mPeaks != peaks) { - this->mPeaks = peaks; - emit this->peaksChanged(); - } - - if (this->mPeak != peak) { - this->mPeak = peak; - emit this->peakChanged(); - } -} - -void PwNodePeakMonitor::updateChannels(const QVector& channels) { - if (this->mChannels == channels) return; - this->mChannels = channels; - emit this->channelsChanged(); -} - -void PwNodePeakMonitor::clearPeaks() { - if (!this->mPeaks.isEmpty()) { - this->mPeaks.clear(); - emit this->peaksChanged(); - } - - if (!this->mChannels.isEmpty()) { - this->mChannels.clear(); - emit this->channelsChanged(); - } - - if (this->mPeak != 0.0f) { - this->mPeak = 0.0f; - emit this->peakChanged(); - } -} - -void PwNodePeakMonitor::rebuildStream() { - delete this->mStream; - this->mStream = nullptr; - - auto* node = this->mNodeRef.object(); - if (!this->mEnabled || node == nullptr) { - this->clearPeaks(); - return; - } - - if (node == nullptr || !node->type.testFlags(PwNodeType::Audio)) { - this->clearPeaks(); - return; - } - - this->mStream = new PwPeakStream(this, node); - if (!this->mStream->start()) { - delete this->mStream; - this->mStream = nullptr; - this->clearPeaks(); - } -} - -} // namespace qs::service::pipewire - -#pragma GCC diagnostic pop diff --git a/src/services/pipewire/peak.hpp b/src/services/pipewire/peak.hpp deleted file mode 100644 index c4af3c2..0000000 --- a/src/services/pipewire/peak.hpp +++ /dev/null @@ -1,87 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "node.hpp" - -namespace qs::service::pipewire { - -class PwNodeIface; -class PwPeakStream; - -} // namespace qs::service::pipewire - -Q_DECLARE_OPAQUE_POINTER(qs::service::pipewire::PwNodeIface*); - -namespace qs::service::pipewire { - -///! Monitors peak levels of an audio node. -/// Tracks volume peaks for a node across all its channels. -/// -/// The peak monitor binds nodes similarly to @@PwObjectTracker when enabled. -class PwNodePeakMonitor: public QObject { - Q_OBJECT; - // clang-format off - /// The node to monitor. Must be an audio node. - Q_PROPERTY(qs::service::pipewire::PwNodeIface* node READ node WRITE setNode NOTIFY nodeChanged); - /// If true, the monitor is actively capturing and computing peaks. Defaults to true. - Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged); - /// Per-channel peak noise levels (0.0-1.0). Length matches @@channels. - /// - /// The channel's volume does not affect this property. - Q_PROPERTY(QVector peaks READ peaks NOTIFY peaksChanged); - /// Maximum value of @@peaks. - Q_PROPERTY(float peak READ peak NOTIFY peakChanged); - /// Channel positions for the captured format. Length matches @@peaks. - Q_PROPERTY(QVector channels READ channels NOTIFY channelsChanged); - // clang-format on - QML_ELEMENT; - -public: - explicit PwNodePeakMonitor(QObject* parent = nullptr); - ~PwNodePeakMonitor() override; - Q_DISABLE_COPY_MOVE(PwNodePeakMonitor); - - [[nodiscard]] PwNodeIface* node() const; - void setNode(PwNodeIface* node); - - [[nodiscard]] bool isEnabled() const; - void setEnabled(bool enabled); - - [[nodiscard]] QVector peaks() const { return this->mPeaks; } - [[nodiscard]] float peak() const { return this->mPeak; } - [[nodiscard]] QVector channels() const { return this->mChannels; } - -signals: - void nodeChanged(); - void enabledChanged(); - void peaksChanged(); - void peakChanged(); - void channelsChanged(); - -private slots: - void onNodeDestroyed(); - -private: - friend class PwPeakStream; - - void updatePeaks(const QVector& peaks, float peak); - void updateChannels(const QVector& channels); - void clearPeaks(); - void rebuildStream(); - - PwNodeIface* mNode = nullptr; - PwBindableRef mNodeRef; - bool mEnabled = true; - QVector mPeaks; - float mPeak = 0.0f; - QVector mChannels; - PwPeakStream* mStream = nullptr; -}; - -} // namespace qs::service::pipewire diff --git a/src/services/pipewire/qml.cpp b/src/services/pipewire/qml.cpp index e4424c1..9efb17e 100644 --- a/src/services/pipewire/qml.cpp +++ b/src/services/pipewire/qml.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -98,8 +99,15 @@ Pipewire::Pipewire(QObject* parent): QObject(parent) { &Pipewire::defaultConfiguredAudioSourceChanged ); - QObject::connect(&connection->registry, &PwRegistry::initialized, this, &Pipewire::readyChanged); - QObject::connect(&connection->registry, &PwRegistry::cleared, this, &Pipewire::readyChanged); + if (!connection->registry.isInitialized()) { + QObject::connect( + &connection->registry, + &PwRegistry::initialized, + this, + &Pipewire::readyChanged, + Qt::SingleShotConnection + ); + } } ObjectModel* Pipewire::nodes() { return &this->mNodes; } @@ -213,7 +221,6 @@ void PwNodeLinkTracker::updateLinks() { || (this->mNode->isSink() && link->inputNode() == this->mNode->id())) { auto* iface = PwLinkGroupIface::instance(link); - if (iface->target()->node()->isMonitor) return; // do not connect twice if (!this->mLinkGroups.contains(iface)) { @@ -232,7 +239,7 @@ void PwNodeLinkTracker::updateLinks() { for (auto* iface: this->mLinkGroups) { // only disconnect no longer used nodes - if (!newLinks.contains(iface) || iface->target()->node()->isMonitor) { + if (!newLinks.contains(iface)) { QObject::disconnect(iface, nullptr, this, nullptr); } } @@ -272,8 +279,6 @@ void PwNodeLinkTracker::onLinkGroupCreated(PwLinkGroup* linkGroup) { || (this->mNode->isSink() && linkGroup->inputNode() == this->mNode->id())) { auto* iface = PwLinkGroupIface::instance(linkGroup); - if (iface->target()->node()->isMonitor) return; - QObject::connect(iface, &QObject::destroyed, this, &PwNodeLinkTracker::onLinkGroupDestroyed); this->mLinkGroups.push_back(iface); emit this->linkGroupsChanged(); diff --git a/src/services/pipewire/qml.hpp b/src/services/pipewire/qml.hpp index a43ce19..e3489a1 100644 --- a/src/services/pipewire/qml.hpp +++ b/src/services/pipewire/qml.hpp @@ -171,13 +171,13 @@ private: ObjectModel mLinkGroups {this}; }; -///! Tracks non-monitor link connections to a given node. +///! Tracks all link connections to a given node. class PwNodeLinkTracker: public QObject { Q_OBJECT; // clang-format off /// The node to track connections to. Q_PROPERTY(qs::service::pipewire::PwNodeIface* node READ node WRITE setNode NOTIFY nodeChanged); - /// Link groups connected to the given node, excluding monitors. + /// Link groups connected to the given node. /// /// If the node is a sink, links which target the node will be tracked. /// If the node is a source, links which source the node will be tracked. diff --git a/src/services/pipewire/registry.cpp b/src/services/pipewire/registry.cpp index 4b670b1..c08fc1d 100644 --- a/src/services/pipewire/registry.cpp +++ b/src/services/pipewire/registry.cpp @@ -134,46 +134,6 @@ void PwRegistry::init(PwCore& core) { this->coreSyncSeq = this->core->sync(PW_ID_CORE); } -void PwRegistry::reset() { - if (this->core != nullptr) { - QObject::disconnect(this->core, nullptr, this, nullptr); - } - - this->listener.remove(); - - if (this->object != nullptr) { - pw_proxy_destroy(reinterpret_cast(this->object)); - this->object = nullptr; - } - - for (auto* meta: this->metadata.values()) { - meta->safeDestroy(); - } - this->metadata.clear(); - - for (auto* link: this->links.values()) { - link->safeDestroy(); - } - this->links.clear(); - - for (auto* node: this->nodes.values()) { - node->safeDestroy(); - } - this->nodes.clear(); - - for (auto* device: this->devices.values()) { - device->safeDestroy(); - } - this->devices.clear(); - - this->linkGroups.clear(); - this->initState = InitState::SendingObjects; - this->coreSyncSeq = 0; - this->core = nullptr; - - emit this->cleared(); -} - void PwRegistry::onCoreSync(quint32 id, qint32 seq) { if (id != PW_ID_CORE || seq != this->coreSyncSeq) return; diff --git a/src/services/pipewire/registry.hpp b/src/services/pipewire/registry.hpp index bb2db8c..8473f04 100644 --- a/src/services/pipewire/registry.hpp +++ b/src/services/pipewire/registry.hpp @@ -116,7 +116,6 @@ class PwRegistry public: void init(PwCore& core); - void reset(); [[nodiscard]] bool isInitialized() const { return this->initState == InitState::Done; } @@ -137,7 +136,6 @@ signals: void linkGroupAdded(PwLinkGroup* group); void metadataAdded(PwMetadata* metadata); void initialized(); - void cleared(); private slots: void onLinkGroupDestroyed(QObject* object); diff --git a/src/services/polkit/CMakeLists.txt b/src/services/polkit/CMakeLists.txt deleted file mode 100644 index 51791d8..0000000 --- a/src/services/polkit/CMakeLists.txt +++ /dev/null @@ -1,35 +0,0 @@ -find_package(PkgConfig REQUIRED) -pkg_check_modules(glib REQUIRED IMPORTED_TARGET glib-2.0>=2.36) -pkg_check_modules(gobject REQUIRED IMPORTED_TARGET gobject-2.0) -pkg_check_modules(polkit_agent REQUIRED IMPORTED_TARGET polkit-agent-1) -pkg_check_modules(polkit REQUIRED IMPORTED_TARGET polkit-gobject-1) - -qt_add_library(quickshell-service-polkit STATIC - agentimpl.cpp - flow.cpp - identity.cpp - listener.cpp - session.cpp - qml.cpp -) - -qt_add_qml_module(quickshell-service-polkit - URI Quickshell.Services.Polkit - VERSION 0.1 - DEPENDENCIES QtQml -) - -install_qml_module(quickshell-service-polkit) - -target_link_libraries(quickshell-service-polkit PRIVATE - Qt::Qml - Qt::Quick - PkgConfig::glib - PkgConfig::gobject - PkgConfig::polkit_agent - PkgConfig::polkit -) - -qs_module_pch(quickshell-service-polkit) - -target_link_libraries(quickshell PRIVATE quickshell-service-polkitplugin) diff --git a/src/services/polkit/agentimpl.cpp b/src/services/polkit/agentimpl.cpp deleted file mode 100644 index 85c62b7..0000000 --- a/src/services/polkit/agentimpl.cpp +++ /dev/null @@ -1,180 +0,0 @@ -#include "agentimpl.hpp" -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "../../core/generation.hpp" -#include "../../core/logcat.hpp" -#include "gobjectref.hpp" -#include "listener.hpp" -#include "qml.hpp" - -namespace { -QS_LOGGING_CATEGORY(logPolkit, "quickshell.service.polkit", QtWarningMsg); -} - -namespace qs::service::polkit { -PolkitAgentImpl* PolkitAgentImpl::instance = nullptr; - -PolkitAgentImpl::PolkitAgentImpl(PolkitAgent* agent) - : QObject(nullptr) - , listener(qs_polkit_agent_new(this), G_OBJECT_NO_REF) - , qmlAgent(agent) - , path(this->qmlAgent->path()) { - auto utf8Path = this->path.toUtf8(); - qs_polkit_agent_register(this->listener.get(), utf8Path.constData()); -} - -PolkitAgentImpl::~PolkitAgentImpl() { this->cancelAllRequests("PolkitAgent is being destroyed"); } - -void PolkitAgentImpl::cancelAllRequests(const QString& reason) { - for (; !this->queuedRequests.empty(); this->queuedRequests.pop_back()) { - AuthRequest* req = this->queuedRequests.back(); - qCDebug(logPolkit) << "destroying queued authentication request for action" << req->actionId; - req->cancel(reason); - delete req; - } - - auto* flow = this->bActiveFlow.value(); - if (flow) { - flow->cancelAuthenticationRequest(); - flow->deleteLater(); - } - - if (this->bIsRegistered.value()) qs_polkit_agent_unregister(this->listener.get()); -} - -PolkitAgentImpl* PolkitAgentImpl::tryGetOrCreate(PolkitAgent* agent) { - if (instance == nullptr) instance = new PolkitAgentImpl(agent); - if (instance->qmlAgent == agent) return instance; - return nullptr; -} - -PolkitAgentImpl* PolkitAgentImpl::tryGet(const PolkitAgent* agent) { - if (instance == nullptr) return nullptr; - if (instance->qmlAgent == agent) return instance; - return nullptr; -} - -PolkitAgentImpl* PolkitAgentImpl::tryTakeoverOrCreate(PolkitAgent* agent) { - if (auto* impl = tryGetOrCreate(agent); impl != nullptr) return impl; - - auto* prevGen = EngineGeneration::findObjectGeneration(instance->qmlAgent); - auto* myGen = EngineGeneration::findObjectGeneration(agent); - if (prevGen == myGen) return nullptr; - - qCDebug(logPolkit) << "taking over listener from previous generation"; - instance->qmlAgent = agent; - instance->setPath(agent->path()); - - return instance; -} - -void PolkitAgentImpl::onEndOfQmlAgent(PolkitAgent* agent) { - if (instance != nullptr && instance->qmlAgent == agent) { - delete instance; - instance = nullptr; - } -} - -void PolkitAgentImpl::setPath(const QString& path) { - if (this->path == path) return; - - this->path = path; - auto utf8Path = path.toUtf8(); - - this->cancelAllRequests("PolkitAgent path changed"); - qs_polkit_agent_unregister(this->listener.get()); - this->bIsRegistered = false; - - qs_polkit_agent_register(this->listener.get(), utf8Path.constData()); -} - -void PolkitAgentImpl::registerComplete(bool success) { - if (success) this->bIsRegistered = true; - else qCWarning(logPolkit) << "failed to register listener on path" << this->qmlAgent->path(); -} - -void PolkitAgentImpl::initiateAuthentication(AuthRequest* request) { - qCDebug(logPolkit) << "incoming authentication request for action" << request->actionId; - - this->queuedRequests.emplace_back(request); - - if (this->queuedRequests.size() == 1) { - this->activateAuthenticationRequest(); - } -} - -void PolkitAgentImpl::cancelAuthentication(AuthRequest* request) { - qCDebug(logPolkit) << "cancelling authentication request from agent"; - - auto* flow = this->bActiveFlow.value(); - if (flow && flow->authRequest() == request) { - flow->cancelFromAgent(); - } else if (auto it = std::ranges::find(this->queuedRequests, request); - it != this->queuedRequests.end()) - { - qCDebug(logPolkit) << "removing queued authentication request for action" << (*it)->actionId; - (*it)->cancel("Authentication request was cancelled"); - delete (*it); - this->queuedRequests.erase(it); - } else { - qCWarning(logPolkit) << "the cancelled request was not found in the queue."; - } -} - -void PolkitAgentImpl::activateAuthenticationRequest() { - if (this->queuedRequests.empty()) return; - - AuthRequest* req = this->queuedRequests.front(); - this->queuedRequests.pop_front(); - qCDebug(logPolkit) << "activating authentication request for action" << req->actionId - << ", cookie: " << req->cookie; - - QList identities; - for (auto& identity: req->identities) { - auto* obj = Identity::fromPolkitIdentity(identity); - if (obj) identities.append(obj); - } - if (identities.isEmpty()) { - qCWarning( - logPolkit - ) << "no supported identities available for authentication request, cancelling."; - req->cancel("Error requesting authentication: no supported identities available."); - delete req; - return; - } - - this->bActiveFlow = new AuthFlow(req, std::move(identities)); - - QObject::connect( - this->bActiveFlow.value(), - &AuthFlow::isCompletedChanged, - this, - &PolkitAgentImpl::finishAuthenticationRequest - ); - - emit this->qmlAgent->authenticationRequestStarted(); -} - -void PolkitAgentImpl::finishAuthenticationRequest() { - if (!this->bActiveFlow.value()) return; - - qCDebug(logPolkit) << "finishing authentication request for action" - << this->bActiveFlow.value()->actionId(); - - this->bActiveFlow.value()->deleteLater(); - - if (!this->queuedRequests.empty()) { - this->activateAuthenticationRequest(); - } else { - this->bActiveFlow = nullptr; - } -} -} // namespace qs::service::polkit diff --git a/src/services/polkit/agentimpl.hpp b/src/services/polkit/agentimpl.hpp deleted file mode 100644 index 65ae11a..0000000 --- a/src/services/polkit/agentimpl.hpp +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once - -#include - -#include -#include - -#include "flow.hpp" -#include "gobjectref.hpp" -#include "listener.hpp" - -namespace qs::service::polkit { -class PolkitAgent; - -class PolkitAgentImpl - : public QObject - , public ListenerCb { - Q_OBJECT; - Q_DISABLE_COPY_MOVE(PolkitAgentImpl); - -public: - ~PolkitAgentImpl() override; - - static PolkitAgentImpl* tryGetOrCreate(PolkitAgent* agent); - static PolkitAgentImpl* tryGet(const PolkitAgent* agent); - static PolkitAgentImpl* tryTakeoverOrCreate(PolkitAgent* agent); - static void onEndOfQmlAgent(PolkitAgent* agent); - - [[nodiscard]] QBindable activeFlow() { return &this->bActiveFlow; }; - [[nodiscard]] QBindable isRegistered() { return &this->bIsRegistered; }; - - [[nodiscard]] const QString& getPath() const { return this->path; } - void setPath(const QString& path); - - void initiateAuthentication(AuthRequest* request) override; - void cancelAuthentication(AuthRequest* request) override; - void registerComplete(bool success) override; - - void cancelAllRequests(const QString& reason); - -signals: - void activeFlowChanged(); - void isRegisteredChanged(); - -private: - PolkitAgentImpl(PolkitAgent* agent); - - static PolkitAgentImpl* instance; - - /// Start handling of the next authentication request in the queue. - void activateAuthenticationRequest(); - /// Finalize and remove the current authentication request. - void finishAuthenticationRequest(); - - GObjectRef listener; - PolkitAgent* qmlAgent = nullptr; - QString path; - - std::deque queuedRequests; - - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(PolkitAgentImpl, AuthFlow*, bActiveFlow, &PolkitAgentImpl::activeFlowChanged); - Q_OBJECT_BINDABLE_PROPERTY(PolkitAgentImpl, bool, bIsRegistered, &PolkitAgentImpl::isRegisteredChanged); - // clang-format on -}; -} // namespace qs::service::polkit diff --git a/src/services/polkit/flow.cpp b/src/services/polkit/flow.cpp deleted file mode 100644 index 2a709eb..0000000 --- a/src/services/polkit/flow.cpp +++ /dev/null @@ -1,163 +0,0 @@ -#include "flow.hpp" -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "../../core/logcat.hpp" -#include "identity.hpp" -#include "qml.hpp" -#include "session.hpp" - -namespace { -QS_LOGGING_CATEGORY(logPolkitState, "quickshell.service.polkit.state", QtWarningMsg); -} - -namespace qs::service::polkit { -AuthFlow::AuthFlow(AuthRequest* request, QList&& identities, QObject* parent) - : QObject(parent) - , mRequest(request) - , mIdentities(std::move(identities)) - , bSelectedIdentity(this->mIdentities.isEmpty() ? nullptr : this->mIdentities.first()) { - // We reject auth requests with no identities before a flow is created. - // This should never happen. - if (!this->bSelectedIdentity.value()) - qCFatal(logPolkitState) << "AuthFlow created with no valid identities!"; - - for (auto* identity: this->mIdentities) { - identity->setParent(this); - } - - this->setupSession(); -} - -AuthFlow::~AuthFlow() { delete this->mRequest; }; - -void AuthFlow::setSelectedIdentity(Identity* identity) { - if (this->bSelectedIdentity.value() == identity) return; - if (!identity) { - qmlWarning(this) << "Cannot set selected identity to null."; - return; - } - this->bSelectedIdentity = identity; - this->currentSession->cancel(); - this->setupSession(); -} - -void AuthFlow::cancelFromAgent() { - if (!this->currentSession) return; - - qCDebug(logPolkitState) << "cancelling authentication request from agent"; - - // Session cancel can immediately call the cancel handler, which also - // performs property updates. - Qt::beginPropertyUpdateGroup(); - this->bIsCancelled = true; - this->currentSession->cancel(); - Qt::endPropertyUpdateGroup(); - - emit this->authenticationRequestCancelled(); - - this->mRequest->cancel("Authentication request cancelled by agent."); -} - -void AuthFlow::submit(const QString& value) { - if (!this->currentSession) return; - - qCDebug(logPolkitState) << "submitting response to authentication request"; - - this->currentSession->respond(value); - - Qt::beginPropertyUpdateGroup(); - this->bIsResponseRequired = false; - this->bInputPrompt = QString(); - this->bResponseVisible = false; - Qt::endPropertyUpdateGroup(); -} - -void AuthFlow::cancelAuthenticationRequest() { - if (!this->currentSession) return; - - qCDebug(logPolkitState) << "cancelling authentication request by user request"; - - // Session cancel can immediately call the cancel handler, which also - // performs property updates. - Qt::beginPropertyUpdateGroup(); - this->bIsCancelled = true; - this->currentSession->cancel(); - Qt::endPropertyUpdateGroup(); - - this->mRequest->cancel("Authentication request cancelled by user."); -} - -void AuthFlow::setupSession() { - delete this->currentSession; - - qCDebug(logPolkitState) << "setting up session for identity" - << this->bSelectedIdentity.value()->name(); - - this->currentSession = new Session( - this->bSelectedIdentity.value()->polkitIdentity.get(), - this->mRequest->cookie, - this - ); - QObject::connect(this->currentSession, &Session::request, this, &AuthFlow::request); - QObject::connect(this->currentSession, &Session::completed, this, &AuthFlow::completed); - QObject::connect(this->currentSession, &Session::showError, this, &AuthFlow::showError); - QObject::connect(this->currentSession, &Session::showInfo, this, &AuthFlow::showInfo); - this->currentSession->initiate(); -} - -void AuthFlow::request(const QString& message, bool echo) { - Qt::beginPropertyUpdateGroup(); - this->bIsResponseRequired = true; - this->bInputPrompt = message; - this->bResponseVisible = echo; - Qt::endPropertyUpdateGroup(); -} - -void AuthFlow::completed(bool gainedAuthorization) { - qCDebug(logPolkitState) << "authentication session completed, gainedAuthorization =" - << gainedAuthorization << ", isCancelled =" << this->bIsCancelled.value(); - - if (gainedAuthorization) { - Qt::beginPropertyUpdateGroup(); - this->bIsCompleted = true; - this->bIsSuccessful = true; - Qt::endPropertyUpdateGroup(); - - this->mRequest->complete(); - - emit this->authenticationSucceeded(); - } else if (this->bIsCancelled.value()) { - Qt::beginPropertyUpdateGroup(); - this->bIsCompleted = true; - this->bIsSuccessful = false; - Qt::endPropertyUpdateGroup(); - } else { - this->bFailed = true; - emit this->authenticationFailed(); - - this->setupSession(); - } -} - -void AuthFlow::showError(const QString& message) { - Qt::beginPropertyUpdateGroup(); - this->bSupplementaryMessage = message; - this->bSupplementaryIsError = true; - Qt::endPropertyUpdateGroup(); -} - -void AuthFlow::showInfo(const QString& message) { - Qt::beginPropertyUpdateGroup(); - this->bSupplementaryMessage = message; - this->bSupplementaryIsError = false; - Qt::endPropertyUpdateGroup(); -} -} // namespace qs::service::polkit diff --git a/src/services/polkit/flow.hpp b/src/services/polkit/flow.hpp deleted file mode 100644 index 0b7e845..0000000 --- a/src/services/polkit/flow.hpp +++ /dev/null @@ -1,179 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "../../core/retainable.hpp" -#include "identity.hpp" -#include "listener.hpp" - -namespace qs::service::polkit { -class Session; - -class AuthFlow - : public QObject - , public Retainable { - Q_OBJECT; - QML_ELEMENT; - Q_DISABLE_COPY_MOVE(AuthFlow); - QML_UNCREATABLE("AuthFlow can only be obtained from PolkitAgent."); - - // clang-format off - /// The main message to present to the user. - Q_PROPERTY(QString message READ message CONSTANT); - - /// The icon to present to the user in association with the message. - /// - /// The icon name follows the [FreeDesktop icon naming specification](https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html). - /// Use @@Quickshell.Quickshell.iconPath() to resolve the icon name to an - /// actual file path for display. - Q_PROPERTY(QString iconName READ iconName CONSTANT); - - /// The action ID represents the action that is being authorized. - /// - /// This is a machine-readable identifier. - Q_PROPERTY(QString actionId READ actionId CONSTANT); - - /// A cookie that identifies this authentication request. - /// - /// This is an internal identifier and not recommended to show to users. - Q_PROPERTY(QString cookie READ cookie CONSTANT); - - /// The list of identities that may be used to authenticate. - /// - /// Each identity may be a user or a group. You may select any of them to - /// authenticate by setting @@selectedIdentity. By default, the first identity - /// in the list is selected. - Q_PROPERTY(QList identities READ identities CONSTANT); - - /// The identity that will be used to authenticate. - /// - /// Changing this will abort any ongoing authentication conversations and start a new one. - Q_PROPERTY(Identity* selectedIdentity READ default WRITE setSelectedIdentity NOTIFY selectedIdentityChanged BINDABLE selectedIdentity); - - /// Indicates that a response from the user is required from the user, - /// typically a password. - Q_PROPERTY(bool isResponseRequired READ default NOTIFY isResponseRequiredChanged BINDABLE isResponseRequired); - - /// This message is used to prompt the user for required input. - Q_PROPERTY(QString inputPrompt READ default NOTIFY inputPromptChanged BINDABLE inputPrompt); - - /// Indicates whether the user's response should be visible. (e.g. for passwords this should be false) - Q_PROPERTY(bool responseVisible READ default NOTIFY responseVisibleChanged BINDABLE responseVisible); - - /// An additional message to present to the user. - /// - /// This may be used to show errors or supplementary information. - /// See @@supplementaryIsError to determine if this is an error message. - Q_PROPERTY(QString supplementaryMessage READ default NOTIFY supplementaryMessageChanged BINDABLE supplementaryMessage); - - /// Indicates whether the supplementary message is an error. - Q_PROPERTY(bool supplementaryIsError READ default NOTIFY supplementaryIsErrorChanged BINDABLE supplementaryIsError); - - /// Has the authentication request been completed. - Q_PROPERTY(bool isCompleted READ default NOTIFY isCompletedChanged BINDABLE isCompleted); - - /// Indicates whether the authentication request was successful. - Q_PROPERTY(bool isSuccessful READ default NOTIFY isSuccessfulChanged BINDABLE isSuccessful); - - /// Indicates whether the current authentication request was cancelled. - Q_PROPERTY(bool isCancelled READ default NOTIFY isCancelledChanged BINDABLE isCancelled); - - /// Indicates whether an authentication attempt has failed at least once during this authentication flow. - Q_PROPERTY(bool failed READ default NOTIFY failedChanged BINDABLE failed); - // clang-format on - -public: - explicit AuthFlow(AuthRequest* request, QList&& identities, QObject* parent = nullptr); - ~AuthFlow() override; - - /// Cancel the ongoing authentication request from the agent side. - void cancelFromAgent(); - - /// Submit a response to a request that was previously emitted. Typically the password. - Q_INVOKABLE void submit(const QString& value); - /// Cancel the ongoing authentication request from the user side. - Q_INVOKABLE void cancelAuthenticationRequest(); - - [[nodiscard]] const QString& message() const { return this->mRequest->message; }; - [[nodiscard]] const QString& iconName() const { return this->mRequest->iconName; }; - [[nodiscard]] const QString& actionId() const { return this->mRequest->actionId; }; - [[nodiscard]] const QString& cookie() const { return this->mRequest->cookie; }; - [[nodiscard]] const QList& identities() const { return this->mIdentities; }; - - [[nodiscard]] QBindable selectedIdentity() { return &this->bSelectedIdentity; }; - void setSelectedIdentity(Identity* identity); - - [[nodiscard]] QBindable isResponseRequired() { return &this->bIsResponseRequired; }; - [[nodiscard]] QBindable inputPrompt() { return &this->bInputPrompt; }; - [[nodiscard]] QBindable responseVisible() { return &this->bResponseVisible; }; - - [[nodiscard]] QBindable supplementaryMessage() { return &this->bSupplementaryMessage; }; - [[nodiscard]] QBindable supplementaryIsError() { return &this->bSupplementaryIsError; }; - - [[nodiscard]] QBindable isCompleted() { return &this->bIsCompleted; }; - [[nodiscard]] QBindable isSuccessful() { return &this->bIsSuccessful; }; - [[nodiscard]] QBindable isCancelled() { return &this->bIsCancelled; }; - [[nodiscard]] QBindable failed() { return &this->bFailed; }; - - [[nodiscard]] AuthRequest* authRequest() const { return this->mRequest; }; - -signals: - /// Emitted whenever an authentication request completes successfully. - void authenticationSucceeded(); - - /// Emitted whenever an authentication request completes unsuccessfully. - /// - /// This may be because the user entered the wrong password or otherwise - /// failed to authenticate. - /// This signal is not emmitted when the user canceled the request or it - /// was cancelled by the PolKit daemon. - /// - /// After this signal, a new session is automatically started for the same - /// identity. - void authenticationFailed(); - - /// Emmitted when on ongoing authentication request is cancelled by the PolKit daemon. - void authenticationRequestCancelled(); - - void selectedIdentityChanged(); - void isResponseRequiredChanged(); - void inputPromptChanged(); - void responseVisibleChanged(); - void supplementaryMessageChanged(); - void supplementaryIsErrorChanged(); - void isCompletedChanged(); - void isSuccessfulChanged(); - void isCancelledChanged(); - void failedChanged(); - -private slots: - // Signals received from session objects. - void request(const QString& message, bool echo); - void completed(bool gainedAuthorization); - void showError(const QString& message); - void showInfo(const QString& message); - -private: - /// Start a session for the currently selected identity and the current request. - void setupSession(); - - Session* currentSession = nullptr; - AuthRequest* mRequest = nullptr; - QList mIdentities; - - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(AuthFlow, Identity*, bSelectedIdentity, &AuthFlow::selectedIdentityChanged); - Q_OBJECT_BINDABLE_PROPERTY(AuthFlow, bool, bIsResponseRequired, &AuthFlow::isResponseRequiredChanged); - Q_OBJECT_BINDABLE_PROPERTY(AuthFlow, QString, bInputPrompt, &AuthFlow::inputPromptChanged); - Q_OBJECT_BINDABLE_PROPERTY(AuthFlow, bool, bResponseVisible, &AuthFlow::responseVisibleChanged); - Q_OBJECT_BINDABLE_PROPERTY(AuthFlow, QString, bSupplementaryMessage, &AuthFlow::supplementaryMessageChanged); - Q_OBJECT_BINDABLE_PROPERTY(AuthFlow, bool, bSupplementaryIsError, &AuthFlow::supplementaryIsErrorChanged); - Q_OBJECT_BINDABLE_PROPERTY(AuthFlow, bool, bIsCompleted, &AuthFlow::isCompletedChanged); - Q_OBJECT_BINDABLE_PROPERTY(AuthFlow, bool, bIsSuccessful, &AuthFlow::isSuccessfulChanged); - Q_OBJECT_BINDABLE_PROPERTY(AuthFlow, bool, bIsCancelled, &AuthFlow::isCancelledChanged); - Q_OBJECT_BINDABLE_PROPERTY(AuthFlow, bool, bFailed, &AuthFlow::failedChanged); - // clang-format on -}; -} // namespace qs::service::polkit diff --git a/src/services/polkit/gobjectref.hpp b/src/services/polkit/gobjectref.hpp deleted file mode 100644 index cd29a9d..0000000 --- a/src/services/polkit/gobjectref.hpp +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#include - -namespace qs::service::polkit { - -struct GObjectNoRefTag {}; -constexpr GObjectNoRefTag G_OBJECT_NO_REF; - -template -class GObjectRef { -public: - explicit GObjectRef(T* ptr = nullptr): ptr(ptr) { - if (this->ptr) { - g_object_ref(this->ptr); - } - } - - explicit GObjectRef(T* ptr, GObjectNoRefTag /*tag*/): ptr(ptr) {} - - ~GObjectRef() { - if (this->ptr) { - g_object_unref(this->ptr); - } - } - - // We do handle self-assignment in a more general case by checking the - // included pointers rather than the wrapper objects themselves. - // NOLINTBEGIN(bugprone-unhandled-self-assignment) - - GObjectRef(const GObjectRef& other): GObjectRef(other.ptr) {} - GObjectRef& operator=(const GObjectRef& other) { - if (*this == other) return *this; - if (this->ptr) { - g_object_unref(this->ptr); - } - this->ptr = other.ptr; - if (this->ptr) { - g_object_ref(this->ptr); - } - return *this; - } - - GObjectRef(GObjectRef&& other) noexcept: ptr(other.ptr) { other.ptr = nullptr; } - GObjectRef& operator=(GObjectRef&& other) noexcept { - if (*this == other) return *this; - if (this->ptr) { - g_object_unref(this->ptr); - } - this->ptr = other.ptr; - other.ptr = nullptr; - return *this; - } - - // NOLINTEND(bugprone-unhandled-self-assignment) - - [[nodiscard]] T* get() const { return this->ptr; } - T* operator->() const { return this->ptr; } - - bool operator==(const GObjectRef& other) const { return this->ptr == other.ptr; } - -private: - T* ptr; -}; -} // namespace qs::service::polkit \ No newline at end of file diff --git a/src/services/polkit/identity.cpp b/src/services/polkit/identity.cpp deleted file mode 100644 index 7be5f39..0000000 --- a/src/services/polkit/identity.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include "identity.hpp" -#include -#include -#include - -#include -#include -#include - -#define POLKIT_AGENT_I_KNOW_API_IS_SUBJECT_TO_CHANGE -// Workaround macro collision with glib 'signals' struct member. -#undef signals -#include -#define signals Q_SIGNALS -#include -#include -#include - -#include "gobjectref.hpp" - -namespace qs::service::polkit { -Identity::Identity( - id_t id, - QString name, - QString displayName, - bool isGroup, - GObjectRef polkitIdentity, - QObject* parent -) - : QObject(parent) - , polkitIdentity(std::move(polkitIdentity)) - , mId(id) - , mName(std::move(name)) - , mDisplayName(std::move(displayName)) - , mIsGroup(isGroup) {} - -Identity* Identity::fromPolkitIdentity(GObjectRef identity) { - if (POLKIT_IS_UNIX_USER(identity.get())) { - auto uid = polkit_unix_user_get_uid(POLKIT_UNIX_USER(identity.get())); - - auto bufSize = sysconf(_SC_GETPW_R_SIZE_MAX); - // The call can fail with -1, in this case choose a default that is - // big enough. - if (bufSize == -1) bufSize = 16384; - auto buffer = std::vector(bufSize); - - std::aligned_storage_t pwBuf; - passwd* pw = nullptr; - getpwuid_r(uid, reinterpret_cast(&pwBuf), buffer.data(), bufSize, &pw); - - auto name = - (pw && pw->pw_name && *pw->pw_name) ? QString::fromUtf8(pw->pw_name) : QString::number(uid); - - return new Identity( - uid, - name, - (pw && pw->pw_gecos && *pw->pw_gecos) ? QString::fromUtf8(pw->pw_gecos) : name, - false, - std::move(identity) - ); - } - - if (POLKIT_IS_UNIX_GROUP(identity.get())) { - auto gid = polkit_unix_group_get_gid(POLKIT_UNIX_GROUP(identity.get())); - - auto bufSize = sysconf(_SC_GETGR_R_SIZE_MAX); - // The call can fail with -1, in this case choose a default that is - // big enough. - if (bufSize == -1) bufSize = 16384; - auto buffer = std::vector(bufSize); - - std::aligned_storage_t grBuf; - group* gr = nullptr; - getgrgid_r(gid, reinterpret_cast(&grBuf), buffer.data(), bufSize, &gr); - - auto name = - (gr && gr->gr_name && *gr->gr_name) ? QString::fromUtf8(gr->gr_name) : QString::number(gid); - return new Identity(gid, name, name, true, std::move(identity)); - } - - // A different type of identity is netgroup. - return nullptr; -} -} // namespace qs::service::polkit diff --git a/src/services/polkit/identity.hpp b/src/services/polkit/identity.hpp deleted file mode 100644 index 27f3c1c..0000000 --- a/src/services/polkit/identity.hpp +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -#include -#include - -#include "gobjectref.hpp" - -// _PolkitIdentity is considered a reserved identifier, but I am specifically -// forward declaring this reserved name. -using PolkitIdentity = struct _PolkitIdentity; // NOLINT(bugprone-reserved-identifier) - -namespace qs::service::polkit { -//! Represents a user or group that can be used to authenticate. -class Identity: public QObject { - Q_OBJECT; - Q_DISABLE_COPY_MOVE(Identity); - - // clang-format off - /// The Id of the identity. If the identity is a user, this is the user's uid. See @@isGroup. - Q_PROPERTY(quint32 id READ id CONSTANT); - - /// The name of the user or group. - /// - /// If available, this is the actual username or group name, but may fallback to the ID. - Q_PROPERTY(QString string READ name CONSTANT); - - /// The full name of the user or group, if available. Otherwise the same as @@name. - Q_PROPERTY(QString displayName READ displayName CONSTANT); - - /// Indicates if this identity is a group or a user. - /// - /// If true, @@id is a gid, otherwise it is a uid. - Q_PROPERTY(bool isGroup READ isGroup CONSTANT); - - QML_UNCREATABLE("Identities cannot be created directly."); - // clang-format on - -public: - explicit Identity( - id_t id, - QString name, - QString displayName, - bool isGroup, - GObjectRef polkitIdentity, - QObject* parent = nullptr - ); - ~Identity() override = default; - - static Identity* fromPolkitIdentity(GObjectRef identity); - - [[nodiscard]] quint32 id() const { return static_cast(this->mId); }; - [[nodiscard]] const QString& name() const { return this->mName; }; - [[nodiscard]] const QString& displayName() const { return this->mDisplayName; }; - [[nodiscard]] bool isGroup() const { return this->mIsGroup; }; - - GObjectRef polkitIdentity; - -private: - id_t mId; - QString mName; - QString mDisplayName; - bool mIsGroup; -}; -} // namespace qs::service::polkit diff --git a/src/services/polkit/listener.cpp b/src/services/polkit/listener.cpp deleted file mode 100644 index e4bca4c..0000000 --- a/src/services/polkit/listener.cpp +++ /dev/null @@ -1,232 +0,0 @@ -#include "listener.hpp" -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../core/logcat.hpp" -#include "gobjectref.hpp" -#include "qml.hpp" - -namespace { -QS_LOGGING_CATEGORY(logPolkitListener, "quickshell.service.polkit.listener", QtWarningMsg); -} - -using qs::service::polkit::GObjectRef; - -// This is mostly GObject code, we follow their naming conventions for improved -// clarity and to mark it as such. Additionally, many methods need to be static -// to conform with the expected declarations. -// NOLINTBEGIN(readability-identifier-naming,misc-use-anonymous-namespace) - -using QsPolkitAgent = struct _QsPolkitAgent { - PolkitAgentListener parent_instance; - - qs::service::polkit::ListenerCb* cb; - gpointer registration_handle; -}; - -G_DEFINE_TYPE(QsPolkitAgent, qs_polkit_agent, POLKIT_AGENT_TYPE_LISTENER) - -static void initiate_authentication( - PolkitAgentListener* listener, - const gchar* actionId, - const gchar* message, - const gchar* iconName, - PolkitDetails* details, - const gchar* cookie, - GList* identities, - GCancellable* cancellable, - GAsyncReadyCallback callback, - gpointer userData -); - -static gboolean -initiate_authentication_finish(PolkitAgentListener* listener, GAsyncResult* result, GError** error); - -static void qs_polkit_agent_init(QsPolkitAgent* self) { - self->cb = nullptr; - self->registration_handle = nullptr; -} - -static void qs_polkit_agent_finalize(GObject* object) { - if (G_OBJECT_CLASS(qs_polkit_agent_parent_class)) - G_OBJECT_CLASS(qs_polkit_agent_parent_class)->finalize(object); -} - -static void qs_polkit_agent_class_init(QsPolkitAgentClass* klass) { - GObjectClass* gobject_class = G_OBJECT_CLASS(klass); - gobject_class->finalize = qs_polkit_agent_finalize; - - PolkitAgentListenerClass* listener_class = POLKIT_AGENT_LISTENER_CLASS(klass); - listener_class->initiate_authentication = initiate_authentication; - listener_class->initiate_authentication_finish = initiate_authentication_finish; -} - -QsPolkitAgent* qs_polkit_agent_new(qs::service::polkit::ListenerCb* cb) { - QsPolkitAgent* self = QS_POLKIT_AGENT(g_object_new(QS_TYPE_POLKIT_AGENT, nullptr)); - self->cb = cb; - return self; -} - -struct RegisterCbData { - GObjectRef agent; - std::string path; -}; - -static void qs_polkit_agent_register_cb(GObject* /*unused*/, GAsyncResult* res, gpointer userData); -void qs_polkit_agent_register(QsPolkitAgent* agent, const char* path) { - if (path == nullptr || *path == '\0') { - qCWarning(logPolkitListener) << "cannot register listener without a path set."; - agent->cb->registerComplete(false); - return; - } - - auto* data = new RegisterCbData {.agent = GObjectRef(agent), .path = path}; - polkit_unix_session_new_for_process(getpid(), nullptr, &qs_polkit_agent_register_cb, data); -} - -static void qs_polkit_agent_register_cb(GObject* /*unused*/, GAsyncResult* res, gpointer userData) { - std::unique_ptr data(reinterpret_cast(userData)); - - GError* error = nullptr; - auto* subject = polkit_unix_session_new_for_process_finish(res, &error); - - if (subject == nullptr || error != nullptr) { - qCWarning(logPolkitListener) << "failed to create subject for listener:" - << (error ? error->message : ""); - g_clear_error(&error); - data->agent->cb->registerComplete(false); - return; - } - - data->agent->registration_handle = polkit_agent_listener_register( - POLKIT_AGENT_LISTENER(data->agent.get()), - POLKIT_AGENT_REGISTER_FLAGS_NONE, - subject, - data->path.c_str(), - nullptr, - &error - ); - - g_object_unref(subject); - - if (error != nullptr) { - qCWarning(logPolkitListener) << "failed to register listener:" << error->message; - g_clear_error(&error); - data->agent->cb->registerComplete(false); - return; - } - - data->agent->cb->registerComplete(true); -} - -void qs_polkit_agent_unregister(QsPolkitAgent* agent) { - if (agent->registration_handle != nullptr) { - polkit_agent_listener_unregister(agent->registration_handle); - agent->registration_handle = nullptr; - } -} - -static void authentication_cancelled_cb(GCancellable* /*unused*/, gpointer userData) { - auto* request = static_cast(userData); - request->cb->cancelAuthentication(request); -} - -static void initiate_authentication( - PolkitAgentListener* listener, - const gchar* actionId, - const gchar* message, - const gchar* iconName, - PolkitDetails* /*unused*/, - const gchar* cookie, - GList* identities, - GCancellable* cancellable, - GAsyncReadyCallback callback, - gpointer userData -) { - auto* self = QS_POLKIT_AGENT(listener); - - auto* asyncResult = g_task_new(reinterpret_cast(self), nullptr, callback, userData); - - // Identities may be duplicated, so we use the hash to filter them out. - std::unordered_set identitySet; - std::vector> identityVector; - for (auto* item = g_list_first(identities); item != nullptr; item = g_list_next(item)) { - auto* identity = static_cast(item->data); - if (identitySet.contains(polkit_identity_hash(identity))) continue; - - identitySet.insert(polkit_identity_hash(identity)); - // The caller unrefs all identities after we return, therefore we need to - // take our own reference for the identities we keep. Our wrapper does - // this automatically. - identityVector.emplace_back(identity); - } - - // The original strings are freed by the caller after we return, so we - // copy them into QStrings. - auto* request = new qs::service::polkit::AuthRequest { - .actionId = QString::fromUtf8(actionId), - .message = QString::fromUtf8(message), - .iconName = QString::fromUtf8(iconName), - .cookie = QString::fromUtf8(cookie), - .identities = std::move(identityVector), - - .task = asyncResult, - .cancellable = cancellable, - .handlerId = 0, - .cb = self->cb - }; - - if (cancellable != nullptr) { - request->handlerId = g_cancellable_connect( - cancellable, - reinterpret_cast(authentication_cancelled_cb), - request, - nullptr - ); - } - - self->cb->initiateAuthentication(request); -} - -static gboolean initiate_authentication_finish( - PolkitAgentListener* /*unused*/, - GAsyncResult* result, - GError** error -) { - return g_task_propagate_boolean(G_TASK(result), error); -} - -namespace qs::service::polkit { -// While these functions can be const since they do not modify member variables, -// they are logically non-const since they modify the state of the -// authentication request. Therefore, we do not mark them as const. -// NOLINTBEGIN(readability-make-member-function-const) -void AuthRequest::complete() { g_task_return_boolean(this->task, true); } - -void AuthRequest::cancel(const QString& reason) { - auto utf8Reason = reason.toUtf8(); - g_task_return_new_error( - this->task, - POLKIT_ERROR, - POLKIT_ERROR_CANCELLED, - "%s", - utf8Reason.constData() - ); -} -// NOLINTEND(readability-make-member-function-const) -} // namespace qs::service::polkit - -// NOLINTEND(readability-identifier-naming,misc-use-anonymous-namespace) diff --git a/src/services/polkit/listener.hpp b/src/services/polkit/listener.hpp deleted file mode 100644 index 996fa23..0000000 --- a/src/services/polkit/listener.hpp +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once - -#include - -#define POLKIT_AGENT_I_KNOW_API_IS_SUBJECT_TO_CHANGE -// This causes a problem with variables of the name. -#undef signals - -#include -#include - -#define signals Q_SIGNALS - -#include "gobjectref.hpp" - -namespace qs::service::polkit { -class ListenerCb; -//! All state that comes in from PolKit about an authentication request. -struct AuthRequest { - //! The action ID that this session is for. - QString actionId; - //! Message to present to the user. - QString message; - //! Icon name according to the FreeDesktop specification. May be empty. - QString iconName; - // Details intentionally omitted because nothing seems to use them. - QString cookie; - //! List of users/groups that can be used for authentication. - std::vector> identities; - - //! Implementation detail to mark authentication done. - GTask* task; - //! Implementation detail for requests cancelled by agent. - GCancellable* cancellable; - //! Callback handler ID for the cancellable. - gulong handlerId; - //! Callbacks for the listener - ListenerCb* cb; - - void complete(); - void cancel(const QString& reason); -}; - -//! Callback interface for PolkitAgent listener events. -class ListenerCb { -public: - ListenerCb() = default; - virtual ~ListenerCb() = default; - Q_DISABLE_COPY_MOVE(ListenerCb); - - //! Called when the agent registration is complete. - virtual void registerComplete(bool success) = 0; - //! Called when an authentication request is initiated by PolKit. - virtual void initiateAuthentication(AuthRequest* request) = 0; - //! Called when an authentication request is cancelled by PolKit before completion. - virtual void cancelAuthentication(AuthRequest* request) = 0; -}; -} // namespace qs::service::polkit - -G_BEGIN_DECLS - -// This is GObject code. By using their naming conventions, we clearly mark it -// as such for the rest of the project. -// NOLINTBEGIN(readability-identifier-naming) - -#define QS_TYPE_POLKIT_AGENT (qs_polkit_agent_get_type()) -G_DECLARE_FINAL_TYPE(QsPolkitAgent, qs_polkit_agent, QS, POLKIT_AGENT, PolkitAgentListener) - -QsPolkitAgent* qs_polkit_agent_new(qs::service::polkit::ListenerCb* cb); -void qs_polkit_agent_register(QsPolkitAgent* agent, const char* path); -void qs_polkit_agent_unregister(QsPolkitAgent* agent); - -// NOLINTEND(readability-identifier-naming) - -G_END_DECLS diff --git a/src/services/polkit/module.md b/src/services/polkit/module.md deleted file mode 100644 index b306ecb..0000000 --- a/src/services/polkit/module.md +++ /dev/null @@ -1,52 +0,0 @@ -name = "Quickshell.Services.Polkit" -description = "Polkit Agent" -headers = [ - "agentimpl.hpp", - "flow.hpp", - "identity.hpp", - "listener.hpp", - "qml.hpp", - "session.hpp", -] ------ -## Purpose of a Polkit Agent - -PolKit is a system for privileged applications to query if a user is permitted to execute an action. -You have probably seen it in the form of a "Please enter your password to continue with X" dialog box before. -This dialog box is presented by your *PolKit agent*, it is a process running as your user that accepts authentication requests from the *daemon* and presents them to you to accept or deny. - -This service enables writing a PolKit agent in Quickshell. - -## Implementing a Polkit Agent - -The backend logic of communicating with the daemon is handled by the @@Quickshell.Services.Polkit.PolkitAgent object. -It exposes incoming requests via @@Quickshell.Services.Polkit.PolkitAgent.flow and provides appropriate signals. - -### Flow of an authentication request - -Incoming authentication requests are queued in the order that they arrive. -If none is queued, a request starts processing right away. -Otherwise, it will wait until prior requests are done. - -A request starts by emitting the @@Quickshell.Services.Polkit.PolkitAgent.authenticationRequestStarted signal. -At this point, information like the action to be performed and permitted users that can authenticate is available. - -An authentication *session* for the request is immediately started, which internally starts a PAM conversation that is likely to prompt for user input. -* Additional prompts may be shared with the user by way of the @@Quickshell.Services.Polkit.AuthFlow.supplementaryMessageChanged / @@Quickshell.Services.Polkit.AuthFlow.supplementaryIsErrorChanged signals and the @@Quickshell.Services.Polkit.AuthFlow.supplementaryMessage and @@Quickshell.Services.Polkit.AuthFlow.supplementaryIsError properties. A common message might be 'Please input your password'. -* An input request is forwarded via the @@Quickshell.Services.Polkit.AuthFlow.isResponseRequiredChanged / @@Quickshell.Services.Polkit.AuthFlow.inputPromptChanged / @@Quickshell.Services.Polkit.AuthFlow.responseVisibleChanged signals and the corresponding properties. Note that the request specifies whether the text box should show the typed input on screen or replace it with placeholders. - -User replies can be submitted via the @@Quickshell.Services.Polkit.AuthFlow.submit method. -A conversation can take multiple turns, for example if second factors are involved. - -If authentication fails, we automatically create a fresh session so the user can try again. -The @@Quickshell.Services.Polkit.AuthFlow.authenticationFailed signal is emitted in this case. - -If authentication is successful, you receive the @@Quickshell.Services.Polkit.AuthFlow.authenticationSucceeeded signal. At this point, the dialog can be closed. -If additional requests are queued, you will receive the @@Quickshell.Services.Polkit.PolkitAgent.authenticationRequestStarted signal again. - -#### Cancelled requests - -Requests may either be canceled by the user or the PolKit daemon. -In this case, we clean up any state and proceed to the next request, if any. - -If the request was cancelled by the daemon and not the user, you also receive the @@Quickshell.Services.Polkit.AuthFlow.authenticationRequestCancelled signal. diff --git a/src/services/polkit/qml.cpp b/src/services/polkit/qml.cpp deleted file mode 100644 index 9a08e5d..0000000 --- a/src/services/polkit/qml.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "qml.hpp" - -#include -#include -#include - -#include "../../core/logcat.hpp" -#include "agentimpl.hpp" - -namespace { -QS_LOGGING_CATEGORY(logPolkit, "quickshell.service.polkit", QtWarningMsg); -} - -namespace qs::service::polkit { -PolkitAgent::~PolkitAgent() { PolkitAgentImpl::onEndOfQmlAgent(this); }; - -void PolkitAgent::componentComplete() { - if (this->mPath.isEmpty()) this->mPath = "/org/quickshell/PolkitAgent"; - - auto* impl = PolkitAgentImpl::tryTakeoverOrCreate(this); - if (impl == nullptr) return; - - this->bFlow.setBinding([impl]() { return impl->activeFlow().value(); }); - this->bIsActive.setBinding([impl]() { return impl->activeFlow().value() != nullptr; }); - this->bIsRegistered.setBinding([impl]() { return impl->isRegistered().value(); }); -} - -void PolkitAgent::setPath(const QString& path) { - if (this->mPath.isEmpty()) { - this->mPath = path; - } else if (this->mPath != path) { - qCWarning(logPolkit) << "cannot change path after it has been set."; - } -} -} // namespace qs::service::polkit diff --git a/src/services/polkit/qml.hpp b/src/services/polkit/qml.hpp deleted file mode 100644 index 5343bcd..0000000 --- a/src/services/polkit/qml.hpp +++ /dev/null @@ -1,84 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include -#include - -#include "flow.hpp" - -// The reserved identifier is exactly the struct I mean. -using PolkitIdentity = struct _PolkitIdentity; // NOLINT(bugprone-reserved-identifier) -using QsPolkitAgent = struct _QsPolkitAgent; - -namespace qs::service::polkit { - -struct AuthRequest; -class Session; -class Identity; -class AuthFlow; - -//! Contains interface to instantiate a PolKit agent listener. -class PolkitAgent - : public QObject - , public QQmlParserStatus { - Q_OBJECT; - QML_ELEMENT; - Q_INTERFACES(QQmlParserStatus); - Q_DISABLE_COPY_MOVE(PolkitAgent); - - /// The D-Bus path that this agent listener will use. - /// - /// If not set, a default of /org/quickshell/Polkit will be used. - Q_PROPERTY(QString path READ path WRITE setPath); - - /// Indicates whether the agent registered successfully and is in use. - Q_PROPERTY(bool isRegistered READ default NOTIFY isRegisteredChanged BINDABLE isRegistered); - - /// Indicates an ongoing authentication request. - /// - /// If this is true, other properties such as @@message and @@iconName will - /// also be populated with relevant information. - Q_PROPERTY(bool isActive READ default NOTIFY isActiveChanged BINDABLE isActive); - - /// The current authentication state if an authentication request is active. - /// - /// Null when no authentication request is active. - Q_PROPERTY(AuthFlow* flow READ default NOTIFY flowChanged BINDABLE flow); - -public: - explicit PolkitAgent(QObject* parent = nullptr): QObject(parent) {}; - ~PolkitAgent() override; - - void classBegin() override {}; - void componentComplete() override; - - [[nodiscard]] QString path() const { return this->mPath; }; - void setPath(const QString& path); - - [[nodiscard]] QBindable flow() { return &this->bFlow; }; - [[nodiscard]] QBindable isActive() { return &this->bIsActive; }; - [[nodiscard]] QBindable isRegistered() { return &this->bIsRegistered; }; - -signals: - /// Emitted when an application makes a request that requires authentication. - /// - /// At this point, @@state will be populated with relevant information. - /// Note that signals for conversation outcome are emitted from the @@AuthFlow instance. - void authenticationRequestStarted(); - - void isRegisteredChanged(); - void isActiveChanged(); - void flowChanged(); - -private: - QString mPath = ""; - - Q_OBJECT_BINDABLE_PROPERTY(PolkitAgent, AuthFlow*, bFlow, &PolkitAgent::flowChanged); - Q_OBJECT_BINDABLE_PROPERTY(PolkitAgent, bool, bIsActive, &PolkitAgent::isActiveChanged); - Q_OBJECT_BINDABLE_PROPERTY(PolkitAgent, bool, bIsRegistered, &PolkitAgent::isRegisteredChanged); -}; -} // namespace qs::service::polkit diff --git a/src/services/polkit/session.cpp b/src/services/polkit/session.cpp deleted file mode 100644 index 71def68..0000000 --- a/src/services/polkit/session.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include "session.hpp" - -#include -#include -#include -#include - -#define POLKIT_AGENT_I_KNOW_API_IS_SUBJECT_TO_CHANGE -// This causes a problem with variables of the name. -#undef signals -#include -#define signals Q_SIGNALS - -namespace qs::service::polkit { - -namespace { -void completedCb(PolkitAgentSession* /*session*/, gboolean gainedAuthorization, gpointer userData) { - auto* self = static_cast(userData); - emit self->completed(gainedAuthorization); -} - -void requestCb( - PolkitAgentSession* /*session*/, - const char* message, - gboolean echo, - gpointer userData -) { - auto* self = static_cast(userData); - emit self->request(QString::fromUtf8(message), echo); -} - -void showErrorCb(PolkitAgentSession* /*session*/, const char* message, gpointer userData) { - auto* self = static_cast(userData); - emit self->showError(QString::fromUtf8(message)); -} - -void showInfoCb(PolkitAgentSession* /*session*/, const char* message, gpointer userData) { - auto* self = static_cast(userData); - emit self->showInfo(QString::fromUtf8(message)); -} -} // namespace - -Session::Session(PolkitIdentity* identity, const QString& cookie, QObject* parent) - : QObject(parent) { - this->session = polkit_agent_session_new(identity, cookie.toUtf8().constData()); - - g_signal_connect(G_OBJECT(this->session), "completed", G_CALLBACK(completedCb), this); - g_signal_connect(G_OBJECT(this->session), "request", G_CALLBACK(requestCb), this); - g_signal_connect(G_OBJECT(this->session), "show-error", G_CALLBACK(showErrorCb), this); - g_signal_connect(G_OBJECT(this->session), "show-info", G_CALLBACK(showInfoCb), this); -} - -Session::~Session() { - // Signals do not need to be disconnected explicitly. This happens during - // destruction of the gobject. Since we own the session object, we can be - // sure it is being destroyed after the unref. - g_object_unref(this->session); -} - -void Session::initiate() { polkit_agent_session_initiate(this->session); } - -void Session::cancel() { polkit_agent_session_cancel(this->session); } - -void Session::respond(const QString& response) { - polkit_agent_session_response(this->session, response.toUtf8().constData()); -} - -} // namespace qs::service::polkit diff --git a/src/services/polkit/session.hpp b/src/services/polkit/session.hpp deleted file mode 100644 index 29331b1..0000000 --- a/src/services/polkit/session.hpp +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include - -// _PolkitIdentity and _PolkitAgentSession are considered reserved identifiers, -// but I am specifically forward declaring those reserved names. - -// NOLINTBEGIN(bugprone-reserved-identifier) -using PolkitIdentity = struct _PolkitIdentity; -using PolkitAgentSession = struct _PolkitAgentSession; -// NOLINTEND(bugprone-reserved-identifier) - -namespace qs::service::polkit { -//! Represents an authentication session for a specific identity. -class Session: public QObject { - Q_OBJECT; - Q_DISABLE_COPY_MOVE(Session); - -public: - explicit Session(PolkitIdentity* identity, const QString& cookie, QObject* parent = nullptr); - ~Session() override; - - /// Call this after connecting to the relevant signals. - void initiate(); - /// Call this to abort a running authentication session. - void cancel(); - /// Provide a response to an input request. - void respond(const QString& response); - -Q_SIGNALS: - /// Emitted when the session wants to request input from the user. - /// - /// The message is a prompt to present to the user. - /// If echo is false, the user's response should not be displayed (e.g. for passwords). - void request(const QString& message, bool echo); - - /// Emitted when the authentication session completes. - /// - /// If success is true, authentication was successful. - /// Otherwise it failed (e.g. wrong password). - void completed(bool success); - - /// Emitted when an error message should be shown to the user. - void showError(const QString& message); - - /// Emitted when an informational message should be shown to the user. - void showInfo(const QString& message); - -private: - PolkitAgentSession* session = nullptr; -}; -} // namespace qs::service::polkit diff --git a/src/services/polkit/test/manual/agent.qml b/src/services/polkit/test/manual/agent.qml deleted file mode 100644 index 4588e4b..0000000 --- a/src/services/polkit/test/manual/agent.qml +++ /dev/null @@ -1,97 +0,0 @@ -import Quickshell -import Quickshell.Services.Polkit -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls - -Scope { - id: root - - FloatingWindow { - title: "Authentication Required" - - visible: polkitAgent.isActive - color: contentItem.palette.window - - ColumnLayout { - id: contentColumn - anchors.fill: parent - anchors.margins: 18 - spacing: 12 - - Item { Layout.fillHeight: true } - - Label { - Layout.fillWidth: true - text: polkitAgent.flow?.message || "" - wrapMode: Text.Wrap - font.bold: true - } - - Label { - Layout.fillWidth: true - text: polkitAgent.flow?.supplementaryMessage || "" - wrapMode: Text.Wrap - opacity: 0.8 - } - - Label { - Layout.fillWidth: true - text: polkitAgent.flow?.inputPrompt || "" - wrapMode: Text.Wrap - } - - Label { - text: "Authentication failed, try again" - color: "red" - visible: polkitAgent.flow?.failed - } - - TextField { - id: passwordInput - echoMode: polkitAgent.flow?.responseVisible - ? TextInput.Normal : TextInput.Password - selectByMouse: true - Layout.fillWidth: true - onAccepted: okButton.clicked() - } - - RowLayout { - spacing: 8 - Button { - id: okButton - text: "OK" - enabled: passwordInput.text.length > 0 || !!polkitAgent.flow?.isResponseRequired - onClicked: { - polkitAgent.flow.submit(passwordInput.text) - passwordInput.text = "" - passwordInput.forceActiveFocus() - } - } - Button { - text: "Cancel" - visible: polkitAgent.isActive - onClicked: { - polkitAgent.flow.cancelAuthenticationRequest() - passwordInput.text = "" - } - } - } - - Item { Layout.fillHeight: true } - } - - Connections { - target: polkitAgent.flow - function onIsResponseRequiredChanged() { - passwordInput.text = "" - if (polkitAgent.flow.isResponseRequired) - passwordInput.forceActiveFocus() - } - } - } - - PolkitAgent { - id: polkitAgent - } -} diff --git a/src/services/status_notifier/item.cpp b/src/services/status_notifier/item.cpp index 17404e1..650c812 100644 --- a/src/services/status_notifier/item.cpp +++ b/src/services/status_notifier/item.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include diff --git a/src/services/status_notifier/item.hpp b/src/services/status_notifier/item.hpp index 2eff95d..5ce5a7f 100644 --- a/src/services/status_notifier/item.hpp +++ b/src/services/status_notifier/item.hpp @@ -207,8 +207,6 @@ private: QS_BINDING_SUBSCRIBE_METHOD(StatusNotifierItem, bOverlayIconPixmaps, updatePixmapIndex, onValueChanged); QS_BINDING_SUBSCRIBE_METHOD(StatusNotifierItem, bAttentionIconPixmaps, updatePixmapIndex, onValueChanged); QS_BINDING_SUBSCRIBE_METHOD(StatusNotifierItem, bMenuPath, onMenuPathChanged, onValueChanged); - QS_BINDING_SUBSCRIBE_METHOD(StatusNotifierItem, bTooltip, tooltipTitleChanged, onValueChanged); - QS_BINDING_SUBSCRIBE_METHOD(StatusNotifierItem, bTooltip, tooltipDescriptionChanged, onValueChanged); Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, quint32, pixmapIndex); Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, QString, bIcon, &StatusNotifierItem::iconChanged); diff --git a/src/services/upower/device.cpp b/src/services/upower/device.cpp index 63382ad..2492b1f 100644 --- a/src/services/upower/device.cpp +++ b/src/services/upower/device.cpp @@ -73,7 +73,7 @@ UPowerDevice::UPowerDevice(QObject* parent): QObject(parent) { return this->bType == UPowerDeviceType::Battery && this->bPowerSupply; }); - this->bHealthSupported.setBinding([this]() { return this->bHealthPercentage.value() != 0; }); + this->bHealthSupported.setBinding([this]() { return this->bHealthPercentage != 0; }); } void UPowerDevice::init(const QString& path) { @@ -101,7 +101,7 @@ QString UPowerDevice::address() const { return this->device ? this->device->serv QString UPowerDevice::path() const { return this->device ? this->device->path() : QString(); } void UPowerDevice::onGetAllFinished() { - qCDebug(logUPowerDevice) << "UPowerDevice" << this->device->path() << "ready."; + qCDebug(logUPowerDevice) << "UPowerDevice" << device->path() << "ready."; this->bReady = true; } @@ -126,8 +126,8 @@ DBusDataTransform::fromWire(quint32 wire) { ); } -DBusResult -DBusDataTransform::fromWire(quint32 wire) { +DBusResult DBusDataTransform::fromWire(quint32 wire +) { if (wire >= UPowerDeviceType::Unknown && wire <= UPowerDeviceType::BluetoothGeneric) { return DBusResult(static_cast(wire)); } diff --git a/src/services/upower/powerprofiles.cpp b/src/services/upower/powerprofiles.cpp index f59b871..4c40798 100644 --- a/src/services/upower/powerprofiles.cpp +++ b/src/services/upower/powerprofiles.cpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include "../../core/logcat.hpp" #include "../../dbus/bus.hpp" @@ -66,8 +66,7 @@ PowerProfiles::PowerProfiles() { auto bus = QDBusConnection::systemBus(); if (!bus.isConnected()) { - qCWarning( - logPowerProfiles + qCWarning(logPowerProfiles ) << "Could not connect to DBus. PowerProfiles services will not work."; } @@ -80,8 +79,7 @@ PowerProfiles::PowerProfiles() { ); if (!this->service->isValid()) { - qCDebug( - logPowerProfiles + qCDebug(logPowerProfiles ) << "PowerProfilesDaemon is not currently running, attempting to start it."; dbus::tryLaunchService(this, bus, "org.freedesktop.UPower.PowerProfiles", [this](bool success) { @@ -105,15 +103,13 @@ void PowerProfiles::init() { void PowerProfiles::setProfile(PowerProfile::Enum profile) { if (!this->properties.isConnected()) { - qCCritical( - logPowerProfiles + qCCritical(logPowerProfiles ) << "Cannot set power profile: power-profiles-daemon not accessible or not running"; return; } if (profile == PowerProfile::Performance && !this->bHasPerformanceProfile) { - qCCritical( - logPowerProfiles + qCCritical(logPowerProfiles ) << "Cannot request performance profile as it is not present for this device."; return; } else if (profile < PowerProfile::PowerSaver || profile > PowerProfile::Performance) { @@ -139,9 +135,8 @@ PowerProfilesQml::PowerProfilesQml(QObject* parent): QObject(parent) { return instance->bHasPerformanceProfile.value(); }); - this->bDegradationReason.setBinding([instance]() { - return instance->bDegradationReason.value(); - }); + this->bDegradationReason.setBinding([instance]() { return instance->bDegradationReason.value(); } + ); this->bHolds.setBinding([instance]() { return instance->bHolds.value(); }); } @@ -169,7 +164,6 @@ QString DBusDataTransform::toWire(Data data) { case PowerProfile::PowerSaver: return QStringLiteral("power-saver"); case PowerProfile::Balanced: return QStringLiteral("balanced"); case PowerProfile::Performance: return QStringLiteral("performance"); - default: qFatal() << "Attempted to convert invalid power profile" << data << "to wire format."; } } diff --git a/src/ui/reload_popup.cpp b/src/ui/reload_popup.cpp index f000374..8e58dc9 100644 --- a/src/ui/reload_popup.cpp +++ b/src/ui/reload_popup.cpp @@ -25,7 +25,7 @@ ReloadPopup::ReloadPopup(QString instanceId, bool failed, QString errorString) this->popup = component.createWithInitialProperties({{"reloadInfo", QVariant::fromValue(this)}}); - if (!this->popup) { + if (!popup) { qCritical() << "Failed to open reload popup:" << component.errorString(); } diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt index cf84713..1d6543e 100644 --- a/src/wayland/CMakeLists.txt +++ b/src/wayland/CMakeLists.txt @@ -1,6 +1,6 @@ find_package(PkgConfig REQUIRED) find_package(WaylandScanner REQUIRED) -pkg_check_modules(wayland REQUIRED IMPORTED_TARGET wayland-client wayland-protocols>=1.41) +pkg_check_modules(wayland REQUIRED IMPORTED_TARGET wayland-client wayland-protocols) # wayland protocols @@ -12,13 +12,13 @@ if(NOT TARGET Qt6::qtwaylandscanner) message(FATAL_ERROR "qtwaylandscanner executable not found. Most likely there is an issue with your Qt installation.") endif() -pkg_get_variable(WAYLAND_PROTOCOLS wayland-protocols pkgdatadir) +execute_process( + COMMAND pkg-config --variable=pkgdatadir wayland-protocols + OUTPUT_VARIABLE WAYLAND_PROTOCOLS + OUTPUT_STRIP_TRAILING_WHITESPACE +) -if(WAYLAND_PROTOCOLS) - message(STATUS "Found wayland protocols at ${WAYLAND_PROTOCOLS}") -else() - message(FATAL_ERROR "Could not find wayland protocols") -endif() +message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}") qs_add_pchset(wayland-protocol DEPENDENCIES Qt::Core Qt::WaylandClient Qt::WaylandClientPrivate @@ -68,13 +68,11 @@ function (wl_proto target name dir) target_include_directories(${target} INTERFACE ${PROTO_BUILD_PATH}) target_link_libraries(${target} wl-proto-${name}-wl Qt6::WaylandClient Qt6::WaylandClientPrivate) qs_pch(${target} SET wayland-protocol) - target_compile_options(wl-proto-${name}-wl PRIVATE ${wayland_CFLAGS}) endfunction() # ----- qt_add_library(quickshell-wayland STATIC - wl_proxy_safe_deref.cpp platformmenu.cpp popupanchor.cpp xdgshell.cpp @@ -82,10 +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") - # required to make sure the constructor is linked add_library(quickshell-wayland-init OBJECT init.cpp) @@ -120,20 +114,6 @@ if (HYPRLAND) add_subdirectory(hyprland) endif() -add_subdirectory(background_effect) -list(APPEND WAYLAND_MODULES Quickshell.Wayland._BackgroundEffect) - -add_subdirectory(idle_inhibit) -list(APPEND WAYLAND_MODULES Quickshell.Wayland._IdleInhibitor) - -add_subdirectory(idle_notify) -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/background_effect/CMakeLists.txt b/src/wayland/background_effect/CMakeLists.txt deleted file mode 100644 index f45f94d..0000000 --- a/src/wayland/background_effect/CMakeLists.txt +++ /dev/null @@ -1,24 +0,0 @@ -qt_add_library(quickshell-wayland-background-effect STATIC - manager.cpp - surface.cpp - qml.cpp -) - -qt_add_qml_module(quickshell-wayland-background-effect - URI Quickshell.Wayland._BackgroundEffect - VERSION 0.1 - DEPENDENCIES QtQml -) - -install_qml_module(quickshell-wayland-background-effect) - -wl_proto(wlp-background-effect ext-background-effect-v1 "${WAYLAND_PROTOCOLS}/staging/ext-background-effect") - -target_link_libraries(quickshell-wayland-background-effect PRIVATE - Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client - wlp-background-effect -) - -qs_module_pch(quickshell-wayland-background-effect) - -target_link_libraries(quickshell PRIVATE quickshell-wayland-background-effectplugin) diff --git a/src/wayland/background_effect/manager.cpp b/src/wayland/background_effect/manager.cpp deleted file mode 100644 index 4cb06f1..0000000 --- a/src/wayland/background_effect/manager.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "manager.hpp" -#include - -#include -#include -#include -#include - -#include "surface.hpp" - -namespace qs::wayland::background_effect::impl { - -BackgroundEffectManager::BackgroundEffectManager(): QWaylandClientExtensionTemplate(1) { - this->initialize(); -} - -BackgroundEffectSurface* -BackgroundEffectManager::createEffectSurface(QtWaylandClient::QWaylandWindow* window) { - return new BackgroundEffectSurface(this->get_background_effect(window->surface())); -} - -bool BackgroundEffectManager::blurAvailable() const { - return this->isActive() && this->mBlurAvailable; -} - -void BackgroundEffectManager::ext_background_effect_manager_v1_capabilities(uint32_t flags) { - auto available = static_cast(flags & capability_blur); - if (available == this->mBlurAvailable) return; - this->mBlurAvailable = available; - emit this->blurAvailableChanged(); -} - -BackgroundEffectManager* BackgroundEffectManager::instance() { - static auto* instance = new BackgroundEffectManager(); // NOLINT - return instance->isInitialized() ? instance : nullptr; -} - -} // namespace qs::wayland::background_effect::impl diff --git a/src/wayland/background_effect/manager.hpp b/src/wayland/background_effect/manager.hpp deleted file mode 100644 index 6c2e981..0000000 --- a/src/wayland/background_effect/manager.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "surface.hpp" - -namespace qs::wayland::background_effect::impl { - -class BackgroundEffectManager - : public QWaylandClientExtensionTemplate - , public QtWayland::ext_background_effect_manager_v1 { - Q_OBJECT; - -public: - explicit BackgroundEffectManager(); - - BackgroundEffectSurface* createEffectSurface(QtWaylandClient::QWaylandWindow* window); - - [[nodiscard]] bool blurAvailable() const; - - static BackgroundEffectManager* instance(); - -signals: - void blurAvailableChanged(); - -protected: - void ext_background_effect_manager_v1_capabilities(uint32_t flags) override; - -private: - bool mBlurAvailable = false; -}; - -} // namespace qs::wayland::background_effect::impl diff --git a/src/wayland/background_effect/qml.cpp b/src/wayland/background_effect/qml.cpp deleted file mode 100644 index b54a847..0000000 --- a/src/wayland/background_effect/qml.cpp +++ /dev/null @@ -1,246 +0,0 @@ -#include "qml.hpp" -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../core/region.hpp" -#include "../../window/proxywindow.hpp" -#include "../../window/windowinterface.hpp" -#include "manager.hpp" -#include "surface.hpp" - -using QtWaylandClient::QWaylandWindow; - -namespace qs::wayland::background_effect { - -BackgroundEffect* BackgroundEffect::qmlAttachedProperties(QObject* object) { - auto* proxyWindow = qobject_cast(object); - - if (!proxyWindow) { - if (auto* iface = qobject_cast(object)) { - proxyWindow = iface->proxyWindow(); - } - } - - if (!proxyWindow) return nullptr; - return new BackgroundEffect(proxyWindow); -} - -BackgroundEffect::BackgroundEffect(ProxyWindowBase* window): QObject(nullptr), proxyWindow(window) { - QObject::connect( - window, - &ProxyWindowBase::windowConnected, - this, - &BackgroundEffect::onWindowConnected - ); - - QObject::connect(window, &ProxyWindowBase::polished, this, &BackgroundEffect::onWindowPolished); - - QObject::connect( - window, - &ProxyWindowBase::devicePixelRatioChanged, - this, - &BackgroundEffect::updateBlurRegion - ); - - QObject::connect(window, &QObject::destroyed, this, &BackgroundEffect::onProxyWindowDestroyed); - - if (window->backingWindow()) { - this->onWindowConnected(); - } -} - -PendingRegion* BackgroundEffect::blurRegion() const { return this->mBlurRegion; } - -void BackgroundEffect::setBlurRegion(PendingRegion* region) { - if (region == this->mBlurRegion) return; - - if (this->mBlurRegion) { - QObject::disconnect(this->mBlurRegion, nullptr, this, nullptr); - } - - this->mBlurRegion = region; - - if (region) { - QObject::connect(region, &QObject::destroyed, this, &BackgroundEffect::onBlurRegionDestroyed); - QObject::connect(region, &PendingRegion::changed, this, &BackgroundEffect::updateBlurRegion); - } - - this->updateBlurRegion(); - emit this->blurRegionChanged(); -} - -void BackgroundEffect::onBlurRegionDestroyed() { - this->mBlurRegion = nullptr; - this->updateBlurRegion(); - emit this->blurRegionChanged(); -} - -void BackgroundEffect::updateBlurRegion() { - if (!this->surface || !this->proxyWindow) return; - - this->pendingBlurRegion = true; - this->proxyWindow->schedulePolish(); -} - -void BackgroundEffect::onWindowPolished() { - if (!this->surface || !this->pendingBlurRegion) return; - if (!this->mWaylandWindow || !this->mWaylandWindow->surface()) { - this->pendingBlurRegion = false; - return; - } - - QRegion region; - if (this->mBlurRegion) { - region = - this->mBlurRegion->applyTo(QRect(0, 0, this->mWindow->width(), this->mWindow->height())); - - auto scale = QHighDpiScaling::factor(this->mWindow); - if (!qFuzzyCompare(scale, 1.0)) { - region = QHighDpi::scale(region, scale); - } - - auto margins = this->mWaylandWindow->clientSideMargins(); - region.translate(margins.left(), margins.top()); - } - - this->surface->setBlurRegion(region); - this->pendingBlurRegion = false; -} - -bool BackgroundEffect::eventFilter(QObject* object, QEvent* event) { - if (event->type() == QEvent::PlatformSurface) { - auto* surfaceEvent = dynamic_cast(event); - if (surfaceEvent->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) { - this->surface = nullptr; - this->pendingBlurRegion = false; - } - } - - return this->QObject::eventFilter(object, event); -} - -void BackgroundEffect::onWindowConnected() { - this->mWindow = this->proxyWindow->backingWindow(); - this->mWindow->installEventFilter(this); - - QObject::connect( - this->mWindow, - &QWindow::visibleChanged, - this, - &BackgroundEffect::onWindowVisibleChanged - ); - - this->onWindowVisibleChanged(); -} - -void BackgroundEffect::onWindowVisibleChanged() { - if (this->mWindow->isVisible()) { - if (!this->mWindow->handle()) { - this->mWindow->create(); - } - } - - auto* window = dynamic_cast(this->mWindow->handle()); - if (window == this->mWaylandWindow) return; - - if (this->mWaylandWindow) { - QObject::disconnect(this->mWaylandWindow, nullptr, this, nullptr); - } - - this->mWaylandWindow = window; - if (!window) return; - - QObject::connect( - this->mWaylandWindow, - &QObject::destroyed, - this, - &BackgroundEffect::onWaylandWindowDestroyed - ); - - QObject::connect( - this->mWaylandWindow, - &QWaylandWindow::surfaceCreated, - this, - &BackgroundEffect::onWaylandSurfaceCreated - ); - - QObject::connect( - this->mWaylandWindow, - &QWaylandWindow::surfaceDestroyed, - this, - &BackgroundEffect::onWaylandSurfaceDestroyed - ); - - if (this->mWaylandWindow->surface()) { - this->onWaylandSurfaceCreated(); - } -} - -void BackgroundEffect::onWaylandWindowDestroyed() { this->mWaylandWindow = nullptr; } - -void BackgroundEffect::onWaylandSurfaceCreated() { - auto* manager = impl::BackgroundEffectManager::instance(); - - if (!manager) { - qWarning() << "Cannot enable background effect as ext-background-effect-v1 is not supported " - "by the current compositor."; - return; - } - - // Steal protocol surface from previous BackgroundEffect to avoid duplicate-attachment on reload. - auto v = this->mWaylandWindow->property("qs_background_effect"); - if (v.canConvert()) { - auto* prev = v.value(); - if (prev != this && prev->surface) { - this->surface.swap(prev->surface); - } - } - - if (!this->surface) { - this->surface = std::unique_ptr( - manager->createEffectSurface(this->mWaylandWindow) - ); - } - - this->mWaylandWindow->setProperty("qs_background_effect", QVariant::fromValue(this)); - - this->pendingBlurRegion = this->mBlurRegion != nullptr; - if (this->pendingBlurRegion) { - this->proxyWindow->schedulePolish(); - } -} - -void BackgroundEffect::onWaylandSurfaceDestroyed() { - this->surface = nullptr; - this->pendingBlurRegion = false; - - if (!this->proxyWindow) { - this->deleteLater(); - } -} - -void BackgroundEffect::onProxyWindowDestroyed() { - // Don't delete the BackgroundEffect, and therefore the impl::BackgroundEffectSurface - // until the wl_surface is destroyed. Deleting it when the proxy window is deleted would - // cause a frame without blur between the destruction of the ext_background_effect_surface_v1 - // and wl_surface objects. - - this->proxyWindow = nullptr; - - if (this->surface == nullptr) { - this->deleteLater(); - } -} - -} // namespace qs::wayland::background_effect diff --git a/src/wayland/background_effect/qml.hpp b/src/wayland/background_effect/qml.hpp deleted file mode 100644 index dd93aec..0000000 --- a/src/wayland/background_effect/qml.hpp +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include -#include -#include - -#include "../../core/region.hpp" -#include "../../window/proxywindow.hpp" -#include "surface.hpp" - -namespace qs::wayland::background_effect { - -///! Background blur effect for Wayland surfaces. -/// Applies background blur behind a @@Quickshell.QsWindow or subclass, -/// as an attached object, using the [ext-background-effect-v1] Wayland protocol. -/// -/// > [!NOTE] Using a background effect requires the compositor support the -/// > [ext-background-effect-v1] protocol. -/// -/// [ext-background-effect-v1]: https://wayland.app/protocols/ext-background-effect-v1 -/// -/// #### Example -/// ```qml -/// @@Quickshell.PanelWindow { -/// id: root -/// color: "#80000000" -/// -/// BackgroundEffect.blurRegion: Region { item: root.contentItem } -/// } -/// ``` -class BackgroundEffect: public QObject { - Q_OBJECT; - // clang-format off - /// Region to blur behind the surface. Set to null to remove blur. - Q_PROPERTY(PendingRegion* blurRegion READ blurRegion WRITE setBlurRegion NOTIFY blurRegionChanged); - // clang-format on - QML_ELEMENT; - QML_UNCREATABLE("BackgroundEffect can only be used as an attached object."); - QML_ATTACHED(BackgroundEffect); - -public: - explicit BackgroundEffect(ProxyWindowBase* window); - - [[nodiscard]] PendingRegion* blurRegion() const; - void setBlurRegion(PendingRegion* region); - - static BackgroundEffect* qmlAttachedProperties(QObject* object); - - bool eventFilter(QObject* object, QEvent* event) override; - -signals: - void blurRegionChanged(); - -private slots: - void onWindowConnected(); - void onWindowVisibleChanged(); - void onWaylandWindowDestroyed(); - void onWaylandSurfaceCreated(); - void onWaylandSurfaceDestroyed(); - void onProxyWindowDestroyed(); - void onBlurRegionDestroyed(); - void onWindowPolished(); - void updateBlurRegion(); - -private: - ProxyWindowBase* proxyWindow = nullptr; - QWindow* mWindow = nullptr; - QtWaylandClient::QWaylandWindow* mWaylandWindow = nullptr; - - bool pendingBlurRegion = false; - PendingRegion* mBlurRegion = nullptr; - std::unique_ptr surface; -}; - -} // namespace qs::wayland::background_effect diff --git a/src/wayland/background_effect/surface.cpp b/src/wayland/background_effect/surface.cpp deleted file mode 100644 index 648361d..0000000 --- a/src/wayland/background_effect/surface.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "surface.hpp" - -#include -#include -#include -#include -#include - -namespace qs::wayland::background_effect::impl { - -BackgroundEffectSurface::BackgroundEffectSurface( - ::ext_background_effect_surface_v1* surface // NOLINT(misc-include-cleaner) -) - : QtWayland::ext_background_effect_surface_v1(surface) {} - -BackgroundEffectSurface::~BackgroundEffectSurface() { - if (!this->isInitialized()) return; - this->destroy(); -} - -void BackgroundEffectSurface::setBlurRegion(const QRegion& region) { - if (!this->isInitialized()) return; - - if (region.isEmpty()) { - this->set_blur_region(nullptr); - return; - } - - static const auto* waylandIntegration = QtWaylandClient::QWaylandIntegration::instance(); - auto* display = waylandIntegration->display(); - - auto* wlRegion = display->createRegion(region); - this->set_blur_region(wlRegion); - wl_region_destroy(wlRegion); // NOLINT(misc-include-cleaner) -} - -} // namespace qs::wayland::background_effect::impl diff --git a/src/wayland/background_effect/surface.hpp b/src/wayland/background_effect/surface.hpp deleted file mode 100644 index 65b0bc8..0000000 --- a/src/wayland/background_effect/surface.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace qs::wayland::background_effect::impl { - -class BackgroundEffectSurface: public QtWayland::ext_background_effect_surface_v1 { -public: - explicit BackgroundEffectSurface(::ext_background_effect_surface_v1* surface); - ~BackgroundEffectSurface() override; - Q_DISABLE_COPY_MOVE(BackgroundEffectSurface); - - void setBlurRegion(const QRegion& region); -}; - -} // namespace qs::wayland::background_effect::impl diff --git a/src/wayland/background_effect/test/manual/background_effect.qml b/src/wayland/background_effect/test/manual/background_effect.qml deleted file mode 100644 index 679cb01..0000000 --- a/src/wayland/background_effect/test/manual/background_effect.qml +++ /dev/null @@ -1,62 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import Quickshell -import Quickshell.Wayland - -FloatingWindow { - id: root - color: "transparent" - contentItem.palette.windowText: "white" - - ColumnLayout { - anchors.centerIn: parent - - CheckBox { - id: enableBox - checked: true - text: "Enable Blur" - } - - Button { - text: "Hide->Show" - onClicked: { - root.visible = false - showTimer.start() - } - } - - Timer { - id: showTimer - interval: 200 - onTriggered: root.visible = true - } - - Slider { - id: radiusSlider - from: 0 - to: 1000 - value: 100 - } - - component EdgeSlider: Slider { - from: -1 - to: 1000 - value: -1 - } - - EdgeSlider { id: topLeftSlider } - EdgeSlider { id: topRightSlider } - EdgeSlider { id: bottomLeftSlider } - EdgeSlider { id: bottomRightSlider } - } - - BackgroundEffect.blurRegion: Region { - item: enableBox.checked ? root.contentItem : null - radius: radiusSlider.value == -1 ? undefined : radiusSlider.value - topLeftRadius: topLeftSlider.value == -1 ? undefined : topLeftSlider.value - topRightRadius: topRightSlider.value == -1 ? undefined : topRightSlider.value - bottomLeftRadius: bottomLeftSlider.value == -1 ? undefined : bottomLeftSlider.value - bottomRightRadius: bottomRightSlider.value == -1 ? undefined : bottomRightSlider.value - } -} diff --git a/src/wayland/buffer/CMakeLists.txt b/src/wayland/buffer/CMakeLists.txt index 15818fc..f80c53a 100644 --- a/src/wayland/buffer/CMakeLists.txt +++ b/src/wayland/buffer/CMakeLists.txt @@ -1,8 +1,6 @@ find_package(PkgConfig REQUIRED) pkg_check_modules(dmabuf-deps REQUIRED IMPORTED_TARGET libdrm gbm egl) -find_package(VulkanHeaders REQUIRED) - qt_add_library(quickshell-wayland-buffer STATIC manager.cpp dmabuf.cpp @@ -12,10 +10,9 @@ qt_add_library(quickshell-wayland-buffer STATIC wl_proto(wlp-linux-dmabuf linux-dmabuf-v1 "${WAYLAND_PROTOCOLS}/stable/linux-dmabuf") target_link_libraries(quickshell-wayland-buffer PRIVATE - Qt::Quick Qt::QuickPrivate Qt::GuiPrivate Qt::WaylandClient Qt::WaylandClientPrivate wayland-client + Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client PkgConfig::dmabuf-deps wlp-linux-dmabuf - Vulkan::Headers ) qs_pch(quickshell-wayland-buffer SET large) diff --git a/src/wayland/buffer/dmabuf.cpp b/src/wayland/buffer/dmabuf.cpp index 47462fb..4593389 100644 --- a/src/wayland/buffer/dmabuf.cpp +++ b/src/wayland/buffer/dmabuf.cpp @@ -1,6 +1,7 @@ #include "dmabuf.hpp" #include #include +#include #include #include #include @@ -10,11 +11,10 @@ #include #include #include +#include #include #include #include -#include -#include #include #include #include @@ -25,17 +25,12 @@ #include #include #include -#include #include -#include -#include -#include #include #include #include #include #include -#include #include #include #include @@ -54,36 +49,6 @@ QS_LOGGING_CATEGORY(logDmabuf, "quickshell.wayland.buffer.dmabuf", QtWarningMsg) LinuxDmabufManager* MANAGER = nullptr; // NOLINT -VkFormat drmFormatToVkFormat(uint32_t drmFormat) { - // NOLINTBEGIN(bugprone-branch-clone): XRGB/ARGB intentionally map to the same VK format - switch (drmFormat) { - case DRM_FORMAT_ARGB8888: return VK_FORMAT_B8G8R8A8_UNORM; - case DRM_FORMAT_XRGB8888: return VK_FORMAT_B8G8R8A8_UNORM; - case DRM_FORMAT_ABGR8888: return VK_FORMAT_R8G8B8A8_UNORM; - case DRM_FORMAT_XBGR8888: return VK_FORMAT_R8G8B8A8_UNORM; - case DRM_FORMAT_ARGB2101010: return VK_FORMAT_A2R10G10B10_UNORM_PACK32; - case DRM_FORMAT_XRGB2101010: return VK_FORMAT_A2R10G10B10_UNORM_PACK32; - case DRM_FORMAT_ABGR2101010: return VK_FORMAT_A2B10G10R10_UNORM_PACK32; - case DRM_FORMAT_XBGR2101010: return VK_FORMAT_A2B10G10R10_UNORM_PACK32; - case DRM_FORMAT_ABGR16161616F: return VK_FORMAT_R16G16B16A16_SFLOAT; - case DRM_FORMAT_RGB565: return VK_FORMAT_R5G6B5_UNORM_PACK16; - case DRM_FORMAT_BGR565: return VK_FORMAT_B5G6R5_UNORM_PACK16; - default: return VK_FORMAT_UNDEFINED; - } - // NOLINTEND(bugprone-branch-clone) -} - -bool drmFormatHasAlpha(uint32_t drmFormat) { - switch (drmFormat) { - case DRM_FORMAT_ARGB8888: - case DRM_FORMAT_ABGR8888: - case DRM_FORMAT_ARGB2101010: - case DRM_FORMAT_ABGR2101010: - case DRM_FORMAT_ABGR16161616F: return true; - default: return false; - } -} - } // namespace QDebug& operator<<(QDebug& debug, const FourCCStr& fourcc) { @@ -112,32 +77,30 @@ QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer) { } GbmDeviceHandle::~GbmDeviceHandle() { - if (this->device) { + if (device) { MANAGER->unrefGbmDevice(this->device); } } -// Prefer ARGB over XRGB: XRGB has undefined alpha bytes which cause -// transparency artifacts on Vulkan (notably Intel GPUs) since Vulkan -// doesn't auto-fill alpha=1.0 for X formats like EGL does. +// This will definitely backfire later void LinuxDmabufFormatSelection::ensureSorted() { if (this->sorted) return; auto beginIter = this->formats.begin(); - auto argbIter = std::ranges::find_if(this->formats, [](const auto& format) { - return format.first == DRM_FORMAT_ARGB8888; - }); - - if (argbIter != this->formats.end()) { - std::swap(*beginIter, *argbIter); - ++beginIter; - } - auto xrgbIter = std::ranges::find_if(this->formats, [](const auto& format) { return format.first == DRM_FORMAT_XRGB8888; }); - if (xrgbIter != this->formats.end()) std::swap(*beginIter, *xrgbIter); + if (xrgbIter != this->formats.end()) { + std::swap(*beginIter, *xrgbIter); + ++beginIter; + } + + auto argbIter = std::ranges::find_if(this->formats, [](const auto& format) { + return format.first == DRM_FORMAT_ARGB8888; + }); + + if (argbIter != this->formats.end()) std::swap(*beginIter, *argbIter); this->sorted = true; } @@ -451,8 +414,7 @@ WlBuffer* LinuxDmabufManager::createDmabuf( if (modifiers.modifiers.isEmpty()) { if (!modifiers.implicit) { - qCritical( - logDmabuf + qCritical(logDmabuf ) << "Failed to create gbm_bo: format supports no implicit OR explicit modifiers."; return nullptr; } @@ -560,7 +522,7 @@ WlDmaBuffer::~WlDmaBuffer() { bool WlDmaBuffer::isCompatible(const WlBufferRequest& request) const { if (request.width != this->width || request.height != this->height) return false; - auto matchingFormat = std::ranges::find_if(request.dmabuf.formats, [this](const auto& format) { + auto matchingFormat = std::ranges::find_if(request.dmabuf.formats, [&](const auto& format) { return format.format == this->format && (format.modifiers.isEmpty() || std::ranges::find(format.modifiers, this->modifier) != format.modifiers.end()); @@ -570,15 +532,6 @@ bool WlDmaBuffer::isCompatible(const WlBufferRequest& request) const { } WlBufferQSGTexture* WlDmaBuffer::createQsgTexture(QQuickWindow* window) const { - auto* ri = window->rendererInterface(); - if (ri && ri->graphicsApi() == QSGRendererInterface::Vulkan) { - return this->createQsgTextureVulkan(window); - } - - return this->createQsgTextureGl(window); -} - -WlBufferQSGTexture* WlDmaBuffer::createQsgTextureGl(QQuickWindow* window) const { static auto* glEGLImageTargetTexture2DOES = []() { auto* fn = reinterpret_cast( eglGetProcAddress("glEGLImageTargetTexture2DOES") @@ -709,313 +662,6 @@ WlBufferQSGTexture* WlDmaBuffer::createQsgTextureGl(QQuickWindow* window) const return tex; } -WlBufferQSGTexture* WlDmaBuffer::createQsgTextureVulkan(QQuickWindow* window) const { - auto* ri = window->rendererInterface(); - auto* vkInst = window->vulkanInstance(); - - if (!vkInst) { - qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: no QVulkanInstance."; - return nullptr; - } - - auto* vkDevicePtr = - static_cast(ri->getResource(window, QSGRendererInterface::DeviceResource)); - auto* vkPhysDevicePtr = static_cast( - ri->getResource(window, QSGRendererInterface::PhysicalDeviceResource) - ); - - if (!vkDevicePtr || !vkPhysDevicePtr) { - qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: could not get Vulkan device."; - return nullptr; - } - - VkDevice device = *vkDevicePtr; - VkPhysicalDevice physDevice = *vkPhysDevicePtr; - - auto* devFuncs = vkInst->deviceFunctions(device); - auto* instFuncs = vkInst->functions(); - - if (!devFuncs || !instFuncs) { - qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: " - "could not get Vulkan functions."; - return nullptr; - } - - auto getMemoryFdPropertiesKHR = reinterpret_cast( - instFuncs->vkGetDeviceProcAddr(device, "vkGetMemoryFdPropertiesKHR") - ); - - if (!getMemoryFdPropertiesKHR) { - qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: " - "vkGetMemoryFdPropertiesKHR not available. " - "Missing VK_KHR_external_memory_fd extension."; - return nullptr; - } - - const VkFormat vkFormat = drmFormatToVkFormat(this->format); - if (vkFormat == VK_FORMAT_UNDEFINED) { - qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: unsupported DRM format" - << FourCCStr(this->format); - return nullptr; - } - - if (this->planeCount > 4) { - qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: too many planes" - << this->planeCount; - return nullptr; - } - - std::array planeLayouts = {}; - for (int i = 0; i < this->planeCount; ++i) { - planeLayouts[i].offset = this->planes[i].offset; // NOLINT - planeLayouts[i].rowPitch = this->planes[i].stride; // NOLINT - planeLayouts[i].size = 0; - planeLayouts[i].arrayPitch = 0; - planeLayouts[i].depthPitch = 0; - } - - const bool useModifier = this->modifier != DRM_FORMAT_MOD_INVALID; - - VkExternalMemoryImageCreateInfo externalInfo = {}; - externalInfo.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO; - externalInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT; - - VkImageDrmFormatModifierExplicitCreateInfoEXT modifierInfo = {}; - modifierInfo.sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT; - modifierInfo.drmFormatModifier = this->modifier; - modifierInfo.drmFormatModifierPlaneCount = static_cast(this->planeCount); - modifierInfo.pPlaneLayouts = planeLayouts.data(); - - if (useModifier) { - externalInfo.pNext = &modifierInfo; - } - - VkImageCreateInfo imageInfo = {}; - imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - imageInfo.pNext = &externalInfo; - imageInfo.imageType = VK_IMAGE_TYPE_2D; - imageInfo.format = vkFormat; - imageInfo.extent = {.width = this->width, .height = this->height, .depth = 1}; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; - imageInfo.tiling = useModifier ? VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT : VK_IMAGE_TILING_LINEAR; - imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT; - imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - - VkImage image = VK_NULL_HANDLE; - VkResult result = devFuncs->vkCreateImage(device, &imageInfo, nullptr, &image); - if (result != VK_SUCCESS) { - qCWarning(logDmabuf) << "Failed to create VkImage for DMA-BUF import, result:" << result; - return nullptr; - } - - VkDeviceMemory memory = VK_NULL_HANDLE; - - // dup() is required because vkAllocateMemory with VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT - // takes ownership of the fd on succcess. Without dup, WlDmaBuffer would double-close. - const int dupFd = - dup(this->planes[0].fd); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) - if (dupFd < 0) { - qCWarning(logDmabuf) << "Failed to dup() fd for DMA-BUF import"; - goto cleanup_fail; // NOLINT - } - - { - VkMemoryRequirements memReqs = {}; - devFuncs->vkGetImageMemoryRequirements(device, image, &memReqs); - - VkMemoryFdPropertiesKHR fdProps = {}; - fdProps.sType = VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR; - - result = getMemoryFdPropertiesKHR( - device, - VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, - dupFd, - &fdProps - ); - - if (result != VK_SUCCESS) { - close(dupFd); - qCWarning(logDmabuf) << "vkGetMemoryFdPropertiesKHR failed, result:" << result; - goto cleanup_fail; // NOLINT - } - - const uint32_t memTypeBits = memReqs.memoryTypeBits & fdProps.memoryTypeBits; - - VkPhysicalDeviceMemoryProperties memProps = {}; - instFuncs->vkGetPhysicalDeviceMemoryProperties(physDevice, &memProps); - - uint32_t memTypeIndex = UINT32_MAX; - for (uint32_t j = 0; j < memProps.memoryTypeCount; ++j) { - if (memTypeBits & (1u << j)) { - memTypeIndex = j; - break; - } - } - - if (memTypeIndex == UINT32_MAX) { - close(dupFd); - qCWarning(logDmabuf) << "No compatible memory type for DMA-BUF import"; - goto cleanup_fail; // NOLINT - } - - VkImportMemoryFdInfoKHR importInfo = {}; - importInfo.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR; - importInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT; - importInfo.fd = dupFd; - - VkMemoryDedicatedAllocateInfo dedicatedInfo = {}; - dedicatedInfo.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO; - dedicatedInfo.image = image; - dedicatedInfo.pNext = &importInfo; - - VkMemoryAllocateInfo allocInfo = {}; - allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; - allocInfo.pNext = &dedicatedInfo; - allocInfo.allocationSize = memReqs.size; - allocInfo.memoryTypeIndex = memTypeIndex; - - result = devFuncs->vkAllocateMemory(device, &allocInfo, nullptr, &memory); - if (result != VK_SUCCESS) { - close(dupFd); - qCWarning(logDmabuf) << "vkAllocateMemory failed, result:" << result; - goto cleanup_fail; // NOLINT - } - - result = devFuncs->vkBindImageMemory(device, image, memory, 0); - if (result != VK_SUCCESS) { - qCWarning(logDmabuf) << "vkBindImageMemory failed, result:" << result; - goto cleanup_fail; // NOLINT - } - } - - { - // acquire the DMA-BUF from the foreign (compositor) queue and transition - // to shader-read layout. oldLayout must be GENERAL (not UNDEFINED) to - // preserve the DMA-BUF contents written by the external producer. Hopefully. - window->beginExternalCommands(); - - auto* cmdBufPtr = static_cast( - ri->getResource(window, QSGRendererInterface::CommandListResource) - ); - - if (cmdBufPtr && *cmdBufPtr) { - VkCommandBuffer cmdBuf = *cmdBufPtr; - - // find the graphics queue family index for the ownrship transfer. - uint32_t graphicsQueueFamily = 0; - uint32_t queueFamilyCount = 0; - instFuncs->vkGetPhysicalDeviceQueueFamilyProperties(physDevice, &queueFamilyCount, nullptr); - std::vector queueFamilies(queueFamilyCount); - instFuncs->vkGetPhysicalDeviceQueueFamilyProperties( - physDevice, - &queueFamilyCount, - queueFamilies.data() - ); - for (uint32_t i = 0; i < queueFamilyCount; ++i) { - if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { - graphicsQueueFamily = i; - break; - } - } - - VkImageMemoryBarrier barrier = {}; - barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; - barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT; - barrier.dstQueueFamilyIndex = graphicsQueueFamily; - barrier.image = image; - barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - barrier.subresourceRange.baseMipLevel = 0; - barrier.subresourceRange.levelCount = 1; - barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.layerCount = 1; - barrier.srcAccessMask = 0; - barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - - devFuncs->vkCmdPipelineBarrier( - cmdBuf, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, - 0, - 0, - nullptr, - 0, - nullptr, - 1, - &barrier - ); - } - - window->endExternalCommands(); - - auto* qsgTexture = QQuickWindowPrivate::get(window)->createTextureFromNativeTexture( - reinterpret_cast(image), - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - static_cast(vkFormat), - QSize(static_cast(this->width), static_cast(this->height)), - {} - ); - - // For opaque DRM formats (XRGB, XBGR, etc.), the alpha bytes are underfined. - // EGL silently forces alpha=1.0 for these, but Vulkan doesn't. Replace Qt's - // default identity-swizzle VkImageView with one that maps alpha to ONE. - if (!drmFormatHasAlpha(this->format)) { - auto* vkTexture = static_cast(qsgTexture->rhiTexture()); // NOLINT - - devFuncs->vkDestroyImageView(device, vkTexture->imageView, nullptr); - - VkImageViewCreateInfo viewInfo = {}; - viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - viewInfo.image = image; - viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - viewInfo.format = vkFormat; - viewInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; - viewInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; - viewInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; - viewInfo.components.a = VK_COMPONENT_SWIZZLE_ONE; - viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - viewInfo.subresourceRange.levelCount = 1; - viewInfo.subresourceRange.layerCount = 1; - - result = devFuncs->vkCreateImageView(device, &viewInfo, nullptr, &vkTexture->imageView); - if (result != VK_SUCCESS) { - qCWarning(logDmabuf) << "Failed to create alpha-swizzled VkImageView, result:" << result; - } - } - - auto* tex = new WlDmaBufferVulkanQSGTexture(devFuncs, device, image, memory, qsgTexture); - qCDebug(logDmabuf) << "Created WlDmaBufferVulkanQSGTexture" << tex << "from" << this; - return tex; - } - -cleanup_fail: - if (image != VK_NULL_HANDLE) { - devFuncs->vkDestroyImage(device, image, nullptr); - } - if (memory != VK_NULL_HANDLE) { - devFuncs->vkFreeMemory(device, memory, nullptr); - } - return nullptr; -} - -WlDmaBufferVulkanQSGTexture::~WlDmaBufferVulkanQSGTexture() { - delete this->qsgTexture; - - if (this->image != VK_NULL_HANDLE) { - this->devFuncs->vkDestroyImage(this->device, this->image, nullptr); - } - - if (this->memory != VK_NULL_HANDLE) { - this->devFuncs->vkFreeMemory(this->device, this->memory, nullptr); - } - - qCDebug(logDmabuf) << "WlDmaBufferVulkanQSGTexture" << this << "destroyed."; -} - WlDmaBufferQSGTexture::~WlDmaBufferQSGTexture() { auto* context = QOpenGLContext::currentContext(); auto* display = context->nativeInterface()->display(); diff --git a/src/wayland/buffer/dmabuf.hpp b/src/wayland/buffer/dmabuf.hpp index ffe5d02..a05e82a 100644 --- a/src/wayland/buffer/dmabuf.hpp +++ b/src/wayland/buffer/dmabuf.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -13,11 +12,9 @@ #include #include #include -#include #include #include #include -#include #include #include #include @@ -43,7 +40,7 @@ public: '\0'} ) { for (auto i = 3; i != 0; i--) { - if (this->chars[i] == ' ') this->chars[i] = '\0'; + if (chars[i] == ' ') chars[i] = '\0'; else break; } } @@ -117,36 +114,6 @@ private: friend class WlDmaBuffer; }; -class WlDmaBufferVulkanQSGTexture: public WlBufferQSGTexture { -public: - ~WlDmaBufferVulkanQSGTexture() override; - Q_DISABLE_COPY_MOVE(WlDmaBufferVulkanQSGTexture); - - [[nodiscard]] QSGTexture* texture() const override { return this->qsgTexture; } - -private: - WlDmaBufferVulkanQSGTexture( - QVulkanDeviceFunctions* devFuncs, - VkDevice device, - VkImage image, - VkDeviceMemory memory, - QSGTexture* qsgTexture - ) - : devFuncs(devFuncs) - , device(device) - , image(image) - , memory(memory) - , qsgTexture(qsgTexture) {} - - QVulkanDeviceFunctions* devFuncs = nullptr; - VkDevice device = VK_NULL_HANDLE; - VkImage image = VK_NULL_HANDLE; - VkDeviceMemory memory = VK_NULL_HANDLE; - QSGTexture* qsgTexture = nullptr; - - friend class WlDmaBuffer; -}; - class WlDmaBuffer: public WlBuffer { public: ~WlDmaBuffer() override; @@ -184,9 +151,6 @@ private: friend class LinuxDmabufManager; friend QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer); - - [[nodiscard]] WlBufferQSGTexture* createQsgTextureGl(QQuickWindow* window) const; - [[nodiscard]] WlBufferQSGTexture* createQsgTextureVulkan(QQuickWindow* window) const; }; QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer); diff --git a/src/wayland/buffer/manager.cpp b/src/wayland/buffer/manager.cpp index 9cf77fd..713752a 100644 --- a/src/wayland/buffer/manager.cpp +++ b/src/wayland/buffer/manager.cpp @@ -123,10 +123,6 @@ void WlBufferQSGDisplayNode::setRect(const QRectF& rect) { this->setMatrix(matrix); } -void WlBufferQSGDisplayNode::setFiltering(QSGTexture::Filtering filtering) { - this->imageNode->setFiltering(filtering); -} - void WlBufferQSGDisplayNode::syncSwapchain(const WlBufferSwapchain& swapchain) { auto* buffer = swapchain.frontbuffer(); auto& texture = swapchain.presentSecondBuffer ? this->buffer2 : this->buffer1; diff --git a/src/wayland/buffer/qsg.hpp b/src/wayland/buffer/qsg.hpp index bb05954..c230cfe 100644 --- a/src/wayland/buffer/qsg.hpp +++ b/src/wayland/buffer/qsg.hpp @@ -33,7 +33,6 @@ public: void syncSwapchain(const WlBufferSwapchain& swapchain); void setRect(const QRectF& rect); - void setFiltering(QSGTexture::Filtering filtering); private: QQuickWindow* window; diff --git a/src/wayland/hyprland/focus_grab/qml.cpp b/src/wayland/hyprland/focus_grab/qml.cpp index cf1ac24..e26a75a 100644 --- a/src/wayland/hyprland/focus_grab/qml.cpp +++ b/src/wayland/hyprland/focus_grab/qml.cpp @@ -9,6 +9,7 @@ #include #include "../../../window/proxywindow.hpp" +#include "../../../window/windowinterface.hpp" #include "grab.hpp" #include "manager.hpp" @@ -37,51 +38,8 @@ QObjectList HyprlandFocusGrab::windows() const { return this->windowObjects; } void HyprlandFocusGrab::setWindows(QObjectList windows) { if (windows == this->windowObjects) return; - if (this->grab) this->grab->startTransaction(); - - for (auto* obj: this->windowObjects) { - if (windows.contains(obj)) continue; - QObject::disconnect(obj, nullptr, this, nullptr); - - auto* proxy = ProxyWindowBase::forObject(obj); - if (!proxy) continue; - - QObject::disconnect(proxy, nullptr, this, nullptr); - - if (this->grab && proxy->backingWindow()) { - this->grab->removeWindow(proxy->backingWindow()); - } - } - - for (auto it = windows.begin(); it != windows.end();) { - auto* proxy = ProxyWindowBase::forObject(*it); - if (!proxy) { - it = windows.erase(it); - continue; - } - - if (this->windowObjects.contains(*it)) { - ++it; - continue; - } - - QObject::connect(*it, &QObject::destroyed, this, &HyprlandFocusGrab::onObjectDestroyed); - QObject::connect( - proxy, - &ProxyWindowBase::windowConnected, - this, - &HyprlandFocusGrab::onProxyConnected - ); - - if (this->grab && proxy->backingWindow()) { - this->grab->addWindow(proxy->backingWindow()); - } - - ++it; - } - - if (this->grab) this->grab->completeTransaction(); this->windowObjects = std::move(windows); + this->syncWindows(); emit this->windowsChanged(); } @@ -117,18 +75,59 @@ void HyprlandFocusGrab::tryActivate() { QObject::connect(this->grab, &FocusGrab::cleared, this, &HyprlandFocusGrab::onGrabCleared); this->grab->startTransaction(); - for (auto* obj: this->windowObjects) { - auto* proxy = ProxyWindowBase::forObject(obj); - if (proxy && proxy->backingWindow()) { + for (auto* proxy: this->trackedProxies) { + if (proxy->backingWindow() != nullptr) { this->grab->addWindow(proxy->backingWindow()); } } this->grab->completeTransaction(); } -void HyprlandFocusGrab::onObjectDestroyed(QObject* object) { - this->windowObjects.removeOne(object); - emit this->windowsChanged(); +void HyprlandFocusGrab::syncWindows() { + auto newProxy = QList(); + for (auto* windowObject: this->windowObjects) { + auto* proxyWindow = qobject_cast(windowObject); + + if (proxyWindow == nullptr) { + if (auto* iface = qobject_cast(windowObject)) { + proxyWindow = iface->proxyWindow(); + } + } + + if (proxyWindow != nullptr) { + newProxy.push_back(proxyWindow); + } + } + + if (this->grab) this->grab->startTransaction(); + + for (auto* oldWindow: this->trackedProxies) { + if (!newProxy.contains(oldWindow)) { + QObject::disconnect(oldWindow, nullptr, this, nullptr); + + if (this->grab != nullptr && oldWindow->backingWindow() != nullptr) { + this->grab->removeWindow(oldWindow->backingWindow()); + } + } + } + + for (auto* newProxy: newProxy) { + if (!this->trackedProxies.contains(newProxy)) { + QObject::connect( + newProxy, + &ProxyWindowBase::windowConnected, + this, + &HyprlandFocusGrab::onProxyConnected + ); + + if (this->grab != nullptr && newProxy->backingWindow() != nullptr) { + this->grab->addWindow(newProxy->backingWindow()); + } + } + } + + this->trackedProxies = newProxy; + if (this->grab) this->grab->completeTransaction(); } } // namespace qs::hyprland diff --git a/src/wayland/hyprland/focus_grab/qml.hpp b/src/wayland/hyprland/focus_grab/qml.hpp index 97a10de..705b0d3 100644 --- a/src/wayland/hyprland/focus_grab/qml.hpp +++ b/src/wayland/hyprland/focus_grab/qml.hpp @@ -96,13 +96,15 @@ private slots: void onGrabActivated(); void onGrabCleared(); void onProxyConnected(); - void onObjectDestroyed(QObject* object); private: void tryActivate(); + void syncWindows(); bool targetActive = false; QObjectList windowObjects; + QList trackedProxies; + QList trackedWindows; focus_grab::FocusGrab* grab = nullptr; }; 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..067b922 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( @@ -443,8 +442,8 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) { if (!ok) return; auto workspaceName = QString::fromUtf8(args.at(1)); - auto windowClass = QString::fromUtf8(args.at(2)); - auto windowTitle = QString::fromUtf8(args.at(3)); + auto windowTitle = QString::fromUtf8(args.at(2)); + auto windowClass = QString::fromUtf8(args.at(3)); auto* workspace = this->findWorkspaceByName(workspaceName, false); if (!workspace) { @@ -485,7 +484,6 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) { } auto* toplevel = *toplevelIter; - if (toplevel == this->bActiveToplevel.value()) this->bActiveToplevel = nullptr; auto index = toplevelIter - mList.begin(); this->mToplevels.removeAt(index); @@ -729,7 +727,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/hyprland/ipc/qml.cpp b/src/wayland/hyprland/ipc/qml.cpp index eb5fdc6..89eec9e 100644 --- a/src/wayland/hyprland/ipc/qml.cpp +++ b/src/wayland/hyprland/ipc/qml.cpp @@ -24,9 +24,9 @@ HyprlandIpcQml::HyprlandIpcQml() { QObject::connect( instance, - &HyprlandIpc::focusedWorkspaceChanged, + &HyprlandIpc::focusedMonitorChanged, this, - &HyprlandIpcQml::focusedWorkspaceChanged + &HyprlandIpcQml::focusedMonitorChanged ); QObject::connect( diff --git a/src/wayland/hyprland/surface/qml.cpp b/src/wayland/hyprland/surface/qml.cpp index 4575842..b00ee33 100644 --- a/src/wayland/hyprland/surface/qml.cpp +++ b/src/wayland/hyprland/surface/qml.cpp @@ -14,6 +14,7 @@ #include "../../../core/region.hpp" #include "../../../window/proxywindow.hpp" +#include "../../../window/windowinterface.hpp" #include "manager.hpp" #include "surface.hpp" @@ -22,7 +23,13 @@ using QtWaylandClient::QWaylandWindow; namespace qs::hyprland::surface { HyprlandWindow* HyprlandWindow::qmlAttachedProperties(QObject* object) { - auto* proxyWindow = ProxyWindowBase::forObject(object); + auto* proxyWindow = qobject_cast(object); + + if (!proxyWindow) { + if (auto* iface = qobject_cast(object)) { + proxyWindow = iface->proxyWindow(); + } + } if (!proxyWindow) return nullptr; return new HyprlandWindow(proxyWindow); @@ -58,8 +65,7 @@ void HyprlandWindow::setOpacity(qreal opacity) { if (opacity == this->mOpacity) return; if (opacity < 0.0 || opacity > 1.0) { - qmlWarning( - this + qmlWarning(this ) << "Cannot set HyprlandWindow.opacity to a value larger than 1.0 or smaller than 0.0"; return; } diff --git a/src/wayland/hyprland/surface/surface.cpp b/src/wayland/hyprland/surface/surface.cpp index 774acd0..f49ab8f 100644 --- a/src/wayland/hyprland/surface/surface.cpp +++ b/src/wayland/hyprland/surface/surface.cpp @@ -1,4 +1,5 @@ #include "surface.hpp" +#include #include #include diff --git a/src/wayland/idle_inhibit/CMakeLists.txt b/src/wayland/idle_inhibit/CMakeLists.txt deleted file mode 100644 index eb346d6..0000000 --- a/src/wayland/idle_inhibit/CMakeLists.txt +++ /dev/null @@ -1,25 +0,0 @@ -qt_add_library(quickshell-wayland-idle-inhibit STATIC - proto.cpp - inhibitor.cpp -) - -qt_add_qml_module(quickshell-wayland-idle-inhibit - URI Quickshell.Wayland._IdleInhibitor - VERSION 0.1 - DEPENDENCIES QtQuick -) - -install_qml_module(quickshell-wayland-idle-inhibit) - -qs_add_module_deps_light(quickshell-wayland-idle-inhibit Quickshell) - -wl_proto(wlp-idle-inhibit idle-inhibit-unstable-v1 "${WAYLAND_PROTOCOLS}/unstable/idle-inhibit") - -target_link_libraries(quickshell-wayland-idle-inhibit PRIVATE - Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client - wlp-idle-inhibit -) - -qs_module_pch(quickshell-wayland-idle-inhibit SET large) - -target_link_libraries(quickshell PRIVATE quickshell-wayland-idle-inhibitplugin) diff --git a/src/wayland/idle_inhibit/inhibitor.cpp b/src/wayland/idle_inhibit/inhibitor.cpp deleted file mode 100644 index bfea7a0..0000000 --- a/src/wayland/idle_inhibit/inhibitor.cpp +++ /dev/null @@ -1,125 +0,0 @@ -#include "inhibitor.hpp" - -#include -#include -#include -#include - -#include "../../window/proxywindow.hpp" -#include "proto.hpp" - -namespace qs::wayland::idle_inhibit { -using QtWaylandClient::QWaylandWindow; - -IdleInhibitor::IdleInhibitor() { - this->bBoundWindow.setBinding([this] { - return this->bEnabled ? this->bWindowObject.value() : nullptr; - }); -} - -IdleInhibitor::~IdleInhibitor() { delete this->inhibitor; } - -QObject* IdleInhibitor::window() const { return this->bWindowObject; } - -void IdleInhibitor::setWindow(QObject* window) { - if (window == this->bWindowObject) return; - - auto* proxyWindow = ProxyWindowBase::forObject(window); - this->bWindowObject = proxyWindow ? window : nullptr; -} - -void IdleInhibitor::boundWindowChanged() { - auto* window = this->bBoundWindow.value(); - auto* proxyWindow = ProxyWindowBase::forObject(window); - if (proxyWindow == this->proxyWindow) return; - - if (this->mWaylandWindow) { - QObject::disconnect(this->mWaylandWindow, nullptr, this, nullptr); - this->mWaylandWindow = nullptr; - this->onWaylandSurfaceDestroyed(); - } - - if (this->proxyWindow) { - QObject::disconnect(this->proxyWindow, nullptr, this, nullptr); - this->proxyWindow = nullptr; - } - - if (proxyWindow) { - this->proxyWindow = proxyWindow; - - QObject::connect(proxyWindow, &QObject::destroyed, this, &IdleInhibitor::onWindowDestroyed); - - QObject::connect( - proxyWindow, - &ProxyWindowBase::backerVisibilityChanged, - this, - &IdleInhibitor::onWindowVisibilityChanged - ); - - this->onWindowVisibilityChanged(); - } - - emit this->windowChanged(); -} - -void IdleInhibitor::onWindowDestroyed() { - this->proxyWindow = nullptr; - this->onWaylandSurfaceDestroyed(); - this->bWindowObject = nullptr; -} - -void IdleInhibitor::onWindowVisibilityChanged() { - if (!this->proxyWindow->isVisibleDirect()) return; - - auto* window = this->proxyWindow->backingWindow(); - if (!window->handle()) window->create(); - - auto* waylandWindow = dynamic_cast(window->handle()); - if (waylandWindow == this->mWaylandWindow) return; - this->mWaylandWindow = waylandWindow; - - QObject::connect( - waylandWindow, - &QObject::destroyed, - this, - &IdleInhibitor::onWaylandWindowDestroyed - ); - - QObject::connect( - waylandWindow, - &QWaylandWindow::surfaceCreated, - this, - &IdleInhibitor::onWaylandSurfaceCreated - ); - - QObject::connect( - waylandWindow, - &QWaylandWindow::surfaceDestroyed, - this, - &IdleInhibitor::onWaylandSurfaceDestroyed - ); - - if (waylandWindow->surface()) this->onWaylandSurfaceCreated(); -} - -void IdleInhibitor::onWaylandWindowDestroyed() { this->mWaylandWindow = nullptr; } - -void IdleInhibitor::onWaylandSurfaceCreated() { - auto* manager = impl::IdleInhibitManager::instance(); - - if (!manager) { - qWarning() << "Cannot enable idle inhibitor as idle-inhibit-unstable-v1 is not supported by " - "the current compositor."; - return; - } - - delete this->inhibitor; - this->inhibitor = manager->createIdleInhibitor(this->mWaylandWindow); -} - -void IdleInhibitor::onWaylandSurfaceDestroyed() { - delete this->inhibitor; - this->inhibitor = nullptr; -} - -} // namespace qs::wayland::idle_inhibit diff --git a/src/wayland/idle_inhibit/inhibitor.hpp b/src/wayland/idle_inhibit/inhibitor.hpp deleted file mode 100644 index c1a3e95..0000000 --- a/src/wayland/idle_inhibit/inhibitor.hpp +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "../../window/proxywindow.hpp" -#include "proto.hpp" - -namespace qs::wayland::idle_inhibit { - -///! Prevents a wayland session from idling -/// If an idle daemon is running, it may perform actions such as locking the screen -/// or putting the computer to sleep. -/// -/// An idle inhibitor prevents a wayland session from being marked as idle, if compositor -/// defined heuristics determine the window the inhibitor is attached to is important. -/// -/// A compositor will usually consider a @@Quickshell.PanelWindow or -/// a focused @@Quickshell.FloatingWindow to be important. -/// -/// > [!NOTE] Using an idle inhibitor requires the compositor support the [idle-inhibit-unstable-v1] protocol. -/// -/// [idle-inhibit-unstable-v1]: https://wayland.app/protocols/idle-inhibit-unstable-v1 -class IdleInhibitor: public QObject { - Q_OBJECT; - QML_ELEMENT; - // clang-format off - /// If the idle inhibitor should be enabled. Defaults to false. - Q_PROPERTY(bool enabled READ default WRITE default NOTIFY enabledChanged BINDABLE bindableEnabled); - /// The window to associate the idle inhibitor with. This may be used by the compositor - /// to determine if the inhibitor should be respected. - /// - /// Must be set to a non null value to enable the inhibitor. - Q_PROPERTY(QObject* window READ window WRITE setWindow NOTIFY windowChanged); - // clang-format on - -public: - IdleInhibitor(); - ~IdleInhibitor() override; - Q_DISABLE_COPY_MOVE(IdleInhibitor); - - [[nodiscard]] QObject* window() const; - void setWindow(QObject* window); - - [[nodiscard]] QBindable bindableEnabled() { return &this->bEnabled; } - -signals: - void enabledChanged(); - void windowChanged(); - -private slots: - void onWindowDestroyed(); - void onWindowVisibilityChanged(); - void onWaylandWindowDestroyed(); - void onWaylandSurfaceCreated(); - void onWaylandSurfaceDestroyed(); - -private: - void boundWindowChanged(); - ProxyWindowBase* proxyWindow = nullptr; - QtWaylandClient::QWaylandWindow* mWaylandWindow = nullptr; - - impl::IdleInhibitor* inhibitor = nullptr; - - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(IdleInhibitor, bool, bEnabled, &IdleInhibitor::enabledChanged); - Q_OBJECT_BINDABLE_PROPERTY(IdleInhibitor, QObject*, bWindowObject, &IdleInhibitor::windowChanged); - Q_OBJECT_BINDABLE_PROPERTY(IdleInhibitor, QObject*, bBoundWindow, &IdleInhibitor::boundWindowChanged); - // clang-format on -}; - -} // namespace qs::wayland::idle_inhibit diff --git a/src/wayland/idle_inhibit/proto.cpp b/src/wayland/idle_inhibit/proto.cpp deleted file mode 100644 index 25701a7..0000000 --- a/src/wayland/idle_inhibit/proto.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "proto.hpp" - -#include -#include -#include -#include - -#include "../../core/logcat.hpp" - -namespace qs::wayland::idle_inhibit::impl { - -namespace { -QS_LOGGING_CATEGORY(logIdleInhibit, "quickshell.wayland.idle_inhibit", QtWarningMsg); -} - -IdleInhibitManager::IdleInhibitManager(): QWaylandClientExtensionTemplate(1) { this->initialize(); } - -IdleInhibitManager* IdleInhibitManager::instance() { - static auto* instance = new IdleInhibitManager(); // NOLINT - return instance->isInitialized() ? instance : nullptr; -} - -IdleInhibitor* IdleInhibitManager::createIdleInhibitor(QtWaylandClient::QWaylandWindow* surface) { - auto* inhibitor = new IdleInhibitor(this->create_inhibitor(surface->surface())); - qCDebug(logIdleInhibit) << "Created inhibitor" << inhibitor; - return inhibitor; -} - -IdleInhibitor::~IdleInhibitor() { - qCDebug(logIdleInhibit) << "Destroyed inhibitor" << this; - if (this->isInitialized()) this->destroy(); -} - -} // namespace qs::wayland::idle_inhibit::impl diff --git a/src/wayland/idle_inhibit/proto.hpp b/src/wayland/idle_inhibit/proto.hpp deleted file mode 100644 index c797c33..0000000 --- a/src/wayland/idle_inhibit/proto.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "wayland-idle-inhibit-unstable-v1-client-protocol.h" - -namespace qs::wayland::idle_inhibit::impl { - -class IdleInhibitor; - -class IdleInhibitManager - : public QWaylandClientExtensionTemplate - , public QtWayland::zwp_idle_inhibit_manager_v1 { -public: - explicit IdleInhibitManager(); - - IdleInhibitor* createIdleInhibitor(QtWaylandClient::QWaylandWindow* surface); - - static IdleInhibitManager* instance(); -}; - -class IdleInhibitor: public QtWayland::zwp_idle_inhibitor_v1 { -public: - explicit IdleInhibitor(::zwp_idle_inhibitor_v1* inhibitor) - : QtWayland::zwp_idle_inhibitor_v1(inhibitor) {} - - ~IdleInhibitor() override; - Q_DISABLE_COPY_MOVE(IdleInhibitor); -}; - -} // namespace qs::wayland::idle_inhibit::impl diff --git a/src/wayland/idle_inhibit/test/manual/idle_inhibit.qml b/src/wayland/idle_inhibit/test/manual/idle_inhibit.qml deleted file mode 100644 index f80e647..0000000 --- a/src/wayland/idle_inhibit/test/manual/idle_inhibit.qml +++ /dev/null @@ -1,20 +0,0 @@ -import QtQuick -import QtQuick.Controls -import Quickshell -import Quickshell.Wayland - -FloatingWindow { - id: root - color: contentItem.palette.window - - CheckBox { - id: enableCb - anchors.centerIn: parent - text: "Enable Inhibitor" - } - - IdleInhibitor { - window: root - enabled: enableCb.checked - } -} diff --git a/src/wayland/idle_notify/CMakeLists.txt b/src/wayland/idle_notify/CMakeLists.txt deleted file mode 100644 index 889c7ce..0000000 --- a/src/wayland/idle_notify/CMakeLists.txt +++ /dev/null @@ -1,25 +0,0 @@ -qt_add_library(quickshell-wayland-idle-notify STATIC - proto.cpp - monitor.cpp -) - -qt_add_qml_module(quickshell-wayland-idle-notify - URI Quickshell.Wayland._IdleNotify - VERSION 0.1 - DEPENDENCIES QtQuick -) - -install_qml_module(quickshell-wayland-idle-notify) - -qs_add_module_deps_light(quickshell-wayland-idle-notify Quickshell) - -wl_proto(wlp-idle-notify ext-idle-notify-v1 "${WAYLAND_PROTOCOLS}/staging/ext-idle-notify") - -target_link_libraries(quickshell-wayland-idle-notify PRIVATE - Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client - wlp-idle-notify -) - -qs_module_pch(quickshell-wayland-idle-notify SET large) - -target_link_libraries(quickshell PRIVATE quickshell-wayland-idle-notifyplugin) diff --git a/src/wayland/idle_notify/monitor.cpp b/src/wayland/idle_notify/monitor.cpp deleted file mode 100644 index 3f496e4..0000000 --- a/src/wayland/idle_notify/monitor.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "monitor.hpp" -#include - -#include -#include -#include - -#include "proto.hpp" - -namespace qs::wayland::idle_notify { - -IdleMonitor::~IdleMonitor() { delete this->bNotification.value(); } - -void IdleMonitor::onPostReload() { - this->bParams.setBinding([this] { - return Params { - .enabled = this->bEnabled.value(), - .timeout = this->bTimeout.value(), - .respectInhibitors = this->bRespectInhibitors.value() - }; - }); - - this->bIsIdle.setBinding([this] { - auto* notification = this->bNotification.value(); - return notification ? notification->bIsIdle.value() : false; - }); -} - -void IdleMonitor::updateNotification() { - auto* notification = this->bNotification.value(); - delete notification; - notification = nullptr; - - auto guard = qScopeGuard([&, this] { this->bNotification = notification; }); - - auto params = this->bParams.value(); - - if (params.enabled) { - auto* manager = impl::IdleNotificationManager::instance(); - - if (!manager) { - qWarning() << "Cannot create idle monitor as ext-idle-notify-v1 is not supported by the " - "current compositor."; - return; - } - - auto timeout = static_cast(std::max(0, static_cast(params.timeout * 1000))); - notification = manager->createIdleNotification(timeout, params.respectInhibitors); - } -} - -} // namespace qs::wayland::idle_notify diff --git a/src/wayland/idle_notify/monitor.hpp b/src/wayland/idle_notify/monitor.hpp deleted file mode 100644 index 25bd5a6..0000000 --- a/src/wayland/idle_notify/monitor.hpp +++ /dev/null @@ -1,78 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "../../core/reload.hpp" -#include "proto.hpp" - -namespace qs::wayland::idle_notify { - -///! Provides a notification when a wayland session is makred idle -/// An idle monitor detects when the user stops providing input for a period of time. -/// -/// > [!NOTE] Using an idle monitor requires the compositor support the [ext-idle-notify-v1] protocol. -/// -/// [ext-idle-notify-v1]: https://wayland.app/protocols/ext-idle-notify-v1 -class IdleMonitor: public PostReloadHook { - Q_OBJECT; - QML_ELEMENT; - // clang-format off - /// If the idle monitor should be enabled. Defaults to true. - Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged); - /// The amount of time in seconds the idle monitor should wait before reporting an idle state. - /// - /// Defaults to zero, which reports idle status immediately. - Q_PROPERTY(qreal timeout READ default WRITE default NOTIFY timeoutChanged BINDABLE bindableTimeout); - /// When set to true, @@isIdle will depend on both user interaction and active idle inhibitors. - /// When false, the value will depend solely on user interaction. Defaults to true. - Q_PROPERTY(bool respectInhibitors READ default WRITE default NOTIFY respectInhibitorsChanged BINDABLE bindableRespectInhibitors); - /// This property is true if the user has been idle for at least @@timeout. - /// What is considered to be idle is influenced by @@respectInhibitors. - Q_PROPERTY(bool isIdle READ default NOTIFY isIdleChanged BINDABLE bindableIsIdle); - // clang-format on - -public: - IdleMonitor() = default; - ~IdleMonitor() override; - Q_DISABLE_COPY_MOVE(IdleMonitor); - - void onPostReload() override; - - [[nodiscard]] bool isEnabled() const { return this->bNotification.value(); } - void setEnabled(bool enabled) { this->bEnabled = enabled; } - - [[nodiscard]] QBindable bindableTimeout() { return &this->bTimeout; } - [[nodiscard]] QBindable bindableRespectInhibitors() { return &this->bRespectInhibitors; } - [[nodiscard]] QBindable bindableIsIdle() const { return &this->bIsIdle; } - -signals: - void enabledChanged(); - void timeoutChanged(); - void respectInhibitorsChanged(); - void isIdleChanged(); - -private: - void updateNotification(); - - struct Params { - bool enabled; - qreal timeout; - bool respectInhibitors; - }; - - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(IdleMonitor, bool, bEnabled, true, &IdleMonitor::enabledChanged); - Q_OBJECT_BINDABLE_PROPERTY(IdleMonitor, qreal, bTimeout, &IdleMonitor::timeoutChanged); - Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(IdleMonitor, bool, bRespectInhibitors, true, &IdleMonitor::respectInhibitorsChanged); - Q_OBJECT_BINDABLE_PROPERTY(IdleMonitor, Params, bParams, &IdleMonitor::updateNotification); - Q_OBJECT_BINDABLE_PROPERTY(IdleMonitor, impl::IdleNotification*, bNotification); - Q_OBJECT_BINDABLE_PROPERTY(IdleMonitor, bool, bIsIdle, &IdleMonitor::isIdleChanged); - // clang-format on -}; - -} // namespace qs::wayland::idle_notify diff --git a/src/wayland/idle_notify/proto.cpp b/src/wayland/idle_notify/proto.cpp deleted file mode 100644 index 9b3fa81..0000000 --- a/src/wayland/idle_notify/proto.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include "proto.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../core/logcat.hpp" - -namespace qs::wayland::idle_notify { -QS_LOGGING_CATEGORY(logIdleNotify, "quickshell.wayland.idle_notify", QtWarningMsg); -} - -namespace qs::wayland::idle_notify::impl { - -IdleNotificationManager::IdleNotificationManager(): QWaylandClientExtensionTemplate(2) { - this->initialize(); -} - -IdleNotificationManager* IdleNotificationManager::instance() { - static auto* instance = new IdleNotificationManager(); // NOLINT - return instance->isInitialized() ? instance : nullptr; -} - -IdleNotification* -IdleNotificationManager::createIdleNotification(quint32 timeout, bool respectInhibitors) { - if (!respectInhibitors - && this->QtWayland::ext_idle_notifier_v1::version() - < EXT_IDLE_NOTIFIER_V1_GET_INPUT_IDLE_NOTIFICATION_SINCE_VERSION) - { - qCWarning(logIdleNotify) << "Cannot ignore inhibitors for new idle notifier: Compositor does " - "not support protocol version 2."; - - respectInhibitors = true; - } - - auto* display = QtWaylandClient::QWaylandIntegration::instance()->display(); - auto* inputDevice = display->lastInputDevice(); - if (inputDevice == nullptr) inputDevice = display->defaultInputDevice(); - if (inputDevice == nullptr) { - qCCritical(logIdleNotify) << "Could not create idle notifier: No seat."; - return nullptr; - } - - ::ext_idle_notification_v1* notification = nullptr; - if (respectInhibitors) notification = this->get_idle_notification(timeout, inputDevice->object()); - else notification = this->get_input_idle_notification(timeout, inputDevice->object()); - - auto* wrapper = new IdleNotification(notification); - qCDebug(logIdleNotify) << "Created" << wrapper << "with timeout:" << timeout - << "respects inhibitors:" << respectInhibitors; - return wrapper; -} - -IdleNotification::~IdleNotification() { - qCDebug(logIdleNotify) << "Destroyed" << this; - if (this->isInitialized()) this->destroy(); -} - -void IdleNotification::ext_idle_notification_v1_idled() { - qCDebug(logIdleNotify) << this << "has been marked idle"; - this->bIsIdle = true; -} - -void IdleNotification::ext_idle_notification_v1_resumed() { - qCDebug(logIdleNotify) << this << "has been marked resumed"; - this->bIsIdle = false; -} - -} // namespace qs::wayland::idle_notify::impl diff --git a/src/wayland/idle_notify/proto.hpp b/src/wayland/idle_notify/proto.hpp deleted file mode 100644 index 11dbf29..0000000 --- a/src/wayland/idle_notify/proto.hpp +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../core/logcat.hpp" - -namespace qs::wayland::idle_notify { -QS_DECLARE_LOGGING_CATEGORY(logIdleNotify); -} - -namespace qs::wayland::idle_notify::impl { - -class IdleNotification; - -class IdleNotificationManager - : public QWaylandClientExtensionTemplate - , public QtWayland::ext_idle_notifier_v1 { -public: - explicit IdleNotificationManager(); - IdleNotification* createIdleNotification(quint32 timeout, bool respectInhibitors); - - static IdleNotificationManager* instance(); -}; - -class IdleNotification - : public QObject - , public QtWayland::ext_idle_notification_v1 { - Q_OBJECT; - -public: - explicit IdleNotification(::ext_idle_notification_v1* notification) - : QtWayland::ext_idle_notification_v1(notification) {} - - ~IdleNotification() override; - Q_DISABLE_COPY_MOVE(IdleNotification); - - Q_OBJECT_BINDABLE_PROPERTY(IdleNotification, bool, bIsIdle); - -protected: - void ext_idle_notification_v1_idled() override; - void ext_idle_notification_v1_resumed() override; -}; - -} // namespace qs::wayland::idle_notify::impl diff --git a/src/wayland/idle_notify/test/manual/idle_notify.qml b/src/wayland/idle_notify/test/manual/idle_notify.qml deleted file mode 100644 index 3bf6cbd..0000000 --- a/src/wayland/idle_notify/test/manual/idle_notify.qml +++ /dev/null @@ -1,44 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import Quickshell -import Quickshell.Wayland - -FloatingWindow { - color: contentItem.palette.window - - IdleMonitor { - id: monitor - enabled: enabledCb.checked - timeout: timeoutSb.value - respectInhibitors: respectInhibitorsCb.checked - } - - ColumnLayout { - Label { text: `Is idle? ${monitor.isIdle}` } - - CheckBox { - id: enabledCb - text: "Enabled" - checked: true - } - - CheckBox { - id: respectInhibitorsCb - text: "Respect Inhibitors" - checked: true - } - - RowLayout { - Label { text: "Timeout" } - - SpinBox { - id: timeoutSb - editable: true - from: 0 - to: 1000 - value: 5 - } - } - } -} diff --git a/src/wayland/init.cpp b/src/wayland/init.cpp index 579e42a..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) @@ -33,8 +32,6 @@ class WaylandPlugin: public QsEnginePlugin { return isWayland; } - void preinit() override { installWlProxySafeDeref(); } - void init() override { installPlatformMenuHook(); installPopupPositioner(); diff --git a/src/wayland/module.md b/src/wayland/module.md index 964fa76..b9f8f59 100644 --- a/src/wayland/module.md +++ b/src/wayland/module.md @@ -5,9 +5,5 @@ headers = [ "session_lock.hpp", "toplevel_management/qml.hpp", "screencopy/view.hpp", - "idle_inhibit/inhibitor.hpp", - "idle_notify/monitor.hpp", - "shortcuts_inhibit/inhibitor.hpp", - "background_effect/qml.hpp", ] ----- diff --git a/src/wayland/popupanchor.cpp b/src/wayland/popupanchor.cpp index 14e1923..cbbccae 100644 --- a/src/wayland/popupanchor.cpp +++ b/src/wayland/popupanchor.cpp @@ -16,6 +16,7 @@ using XdgPositioner = QtWayland::xdg_positioner; using qs::wayland::xdg_shell::XdgWmBase; void WaylandPopupPositioner::reposition(PopupAnchor* anchor, QWindow* window, bool onlyIfDirty) { + auto* waylandWindow = dynamic_cast(window->handle()); auto* popupRole = waylandWindow ? waylandWindow->surfaceRole<::xdg_popup>() : nullptr; diff --git a/src/wayland/screencopy/view.cpp b/src/wayland/screencopy/view.cpp index 7d10dc2..7828c98 100644 --- a/src/wayland/screencopy/view.cpp +++ b/src/wayland/screencopy/view.cpp @@ -167,7 +167,6 @@ QSGNode* ScreencopyView::updatePaintNode(QSGNode* oldNode, UpdatePaintNodeData* auto& swapchain = this->context->swapchain(); node->syncSwapchain(swapchain); node->setRect(this->boundingRect()); - node->setFiltering(QSGTexture::Linear); // NOLINT (misc-include-cleaner) if (this->mLive) this->context->captureFrame(); return node; diff --git a/src/wayland/screencopy/wlr_screencopy/wlr_screencopy.cpp b/src/wayland/screencopy/wlr_screencopy/wlr_screencopy.cpp index 927da8d..e1553f5 100644 --- a/src/wayland/screencopy/wlr_screencopy/wlr_screencopy.cpp +++ b/src/wayland/screencopy/wlr_screencopy/wlr_screencopy.cpp @@ -68,11 +68,11 @@ void WlrScreencopyContext::captureFrame() { this->request.reset(); if (this->region.isEmpty()) { - this->init(this->manager->capture_output(this->paintCursors ? 1 : 0, this->screen->output())); + this->init(manager->capture_output(this->paintCursors ? 1 : 0, screen->output())); } else { - this->init(this->manager->capture_output_region( + this->init(manager->capture_output_region( this->paintCursors ? 1 : 0, - this->screen->output(), + screen->output(), this->region.x(), this->region.y(), this->region.width(), @@ -165,8 +165,7 @@ WlrScreencopyContext::OutputTransformQuery::~OutputTransformQuery() { if (this->isInitialized()) this->release(); } -void WlrScreencopyContext::OutputTransformQuery::setScreen( - QtWaylandClient::QWaylandScreen* screen +void WlrScreencopyContext::OutputTransformQuery::setScreen(QtWaylandClient::QWaylandScreen* screen ) { // cursed hack class QWaylandScreenReflector: public QtWaylandClient::QWaylandScreen { diff --git a/src/wayland/session_lock.cpp b/src/wayland/session_lock.cpp index 2ebe3fd..0ecf9ec 100644 --- a/src/wayland/session_lock.cpp +++ b/src/wayland/session_lock.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -80,8 +79,8 @@ void WlSessionLock::updateSurfaces(bool show, WlSessionLock* old) { auto* instance = qobject_cast(instanceObj); if (instance == nullptr) { - qWarning() - << "WlSessionLock.surface does not create a WlSessionLockSurface. Aborting lock."; + qWarning( + ) << "WlSessionLock.surface does not create a WlSessionLockSurface. Aborting lock."; if (instanceObj != nullptr) instanceObj->deleteLater(); this->unlock(); return; @@ -217,15 +216,6 @@ void WlSessionLockSurface::onReload(QObject* oldInstance) { if (this->window == nullptr) { this->window = new QQuickWindow(); - - // needed for vulkan dmabuf import, qt ignores these if not applicable - auto graphicsConfig = this->window->graphicsConfiguration(); - graphicsConfig.setDeviceExtensions({ - "VK_KHR_external_memory_fd", - "VK_EXT_external_memory_dma_buf", - "VK_EXT_image_drm_format_modifier", - }); - this->window->setGraphicsConfiguration(graphicsConfig); } this->mContentItem->setParentItem(this->window->contentItem()); diff --git a/src/wayland/session_lock/shell_integration.cpp b/src/wayland/session_lock/shell_integration.cpp index 207ef42..2b5fdbf 100644 --- a/src/wayland/session_lock/shell_integration.cpp +++ b/src/wayland/session_lock/shell_integration.cpp @@ -10,11 +10,10 @@ QtWaylandClient::QWaylandShellSurface* QSWaylandSessionLockIntegration::createShellSurface(QtWaylandClient::QWaylandWindow* window) { auto* lock = LockWindowExtension::get(window->window()); - if (lock == nullptr || lock->surface == nullptr) { + if (lock == nullptr || lock->surface == nullptr || !lock->surface->isExposed()) { qFatal() << "Visibility canary failed. A window with a LockWindowExtension MUST be set to " "visible via LockWindowExtension::setVisible"; } - QSWaylandSessionLockSurface* surface = lock->surface; // shut up the unused include linter - return surface; + return lock->surface; } diff --git a/src/wayland/session_lock/shell_integration.hpp b/src/wayland/session_lock/shell_integration.hpp index b2e2891..d6f9175 100644 --- a/src/wayland/session_lock/shell_integration.hpp +++ b/src/wayland/session_lock/shell_integration.hpp @@ -8,6 +8,6 @@ class QSWaylandSessionLockIntegration: public QtWaylandClient::QWaylandShellIntegration { public: bool initialize(QtWaylandClient::QWaylandDisplay* /* display */) override { return true; } - QtWaylandClient::QWaylandShellSurface* - createShellSurface(QtWaylandClient::QWaylandWindow* window) override; + QtWaylandClient::QWaylandShellSurface* createShellSurface(QtWaylandClient::QWaylandWindow* window + ) override; }; diff --git a/src/wayland/session_lock/surface.cpp b/src/wayland/session_lock/surface.cpp index c73f459..bc0e75d 100644 --- a/src/wayland/session_lock/surface.cpp +++ b/src/wayland/session_lock/surface.cpp @@ -28,7 +28,7 @@ QSWaylandSessionLockSurface::QSWaylandSessionLockSurface(QtWaylandClient::QWayla wl_output* output = nullptr; // NOLINT (include) auto* waylandScreen = dynamic_cast(qwindow->screen()->handle()); - if (waylandScreen != nullptr && !waylandScreen->isPlaceholder() && waylandScreen->output()) { + if (waylandScreen != nullptr) { output = waylandScreen->output(); } else { qFatal() << "Session lock screen does not corrospond to a real screen. Force closing window"; @@ -48,6 +48,16 @@ void QSWaylandSessionLockSurface::applyConfigure() { this->window()->resizeFromApplyConfigure(this->size); } +bool QSWaylandSessionLockSurface::handleExpose(const QRegion& region) { + if (this->initBuf != nullptr) { + // at this point qt's next commit to the surface will have a new buffer, and we can safely delete this one. + delete this->initBuf; + this->initBuf = nullptr; + } + + return this->QtWaylandClient::QWaylandShellSurface::handleExpose(region); +} + void QSWaylandSessionLockSurface::setExtension(LockWindowExtension* ext) { if (ext == nullptr) { if (this->window() != nullptr) this->window()->window()->close(); @@ -61,6 +71,11 @@ void QSWaylandSessionLockSurface::setExtension(LockWindowExtension* ext) { } } +void QSWaylandSessionLockSurface::setVisible() { + if (this->configured && !this->visible) this->initVisible(); + this->visible = true; +} + void QSWaylandSessionLockSurface::ext_session_lock_surface_v1_configure( quint32 serial, quint32 width, @@ -82,41 +97,13 @@ void QSWaylandSessionLockSurface::ext_session_lock_surface_v1_configure( #else this->window()->updateExposure(); #endif - -#if QT_VERSION < QT_VERSION_CHECK(6, 10, 0) if (this->visible) this->initVisible(); -#endif } else { // applyConfigureWhenPossible runs too late and causes a protocol error on reconfigure. this->window()->resizeFromApplyConfigure(this->size); } } -#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0) - -bool QSWaylandSessionLockSurface::commitSurfaceRole() const { return false; } - -void QSWaylandSessionLockSurface::setVisible() { this->window()->window()->setVisible(true); } - -#else - -bool QSWaylandSessionLockSurface::handleExpose(const QRegion& region) { - if (this->initBuf != nullptr) { - // at this point qt's next commit to the surface will have a new buffer, and we can safely delete this one. - delete this->initBuf; - this->initBuf = nullptr; - } - - return this->QtWaylandClient::QWaylandShellSurface::handleExpose(region); -} - -void QSWaylandSessionLockSurface::setVisible() { - if (this->configured && !this->visible) this->initVisible(); - this->visible = true; -} - -#endif - #if QT_VERSION < QT_VERSION_CHECK(6, 9, 0) #include @@ -136,7 +123,7 @@ void QSWaylandSessionLockSurface::initVisible() { this->window()->window()->setVisible(true); } -#elif QT_VERSION < QT_VERSION_CHECK(6, 10, 0) +#else #include diff --git a/src/wayland/session_lock/surface.hpp b/src/wayland/session_lock/surface.hpp index 39be88d..f7abc77 100644 --- a/src/wayland/session_lock/surface.hpp +++ b/src/wayland/session_lock/surface.hpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include @@ -21,12 +20,7 @@ public: [[nodiscard]] bool isExposed() const override; void applyConfigure() override; - -#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0) - [[nodiscard]] bool commitSurfaceRole() const override; -#else bool handleExpose(const QRegion& region) override; -#endif void setExtension(LockWindowExtension* ext); void setVisible(); @@ -35,13 +29,11 @@ private: void ext_session_lock_surface_v1_configure(quint32 serial, quint32 width, quint32 height) override; -#if QT_VERSION < QT_VERSION_CHECK(6, 10, 0) void initVisible(); - bool visible = false; - QtWaylandClient::QWaylandShmBuffer* initBuf = nullptr; -#endif LockWindowExtension* ext = nullptr; QSize size; bool configured = false; + bool visible = false; + QtWaylandClient::QWaylandShmBuffer* initBuf = nullptr; }; diff --git a/src/wayland/shortcuts_inhibit/CMakeLists.txt b/src/wayland/shortcuts_inhibit/CMakeLists.txt deleted file mode 100644 index 8dedd3d..0000000 --- a/src/wayland/shortcuts_inhibit/CMakeLists.txt +++ /dev/null @@ -1,25 +0,0 @@ -qt_add_library(quickshell-wayland-shortcuts-inhibit STATIC - proto.cpp - inhibitor.cpp -) - -qt_add_qml_module(quickshell-wayland-shortcuts-inhibit - URI Quickshell.Wayland._ShortcutsInhibitor - VERSION 0.1 - DEPENDENCIES QtQuick -) - -install_qml_module(quickshell-wayland-shortcuts-inhibit) - -qs_add_module_deps_light(quickshell-wayland-shortcuts-inhibit Quickshell) - -wl_proto(wlp-shortcuts-inhibit keyboard-shortcuts-inhibit-unstable-v1 "${WAYLAND_PROTOCOLS}/unstable/keyboard-shortcuts-inhibit") - -target_link_libraries(quickshell-wayland-shortcuts-inhibit PRIVATE - Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client - wlp-shortcuts-inhibit -) - -qs_module_pch(quickshell-wayland-shortcuts-inhibit SET large) - -target_link_libraries(quickshell PRIVATE quickshell-wayland-shortcuts-inhibitplugin) \ No newline at end of file diff --git a/src/wayland/shortcuts_inhibit/inhibitor.cpp b/src/wayland/shortcuts_inhibit/inhibitor.cpp deleted file mode 100644 index a91d5e2..0000000 --- a/src/wayland/shortcuts_inhibit/inhibitor.cpp +++ /dev/null @@ -1,179 +0,0 @@ -#include "inhibitor.hpp" - -#include -#include -#include -#include -#include -#include -#include - -#include "../../window/proxywindow.hpp" -#include "proto.hpp" - -namespace qs::wayland::shortcuts_inhibit { -using QtWaylandClient::QWaylandWindow; - -ShortcutInhibitor::ShortcutInhibitor() { - this->bBoundWindow.setBinding([this] { - return this->bEnabled ? this->bWindowObject.value() : nullptr; - }); - - this->bActive.setBinding([this]() { - auto* inhibitor = this->bInhibitor.value(); - if (!inhibitor) return false; - if (!inhibitor->bindableActive().value()) return false; - return this->bWindow.value() == this->bFocusedWindow; - }); - - QObject::connect( - dynamic_cast(QGuiApplication::instance()), - &QGuiApplication::focusWindowChanged, - this, - &ShortcutInhibitor::onFocusedWindowChanged - ); - - this->onFocusedWindowChanged(QGuiApplication::focusWindow()); -} - -ShortcutInhibitor::~ShortcutInhibitor() { - if (!this->bInhibitor) return; - - auto* manager = impl::ShortcutsInhibitManager::instance(); - if (!manager) return; - - manager->unrefShortcutsInhibitor(this->bInhibitor); -} - -void ShortcutInhibitor::onBoundWindowChanged() { - auto* window = this->bBoundWindow.value(); - auto* proxyWindow = ProxyWindowBase::forObject(window); - if (proxyWindow == this->proxyWindow) return; - - if (this->proxyWindow) { - QObject::disconnect(this->proxyWindow, nullptr, this, nullptr); - this->proxyWindow = nullptr; - } - - if (this->mWaylandWindow) { - QObject::disconnect(this->mWaylandWindow, nullptr, this, nullptr); - this->mWaylandWindow = nullptr; - this->onWaylandSurfaceDestroyed(); - } - - if (proxyWindow) { - this->proxyWindow = proxyWindow; - - QObject::connect(proxyWindow, &QObject::destroyed, this, &ShortcutInhibitor::onWindowDestroyed); - - QObject::connect( - proxyWindow, - &ProxyWindowBase::backerVisibilityChanged, - this, - &ShortcutInhibitor::onWindowVisibilityChanged - ); - - this->onWindowVisibilityChanged(); - } -} - -void ShortcutInhibitor::onWindowDestroyed() { - this->proxyWindow = nullptr; - this->onWaylandSurfaceDestroyed(); -} - -void ShortcutInhibitor::onWindowVisibilityChanged() { - if (!this->proxyWindow->isVisibleDirect()) return; - - auto* window = this->proxyWindow->backingWindow(); - if (!window->handle()) window->create(); - - auto* waylandWindow = dynamic_cast(window->handle()); - if (!waylandWindow) { - qCCritical(impl::logShortcutsInhibit()) << "Window handle is not a QWaylandWindow"; - return; - } - if (waylandWindow == this->mWaylandWindow) return; - this->mWaylandWindow = waylandWindow; - this->bWindow = window; - - QObject::connect( - waylandWindow, - &QObject::destroyed, - this, - &ShortcutInhibitor::onWaylandWindowDestroyed - ); - - QObject::connect( - waylandWindow, - &QWaylandWindow::surfaceCreated, - this, - &ShortcutInhibitor::onWaylandSurfaceCreated - ); - - QObject::connect( - waylandWindow, - &QWaylandWindow::surfaceDestroyed, - this, - &ShortcutInhibitor::onWaylandSurfaceDestroyed - ); - - if (waylandWindow->surface()) this->onWaylandSurfaceCreated(); -} - -void ShortcutInhibitor::onWaylandWindowDestroyed() { this->mWaylandWindow = nullptr; } - -void ShortcutInhibitor::onWaylandSurfaceCreated() { - auto* manager = impl::ShortcutsInhibitManager::instance(); - - if (!manager) { - qWarning() << "Cannot enable shortcuts inhibitor as keyboard-shortcuts-inhibit-unstable-v1 is " - "not supported by " - "the current compositor."; - return; - } - - if (this->bInhibitor) { - qFatal("ShortcutsInhibitor: inhibitor already exists when creating surface"); - } - - this->bInhibitor = manager->createShortcutsInhibitor(this->mWaylandWindow); -} - -void ShortcutInhibitor::onWaylandSurfaceDestroyed() { - if (!this->bInhibitor) return; - - auto* manager = impl::ShortcutsInhibitManager::instance(); - if (!manager) return; - - manager->unrefShortcutsInhibitor(this->bInhibitor); - this->bInhibitor = nullptr; -} - -void ShortcutInhibitor::onInhibitorChanged() { - auto* inhibitor = this->bInhibitor.value(); - if (inhibitor) { - QObject::connect( - inhibitor, - &impl::ShortcutsInhibitor::activeChanged, - this, - &ShortcutInhibitor::onInhibitorActiveChanged - ); - } -} - -void ShortcutInhibitor::onInhibitorActiveChanged() { - auto* inhibitor = this->bInhibitor.value(); - if (inhibitor && !inhibitor->isActive()) { - // Compositor has deactivated the inhibitor, making it invalid. - // Set enabled to false so the user can enable it again to create a new inhibitor. - this->bEnabled = false; - emit this->cancelled(); - } -} - -void ShortcutInhibitor::onFocusedWindowChanged(QWindow* focusedWindow) { - this->bFocusedWindow = focusedWindow; -} - -} // namespace qs::wayland::shortcuts_inhibit diff --git a/src/wayland/shortcuts_inhibit/inhibitor.hpp b/src/wayland/shortcuts_inhibit/inhibitor.hpp deleted file mode 100644 index 2eed54f..0000000 --- a/src/wayland/shortcuts_inhibit/inhibitor.hpp +++ /dev/null @@ -1,89 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "../../window/proxywindow.hpp" -#include "proto.hpp" - -namespace qs::wayland::shortcuts_inhibit { - -///! Prevents compositor keyboard shortcuts from being triggered -/// A shortcuts inhibitor prevents the compositor from processing its own keyboard shortcuts -/// for the focused surface. This allows applications to receive key events for shortcuts -/// that would normally be handled by the compositor. -/// -/// The inhibitor only takes effect when the associated window is focused and the inhibitor -/// is enabled. The compositor may choose to ignore inhibitor requests based on its policy. -/// -/// > [!NOTE] Using a shortcuts inhibitor requires the compositor support the [keyboard-shortcuts-inhibit-unstable-v1] protocol. -/// -/// [keyboard-shortcuts-inhibit-unstable-v1]: https://wayland.app/protocols/keyboard-shortcuts-inhibit-unstable-v1 -class ShortcutInhibitor: public QObject { - Q_OBJECT; - QML_ELEMENT; - // clang-format off - /// If the shortcuts inhibitor should be enabled. Defaults to false. - Q_PROPERTY(bool enabled READ default WRITE default NOTIFY enabledChanged BINDABLE bindableEnabled); - /// The window to associate the shortcuts inhibitor with. - /// The inhibitor will only inhibit shortcuts pressed while this window has keyboard focus. - /// - /// Must be set to a non null value to enable the inhibitor. - Q_PROPERTY(QObject* window READ default WRITE default NOTIFY windowChanged BINDABLE bindableWindow); - /// Whether the inhibitor is currently active. The inhibitor is only active if @@enabled is true, - /// @@window has keyboard focus, and the compositor grants the inhibit request. - /// - /// The compositor may deactivate the inhibitor at any time (for example, if the user requests - /// normal shortcuts to be restored). When deactivated by the compositor, the inhibitor cannot be - /// programmatically reactivated. - Q_PROPERTY(bool active READ default NOTIFY activeChanged BINDABLE bindableActive); - // clang-format on - -public: - ShortcutInhibitor(); - ~ShortcutInhibitor() override; - Q_DISABLE_COPY_MOVE(ShortcutInhibitor); - - [[nodiscard]] QBindable bindableEnabled() { return &this->bEnabled; } - [[nodiscard]] QBindable bindableWindow() { return &this->bWindowObject; } - [[nodiscard]] QBindable bindableActive() const { return &this->bActive; } - -signals: - void enabledChanged(); - void windowChanged(); - void activeChanged(); - /// Sent if the compositor cancels the inhibitor while it is active. - void cancelled(); - -private slots: - void onWindowDestroyed(); - void onWindowVisibilityChanged(); - void onWaylandWindowDestroyed(); - void onWaylandSurfaceCreated(); - void onWaylandSurfaceDestroyed(); - void onInhibitorActiveChanged(); - -private: - void onBoundWindowChanged(); - void onInhibitorChanged(); - void onFocusedWindowChanged(QWindow* focusedWindow); - - ProxyWindowBase* proxyWindow = nullptr; - QtWaylandClient::QWaylandWindow* mWaylandWindow = nullptr; - - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, bool, bEnabled, &ShortcutInhibitor::enabledChanged); - Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, QObject*, bWindowObject, &ShortcutInhibitor::windowChanged); - Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, QObject*, bBoundWindow, &ShortcutInhibitor::onBoundWindowChanged); - Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, impl::ShortcutsInhibitor*, bInhibitor, &ShortcutInhibitor::onInhibitorChanged); - Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, QWindow*, bWindow); - Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, QWindow*, bFocusedWindow); - Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, bool, bActive, &ShortcutInhibitor::activeChanged); - // clang-format on -}; - -} // namespace qs::wayland::shortcuts_inhibit diff --git a/src/wayland/shortcuts_inhibit/proto.cpp b/src/wayland/shortcuts_inhibit/proto.cpp deleted file mode 100644 index 8d35d5e..0000000 --- a/src/wayland/shortcuts_inhibit/proto.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include "proto.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../core/logcat.hpp" - -namespace qs::wayland::shortcuts_inhibit::impl { - -QS_LOGGING_CATEGORY(logShortcutsInhibit, "quickshell.wayland.shortcuts_inhibit", QtWarningMsg); - -ShortcutsInhibitManager::ShortcutsInhibitManager(): QWaylandClientExtensionTemplate(1) { - this->initialize(); -} - -ShortcutsInhibitManager* ShortcutsInhibitManager::instance() { - static auto* instance = new ShortcutsInhibitManager(); // NOLINT - return instance->isInitialized() ? instance : nullptr; -} - -ShortcutsInhibitor* -ShortcutsInhibitManager::createShortcutsInhibitor(QtWaylandClient::QWaylandWindow* surface) { - auto* display = QtWaylandClient::QWaylandIntegration::instance()->display(); - auto* inputDevice = display->lastInputDevice(); - if (inputDevice == nullptr) inputDevice = display->defaultInputDevice(); - - if (inputDevice == nullptr) { - qCCritical(logShortcutsInhibit) << "Could not create shortcuts inhibitor: No seat."; - return nullptr; - } - - auto* wlSurface = surface->surface(); - - if (this->inhibitors.contains(wlSurface)) { - auto& pair = this->inhibitors[wlSurface]; - pair.second++; - qCDebug(logShortcutsInhibit) << "Reusing existing inhibitor" << pair.first << "for surface" - << wlSurface << "- refcount:" << pair.second; - return pair.first; - } - - auto* inhibitor = - new ShortcutsInhibitor(this->inhibit_shortcuts(wlSurface, inputDevice->object()), wlSurface); - this->inhibitors.insert(wlSurface, qMakePair(inhibitor, 1)); - qCDebug(logShortcutsInhibit) << "Created inhibitor" << inhibitor << "for surface" << wlSurface; - return inhibitor; -} - -void ShortcutsInhibitManager::unrefShortcutsInhibitor(ShortcutsInhibitor* inhibitor) { - if (!inhibitor) return; - - auto* surface = inhibitor->surface(); - if (!this->inhibitors.contains(surface)) return; - - auto& pair = this->inhibitors[surface]; - pair.second--; - qCDebug(logShortcutsInhibit) << "Decremented refcount for inhibitor" << inhibitor - << "- refcount:" << pair.second; - - if (pair.second <= 0) { - qCDebug(logShortcutsInhibit) << "Refcount reached 0, destroying inhibitor" << inhibitor; - this->inhibitors.remove(surface); - delete inhibitor; - } -} - -ShortcutsInhibitor::~ShortcutsInhibitor() { - qCDebug(logShortcutsInhibit) << "Destroying inhibitor" << this << "for surface" << this->mSurface; - if (this->isInitialized()) this->destroy(); -} - -void ShortcutsInhibitor::zwp_keyboard_shortcuts_inhibitor_v1_active() { - qCDebug(logShortcutsInhibit) << "Inhibitor became active" << this; - this->bActive = true; -} - -void ShortcutsInhibitor::zwp_keyboard_shortcuts_inhibitor_v1_inactive() { - qCDebug(logShortcutsInhibit) << "Inhibitor became inactive" << this; - this->bActive = false; -} - -} // namespace qs::wayland::shortcuts_inhibit::impl \ No newline at end of file diff --git a/src/wayland/shortcuts_inhibit/proto.hpp b/src/wayland/shortcuts_inhibit/proto.hpp deleted file mode 100644 index e79d5ca..0000000 --- a/src/wayland/shortcuts_inhibit/proto.hpp +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "../../core/logcat.hpp" -#include "wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h" - -namespace qs::wayland::shortcuts_inhibit::impl { - -QS_DECLARE_LOGGING_CATEGORY(logShortcutsInhibit); - -class ShortcutsInhibitor; - -class ShortcutsInhibitManager - : public QWaylandClientExtensionTemplate - , public QtWayland::zwp_keyboard_shortcuts_inhibit_manager_v1 { -public: - explicit ShortcutsInhibitManager(); - - ShortcutsInhibitor* createShortcutsInhibitor(QtWaylandClient::QWaylandWindow* surface); - void unrefShortcutsInhibitor(ShortcutsInhibitor* inhibitor); - - static ShortcutsInhibitManager* instance(); - -private: - QHash> inhibitors; -}; - -class ShortcutsInhibitor - : public QObject - , public QtWayland::zwp_keyboard_shortcuts_inhibitor_v1 { - Q_OBJECT; - -public: - explicit ShortcutsInhibitor(::zwp_keyboard_shortcuts_inhibitor_v1* inhibitor, wl_surface* surface) - : QtWayland::zwp_keyboard_shortcuts_inhibitor_v1(inhibitor) - , mSurface(surface) - , bActive(false) {} - - ~ShortcutsInhibitor() override; - Q_DISABLE_COPY_MOVE(ShortcutsInhibitor); - - [[nodiscard]] QBindable bindableActive() const { return &this->bActive; } - [[nodiscard]] bool isActive() const { return this->bActive; } - [[nodiscard]] wl_surface* surface() const { return this->mSurface; } - -signals: - void activeChanged(); - -protected: - void zwp_keyboard_shortcuts_inhibitor_v1_active() override; - void zwp_keyboard_shortcuts_inhibitor_v1_inactive() override; - -private: - wl_surface* mSurface; - Q_OBJECT_BINDABLE_PROPERTY(ShortcutsInhibitor, bool, bActive, &ShortcutsInhibitor::activeChanged); -}; - -} // namespace qs::wayland::shortcuts_inhibit::impl \ No newline at end of file diff --git a/src/wayland/shortcuts_inhibit/test/manual/test.qml b/src/wayland/shortcuts_inhibit/test/manual/test.qml deleted file mode 100644 index 1f64cbf..0000000 --- a/src/wayland/shortcuts_inhibit/test/manual/test.qml +++ /dev/null @@ -1,65 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import Quickshell -import Quickshell.Wayland - -Scope { - Timer { - id: toggleTimer - interval: 100 - onTriggered: windowLoader.active = true - } - - LazyLoader { - id: windowLoader - active: true - - property bool enabled: false - - FloatingWindow { - id: w - color: contentItem.palette.window - - ColumnLayout { - anchors.centerIn: parent - - CheckBox { - id: loadedCb - text: "Loaded" - checked: true - } - - CheckBox { - id: enabledCb - text: "Enabled" - checked: windowLoader.enabled - onCheckedChanged: windowLoader.enabled = checked - } - - Label { - text: `Active: ${inhibitorLoader.item?.active ?? false}` - } - - Button { - text: "Toggle Window" - onClicked: { - windowLoader.active = false; - toggleTimer.start(); - } - } - } - - LazyLoader { - id: inhibitorLoader - active: loadedCb.checked - - ShortcutInhibitor { - window: w - enabled: enabledCb.checked - onCancelled: enabledCb.checked = false - } - } - } - } -} diff --git a/src/wayland/toplevel_management/manager.hpp b/src/wayland/toplevel_management/manager.hpp index 83e3e09..4b906a5 100644 --- a/src/wayland/toplevel_management/manager.hpp +++ b/src/wayland/toplevel_management/manager.hpp @@ -33,8 +33,8 @@ signals: protected: explicit ToplevelManager(); - void - zwlr_foreign_toplevel_manager_v1_toplevel(::zwlr_foreign_toplevel_handle_v1* toplevel) override; + void zwlr_foreign_toplevel_manager_v1_toplevel(::zwlr_foreign_toplevel_handle_v1* toplevel + ) override; private slots: void onToplevelReady(); diff --git a/src/wayland/toplevel_management/qml.cpp b/src/wayland/toplevel_management/qml.cpp index cb53381..0eae3de 100644 --- a/src/wayland/toplevel_management/qml.cpp +++ b/src/wayland/toplevel_management/qml.cpp @@ -9,6 +9,7 @@ #include "../../core/qmlscreen.hpp" #include "../../core/util.hpp" #include "../../window/proxywindow.hpp" +#include "../../window/windowinterface.hpp" #include "../output_tracking.hpp" #include "handle.hpp" #include "manager.hpp" @@ -72,7 +73,13 @@ void Toplevel::fullscreenOn(QuickshellScreenInfo* screen) { } void Toplevel::setRectangle(QObject* window, QRect rect) { - auto* proxyWindow = ProxyWindowBase::forObject(window); + auto* proxyWindow = qobject_cast(window); + + if (proxyWindow == nullptr) { + if (auto* iface = qobject_cast(window)) { + proxyWindow = iface->proxyWindow(); + } + } if (proxyWindow != this->rectWindow) { if (this->rectWindow != nullptr) { @@ -154,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 2664a99..0000000 --- a/src/wayland/wl_proxy_safe_deref.cpp +++ /dev/null @@ -1,41 +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/shell_integration.hpp b/src/wayland/wlr_layershell/shell_integration.hpp index 93cda01..e92b7c6 100644 --- a/src/wayland/wlr_layershell/shell_integration.hpp +++ b/src/wayland/wlr_layershell/shell_integration.hpp @@ -15,8 +15,8 @@ public: ~LayerShellIntegration() override; Q_DISABLE_COPY_MOVE(LayerShellIntegration); - QtWaylandClient::QWaylandShellSurface* - createShellSurface(QtWaylandClient::QWaylandWindow* window) override; + QtWaylandClient::QWaylandShellSurface* createShellSurface(QtWaylandClient::QWaylandWindow* window + ) override; }; } // namespace qs::wayland::layershell diff --git a/src/wayland/wlr_layershell/surface.cpp b/src/wayland/wlr_layershell/surface.cpp index 0b0e7d7..26d7558 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" @@ -31,8 +30,8 @@ namespace qs::wayland::layershell { namespace { -[[nodiscard]] QtWayland::zwlr_layer_shell_v1::layer -toWaylandLayer(const WlrLayer::Enum& layer) noexcept { +[[nodiscard]] QtWayland::zwlr_layer_shell_v1::layer toWaylandLayer(const WlrLayer::Enum& layer +) noexcept { switch (layer) { case WlrLayer::Background: return QtWayland::zwlr_layer_shell_v1::layer_background; case WlrLayer::Bottom: return QtWayland::zwlr_layer_shell_v1::layer_bottom; @@ -43,8 +42,8 @@ toWaylandLayer(const WlrLayer::Enum& layer) noexcept { return QtWayland::zwlr_layer_shell_v1::layer_top; } -[[nodiscard]] QtWayland::zwlr_layer_surface_v1::anchor -toWaylandAnchors(const Anchors& anchors) noexcept { +[[nodiscard]] QtWayland::zwlr_layer_surface_v1::anchor toWaylandAnchors(const Anchors& anchors +) noexcept { quint32 wl = 0; if (anchors.mLeft) wl |= QtWayland::zwlr_layer_surface_v1::anchor_left; if (anchors.mRight) wl |= QtWayland::zwlr_layer_surface_v1::anchor_right; @@ -144,11 +143,11 @@ LayerSurface::LayerSurface(LayerShellIntegration* shell, QtWaylandClient::QWayla auto* waylandScreen = dynamic_cast(qwindow->screen()->handle()); - if (waylandScreen != nullptr && !waylandScreen->isPlaceholder() && waylandScreen->output()) { + if (waylandScreen != nullptr) { output = waylandScreen->output(); } else { - qWarning() - << "Layershell screen does not correspond to a real screen. Letting the compositor pick."; + qWarning( + ) << "Layershell screen does not corrospond to a real screen. Letting the compositor pick."; } } @@ -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/wayland/wlr_layershell/wlr_layershell.cpp b/src/wayland/wlr_layershell/wlr_layershell.cpp index 947c51a..2b77690 100644 --- a/src/wayland/wlr_layershell/wlr_layershell.cpp +++ b/src/wayland/wlr_layershell/wlr_layershell.cpp @@ -28,10 +28,9 @@ WlrLayershell::WlrLayershell(QObject* parent): ProxyWindowBase(parent) { case Qt::BottomEdge: return this->bImplicitHeight + margins.top; case Qt::LeftEdge: return this->bImplicitWidth + margins.right; case Qt::RightEdge: return this->bImplicitWidth + margins.left; + default: return 0; } } - - return 0; }); this->bcExclusionEdge.setBinding([this] { return this->bAnchors.value().exclusionEdge(); }); diff --git a/src/widgets/ClippingRectangle.qml b/src/widgets/ClippingRectangle.qml index 604f346..86fe601 100644 --- a/src/widgets/ClippingRectangle.qml +++ b/src/widgets/ClippingRectangle.qml @@ -1,5 +1,3 @@ -pragma ComponentBehavior: Bound - import QtQuick ///! Rectangle capable of clipping content inside its border. @@ -74,12 +72,6 @@ Item { } } - ShaderEffectSource { - id: shaderSource - hideSource: true - sourceItem: contentItemContainer - } - ShaderEffect { id: shader anchors.fill: root @@ -87,6 +79,10 @@ Item { property Rectangle rect: rectangle property color backgroundColor: "white" property color borderColor: root.border.color - property ShaderEffectSource content: shaderSource + + property ShaderEffectSource content: ShaderEffectSource { + hideSource: true + sourceItem: contentItemContainer + } } } diff --git a/src/widgets/marginwrapper.cpp b/src/widgets/marginwrapper.cpp index b7d410c..9960bba 100644 --- a/src/widgets/marginwrapper.cpp +++ b/src/widgets/marginwrapper.cpp @@ -12,8 +12,8 @@ namespace qs::widgets { MarginWrapperManager::MarginWrapperManager(QObject* parent): WrapperManager(parent) { this->bTopMargin.setBinding([this] { return this->bExtraMargin - + (this->bOverrides.value().testFlag(TopMargin) ? this->bTopMarginOverride - : this->bMargin); + + (this->bOverrides.value().testFlag(TopMargin) ? this->bTopMarginOverride : this->bMargin + ); }); this->bBottomMargin.setBinding([this] { diff --git a/src/window/floatingwindow.cpp b/src/window/floatingwindow.cpp index 7a46bbf..0b9e9b1 100644 --- a/src/window/floatingwindow.cpp +++ b/src/window/floatingwindow.cpp @@ -1,37 +1,14 @@ #include "floatingwindow.hpp" -#include #include #include -#include +#include #include #include -#include #include "proxywindow.hpp" #include "windowinterface.hpp" -ProxyFloatingWindow::ProxyFloatingWindow(QObject* parent): ProxyWindowBase(parent) { - this->bTargetVisible.setBinding([this] { - if (!this->bWantsVisible) return false; - auto* parent = this->bParentProxyWindow.value(); - if (!parent) return true; - return parent->bindableBackerVisibility().value(); - }); -} - -void ProxyFloatingWindow::targetVisibleChanged() { - if (this->window && this->bParentProxyWindow) { - auto* bw = this->bParentProxyWindow.value()->backingWindow(); - - if (bw != this->window->transientParent()) { - this->window->setTransientParent(bw); - } - } - - this->ProxyWindowBase::setVisible(this->bTargetVisible); -} - void ProxyFloatingWindow::connectWindow() { this->ProxyWindowBase::connectWindow(); @@ -40,25 +17,6 @@ void ProxyFloatingWindow::connectWindow() { this->window->setMaximumSize(this->bMaximumSize); } -void ProxyFloatingWindow::completeWindow() { - this->ProxyWindowBase::completeWindow(); - - auto* parent = this->bParentProxyWindow.value(); - this->window->setTransientParent(parent ? parent->backingWindow() : nullptr); -} - -void ProxyFloatingWindow::postCompleteWindow() { - this->ProxyWindowBase::setVisible(this->bTargetVisible); -} - -void ProxyFloatingWindow::onParentDestroyed() { - this->mParentWindow = nullptr; - this->bParentProxyWindow = nullptr; - emit this->parentWindowChanged(); -} - -void ProxyFloatingWindow::setVisible(bool visible) { this->bWantsVisible = visible; } - void ProxyFloatingWindow::trySetWidth(qint32 implicitWidth) { if (!this->window->isVisible()) { this->ProxyWindowBase::trySetWidth(implicitWidth); @@ -86,42 +44,6 @@ void ProxyFloatingWindow::onMaximumSizeChanged() { emit this->maximumSizeChanged(); } -QObject* ProxyFloatingWindow::parentWindow() const { return this->mParentWindow; } - -void ProxyFloatingWindow::setParentWindow(QObject* window) { - if (window == this->mParentWindow) return; - - if (this->window && this->window->isVisible()) { - qmlWarning(this) << "parentWindow cannot be changed after the window is visible."; - return; - } - - if (this->bParentProxyWindow) { - QObject::disconnect(this->bParentProxyWindow, nullptr, this, nullptr); - } - - if (this->mParentWindow) { - QObject::disconnect(this->mParentWindow, nullptr, this, nullptr); - } - - this->mParentWindow = nullptr; - this->bParentProxyWindow = nullptr; - - if (auto* proxy = ProxyWindowBase::forObject(window)) { - this->mParentWindow = window; - this->bParentProxyWindow = proxy; - - QObject::connect( - this->mParentWindow, - &QObject::destroyed, - this, - &ProxyFloatingWindow::onParentDestroyed - ); - } - - emit this->parentWindowChanged(); -} - // FloatingWindowInterface FloatingWindowInterface::FloatingWindowInterface(QObject* parent) @@ -133,8 +55,6 @@ FloatingWindowInterface::FloatingWindowInterface(QObject* parent) QObject::connect(this->window, &ProxyFloatingWindow::titleChanged, this, &FloatingWindowInterface::titleChanged); QObject::connect(this->window, &ProxyFloatingWindow::minimumSizeChanged, this, &FloatingWindowInterface::minimumSizeChanged); QObject::connect(this->window, &ProxyFloatingWindow::maximumSizeChanged, this, &FloatingWindowInterface::maximumSizeChanged); - QObject::connect(this->window, &ProxyFloatingWindow::parentWindowChanged, this, &FloatingWindowInterface::parentWindowChanged); - QObject::connect(this->window, &ProxyWindowBase::windowConnected, this, &FloatingWindowInterface::onWindowConnected); // clang-format on } @@ -146,109 +66,3 @@ void FloatingWindowInterface::onReload(QObject* oldInstance) { } ProxyWindowBase* FloatingWindowInterface::proxyWindow() const { return this->window; } - -void FloatingWindowInterface::onWindowConnected() { - auto* qw = this->window->backingWindow(); - if (qw) { - QObject::connect( - qw, - &QWindow::windowStateChanged, - this, - &FloatingWindowInterface::onWindowStateChanged - ); - this->setMinimized(this->mMinimized); - this->setMaximized(this->mMaximized); - this->setFullscreen(this->mFullscreen); - this->onWindowStateChanged(); - } -} - -void FloatingWindowInterface::onWindowStateChanged() { - auto* qw = this->window->backingWindow(); - auto states = qw ? qw->windowStates() : Qt::WindowStates(); - - auto minimized = states.testFlag(Qt::WindowMinimized); - auto maximized = states.testFlag(Qt::WindowMaximized); - auto fullscreen = states.testFlag(Qt::WindowFullScreen); - - if (minimized != this->mWasMinimized) { - this->mWasMinimized = minimized; - emit this->minimizedChanged(); - } - - if (maximized != this->mWasMaximized) { - this->mWasMaximized = maximized; - emit this->maximizedChanged(); - } - - if (fullscreen != this->mWasFullscreen) { - this->mWasFullscreen = fullscreen; - emit this->fullscreenChanged(); - } -} - -bool FloatingWindowInterface::isMinimized() const { - auto* qw = this->window->backingWindow(); - if (!qw) return this->mWasMinimized; - return qw->windowStates().testFlag(Qt::WindowMinimized); -} - -void FloatingWindowInterface::setMinimized(bool minimized) { - this->mMinimized = minimized; - - if (auto* qw = this->window->backingWindow()) { - auto states = qw->windowStates(); - states.setFlag(Qt::WindowMinimized, minimized); - qw->setWindowStates(states); - } -} - -bool FloatingWindowInterface::isMaximized() const { - auto* qw = this->window->backingWindow(); - if (!qw) return this->mWasMaximized; - return qw->windowStates().testFlag(Qt::WindowMaximized); -} - -void FloatingWindowInterface::setMaximized(bool maximized) { - this->mMaximized = maximized; - - if (auto* qw = this->window->backingWindow()) { - auto states = qw->windowStates(); - states.setFlag(Qt::WindowMaximized, maximized); - qw->setWindowStates(states); - } -} - -bool FloatingWindowInterface::isFullscreen() const { - auto* qw = this->window->backingWindow(); - if (!qw) return this->mWasFullscreen; - return qw->windowStates().testFlag(Qt::WindowFullScreen); -} - -void FloatingWindowInterface::setFullscreen(bool fullscreen) { - this->mFullscreen = fullscreen; - - if (auto* qw = this->window->backingWindow()) { - auto states = qw->windowStates(); - states.setFlag(Qt::WindowFullScreen, fullscreen); - qw->setWindowStates(states); - } -} - -bool FloatingWindowInterface::startSystemMove() const { - auto* qw = this->window->backingWindow(); - if (!qw) return false; - return qw->startSystemMove(); -} - -bool FloatingWindowInterface::startSystemResize(Qt::Edges edges) const { - auto* qw = this->window->backingWindow(); - if (!qw) return false; - return qw->startSystemResize(edges); -} - -QObject* FloatingWindowInterface::parentWindow() const { return this->window->parentWindow(); } - -void FloatingWindowInterface::setParentWindow(QObject* window) { - this->window->setParentWindow(window); -} diff --git a/src/window/floatingwindow.hpp b/src/window/floatingwindow.hpp index e9e536a..f9cd5ce 100644 --- a/src/window/floatingwindow.hpp +++ b/src/window/floatingwindow.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -16,15 +15,9 @@ class ProxyFloatingWindow: public ProxyWindowBase { Q_OBJECT; public: - explicit ProxyFloatingWindow(QObject* parent = nullptr); + explicit ProxyFloatingWindow(QObject* parent = nullptr): ProxyWindowBase(parent) {} void connectWindow() override; - void completeWindow() override; - void postCompleteWindow() override; - void setVisible(bool visible) override; - - [[nodiscard]] QObject* parentWindow() const; - void setParentWindow(QObject* window); // Setting geometry while the window is visible makes the content item shrink but not the window // which is awful so we disable it for floating windows. @@ -35,28 +28,11 @@ signals: void minimumSizeChanged(); void maximumSizeChanged(); void titleChanged(); - void parentWindowChanged(); - -private slots: - void onParentDestroyed(); private: void onMinimumSizeChanged(); void onMaximumSizeChanged(); void onTitleChanged(); - void targetVisibleChanged(); - - QObject* mParentWindow = nullptr; - - Q_OBJECT_BINDABLE_PROPERTY(ProxyFloatingWindow, ProxyWindowBase*, bParentProxyWindow); - Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(ProxyFloatingWindow, bool, bWantsVisible, true); - - Q_OBJECT_BINDABLE_PROPERTY( - ProxyFloatingWindow, - bool, - bTargetVisible, - &ProxyFloatingWindow::targetVisibleChanged - ); public: Q_OBJECT_BINDABLE_PROPERTY( @@ -92,17 +68,6 @@ class FloatingWindowInterface: public WindowInterface { Q_PROPERTY(QSize minimumSize READ default WRITE default NOTIFY minimumSizeChanged BINDABLE bindableMinimumSize); /// Maximum window size given to the window system. Q_PROPERTY(QSize maximumSize READ default WRITE default NOTIFY maximumSizeChanged BINDABLE bindableMaximumSize); - /// Whether the window is currently minimized. - Q_PROPERTY(bool minimized READ isMinimized WRITE setMinimized NOTIFY minimizedChanged); - /// Whether the window is currently maximized. - Q_PROPERTY(bool maximized READ isMaximized WRITE setMaximized NOTIFY maximizedChanged); - /// Whether the window is currently fullscreen. - Q_PROPERTY(bool fullscreen READ isFullscreen WRITE setFullscreen NOTIFY fullscreenChanged); - /// The parent window of this window. Setting this makes the window a child of the parent, - /// which affects window stacking behavior. - /// - /// > [!NOTE] This property cannot be changed after the window is visible. - Q_PROPERTY(QObject* parentWindow READ parentWindow WRITE setParentWindow NOTIFY parentWindowChanged); // clang-format on QML_NAMED_ELEMENT(FloatingWindow); @@ -113,44 +78,15 @@ public: [[nodiscard]] ProxyWindowBase* proxyWindow() const override; - [[nodiscard]] QBindable bindableMinimumSize() { return &this->window->bMinimumSize; } - [[nodiscard]] QBindable bindableMaximumSize() { return &this->window->bMaximumSize; } - [[nodiscard]] QBindable bindableTitle() { return &this->window->bTitle; } - - [[nodiscard]] bool isMinimized() const; - void setMinimized(bool minimized); - [[nodiscard]] bool isMaximized() const; - void setMaximized(bool maximized); - [[nodiscard]] bool isFullscreen() const; - void setFullscreen(bool fullscreen); - - /// Start a system move operation. Must be called during a pointer press/drag. - Q_INVOKABLE [[nodiscard]] bool startSystemMove() const; - /// Start a system resize operation. Must be called during a pointer press/drag. - Q_INVOKABLE [[nodiscard]] bool startSystemResize(Qt::Edges edges) const; - - [[nodiscard]] QObject* parentWindow() const; - void setParentWindow(QObject* window); + QBindable bindableMinimumSize() { return &this->window->bMinimumSize; } + QBindable bindableMaximumSize() { return &this->window->bMaximumSize; } + QBindable bindableTitle() { return &this->window->bTitle; } signals: void minimumSizeChanged(); void maximumSizeChanged(); void titleChanged(); - void minimizedChanged(); - void maximizedChanged(); - void fullscreenChanged(); - void parentWindowChanged(); - -private slots: - void onWindowConnected(); - void onWindowStateChanged(); private: ProxyFloatingWindow* window; - bool mMinimized = false; - bool mMaximized = false; - bool mFullscreen = false; - bool mWasMinimized = false; - bool mWasMaximized = false; - bool mWasFullscreen = false; }; diff --git a/src/window/popupwindow.cpp b/src/window/popupwindow.cpp index bfe261e..ec2be7e 100644 --- a/src/window/popupwindow.cpp +++ b/src/window/popupwindow.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -13,74 +12,29 @@ ProxyPopupWindow::ProxyPopupWindow(QObject* parent): ProxyWindowBase(parent) { this->mVisible = false; - // clang-format off - QObject::connect(&this->mAnchor, &PopupAnchor::windowChanged, this, &ProxyPopupWindow::onParentWindowChanged); + QObject::connect(&this->mAnchor, &PopupAnchor::windowChanged, this, &ProxyPopupWindow::parentWindowChanged); QObject::connect(&this->mAnchor, &PopupAnchor::windowRectChanged, this, &ProxyPopupWindow::reposition); QObject::connect(&this->mAnchor, &PopupAnchor::edgesChanged, this, &ProxyPopupWindow::reposition); QObject::connect(&this->mAnchor, &PopupAnchor::gravityChanged, this, &ProxyPopupWindow::reposition); QObject::connect(&this->mAnchor, &PopupAnchor::adjustmentChanged, this, &ProxyPopupWindow::reposition); + QObject::connect(&this->mAnchor, &PopupAnchor::backingWindowVisibilityChanged, this, &ProxyPopupWindow::onParentUpdated); // clang-format on - - this->bTargetVisible.setBinding([this] { - auto* window = this->mAnchor.bindableProxyWindow().value(); - - if (window == this) { - qmlWarning(this) << "Anchor assigned to current window"; - return false; - } - - if (!window) return false; - - if (!this->bWantsVisible) return false; - return window->bindableBackerVisibility().value(); - }); -} - -void ProxyPopupWindow::targetVisibleChanged() { - this->ProxyWindowBase::setVisible(this->bTargetVisible); } void ProxyPopupWindow::completeWindow() { this->ProxyWindowBase::completeWindow(); // clang-format off - QObject::connect(this, &ProxyWindowBase::closed, this, &ProxyPopupWindow::onClosed); + QObject::connect(this->window, &QWindow::visibleChanged, this, &ProxyPopupWindow::onVisibleChanged); QObject::connect(this->window, &QWindow::widthChanged, this, &ProxyPopupWindow::reposition); QObject::connect(this->window, &QWindow::heightChanged, this, &ProxyPopupWindow::reposition); // clang-format on - auto* bw = this->mAnchor.backingWindow(); - - if (bw && PopupPositioner::instance()->shouldRepositionOnMove()) { - QObject::connect(bw, &QWindow::xChanged, this, &ProxyPopupWindow::reposition); - QObject::connect(bw, &QWindow::yChanged, this, &ProxyPopupWindow::reposition); - QObject::connect(bw, &QWindow::widthChanged, this, &ProxyPopupWindow::reposition); - QObject::connect(bw, &QWindow::heightChanged, this, &ProxyPopupWindow::reposition); - } - - this->window->setTransientParent(bw); - this->window->setFlag(this->bWantsGrab ? Qt::Popup : Qt::ToolTip); - - this->mAnchor.markDirty(); - PopupPositioner::instance()->reposition(&this->mAnchor, this->window); + this->window->setFlag(Qt::ToolTip); } -void ProxyPopupWindow::postCompleteWindow() { - this->ProxyWindowBase::setVisible(this->bTargetVisible); -} - -void ProxyPopupWindow::onClosed() { this->bWantsVisible = false; } - -void ProxyPopupWindow::onParentWindowChanged() { - // recreate for new parent - if (this->bTargetVisible && this->isVisibleDirect()) { - this->ProxyWindowBase::setVisibleDirect(false); - this->ProxyWindowBase::setVisibleDirect(true); - } - - emit this->parentWindowChanged(); -} +void ProxyPopupWindow::postCompleteWindow() { this->updateTransientParent(); } void ProxyPopupWindow::setParentWindow(QObject* parent) { qmlWarning(this) << "PopupWindow.parentWindow is deprecated. Use PopupWindow.anchor.window."; @@ -89,13 +43,59 @@ void ProxyPopupWindow::setParentWindow(QObject* parent) { QObject* ProxyPopupWindow::parentWindow() const { return this->mAnchor.window(); } +void ProxyPopupWindow::updateTransientParent() { + auto* bw = this->mAnchor.backingWindow(); + + if (this->window != nullptr && bw != this->window->transientParent()) { + if (this->window->transientParent()) { + QObject::disconnect(this->window->transientParent(), nullptr, this, nullptr); + } + + if (bw && PopupPositioner::instance()->shouldRepositionOnMove()) { + QObject::connect(bw, &QWindow::xChanged, this, &ProxyPopupWindow::reposition); + QObject::connect(bw, &QWindow::yChanged, this, &ProxyPopupWindow::reposition); + QObject::connect(bw, &QWindow::widthChanged, this, &ProxyPopupWindow::reposition); + QObject::connect(bw, &QWindow::heightChanged, this, &ProxyPopupWindow::reposition); + } + + this->window->setTransientParent(bw); + } + + this->updateVisible(); +} + +void ProxyPopupWindow::onParentUpdated() { this->updateTransientParent(); } + void ProxyPopupWindow::setScreen(QuickshellScreenInfo* /*unused*/) { - qmlWarning( - this + qmlWarning(this ) << "Cannot set screen of popup window, as that is controlled by the parent window"; } -void ProxyPopupWindow::setVisible(bool visible) { this->bWantsVisible = visible; } +void ProxyPopupWindow::setVisible(bool visible) { + if (visible == this->wantsVisible) return; + this->wantsVisible = visible; + this->updateVisible(); +} + +void ProxyPopupWindow::updateVisible() { + auto target = this->wantsVisible && this->mAnchor.window() != nullptr + && this->mAnchor.proxyWindow()->isVisibleDirect(); + + if (target && this->window != nullptr && !this->window->isVisible()) { + PopupPositioner::instance()->reposition(&this->mAnchor, this->window); + } + + this->ProxyWindowBase::setVisible(target); +} + +void ProxyPopupWindow::onVisibleChanged() { + // If the window was made invisible without its parent becoming invisible + // the compositor probably destroyed it. Without this the window won't ever + // be able to become visible again. + if (this->window->transientParent() && this->window->transientParent()->isVisible()) { + this->wantsVisible = this->window->isVisible(); + } +} void ProxyPopupWindow::setRelativeX(qint32 x) { qmlWarning(this) << "PopupWindow.relativeX is deprecated. Use PopupWindow.anchor.rect.x."; @@ -143,5 +143,3 @@ void ProxyPopupWindow::onPolished() { } } } - -bool ProxyPopupWindow::deleteOnInvisible() const { return true; } diff --git a/src/window/popupwindow.hpp b/src/window/popupwindow.hpp index d95eac0..e00495c 100644 --- a/src/window/popupwindow.hpp +++ b/src/window/popupwindow.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include #include @@ -76,15 +75,6 @@ class ProxyPopupWindow: public ProxyWindowBase { /// /// The popup will not be shown until @@anchor is valid, regardless of this property. QSDOC_PROPERTY_OVERRIDE(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged); - /// If true, the popup window will be dismissed and @@visible will change to false - /// if the user clicks outside of the popup or it is otherwise closed. - /// - /// > [!WARNING] Changes to this property while the window is open will only take - /// > effect after the window is hidden and shown again. - /// - /// > [!NOTE] Under Hyprland, @@Quickshell.Hyprland.HyprlandFocusGrab provides more advanced - /// > functionality such as detecting clicks outside without closing the popup. - Q_PROPERTY(bool grabFocus READ default WRITE default NOTIFY grabFocusChanged BINDABLE bindableGrabFocus); /// The screen that the window currently occupies. /// /// This may be modified to move the window to the given screen. @@ -98,7 +88,6 @@ public: void completeWindow() override; void postCompleteWindow() override; void onPolished() override; - bool deleteOnInvisible() const override; void setScreen(QuickshellScreenInfo* screen) override; void setVisible(bool visible) override; @@ -112,37 +101,24 @@ public: [[nodiscard]] qint32 relativeY() const; void setRelativeY(qint32 y); - [[nodiscard]] QBindable bindableGrabFocus() { return &this->bWantsGrab; } - [[nodiscard]] PopupAnchor* anchor(); signals: void parentWindowChanged(); void relativeXChanged(); void relativeYChanged(); - void grabFocusChanged(); private slots: - void onParentWindowChanged(); - void onClosed(); + void onVisibleChanged(); + void onParentUpdated(); void reposition(); private: - void targetVisibleChanged(); - QQuickWindow* parentBackingWindow(); + void updateTransientParent(); + void updateVisible(); PopupAnchor mAnchor {this}; + bool wantsVisible = false; bool pendingReposition = false; - - Q_OBJECT_BINDABLE_PROPERTY(ProxyPopupWindow, bool, bWantsVisible); - - Q_OBJECT_BINDABLE_PROPERTY( - ProxyPopupWindow, - bool, - bTargetVisible, - &ProxyPopupWindow::targetVisibleChanged - ); - - Q_OBJECT_BINDABLE_PROPERTY(ProxyPopupWindow, bool, bWantsGrab); }; diff --git a/src/window/proxywindow.cpp b/src/window/proxywindow.cpp index 8a20dfa..618751a 100644 --- a/src/window/proxywindow.cpp +++ b/src/window/proxywindow.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -57,17 +56,10 @@ ProxyWindowBase::ProxyWindowBase(QObject* parent) ProxyWindowBase::~ProxyWindowBase() { this->deleteWindow(true); } -ProxyWindowBase* ProxyWindowBase::forObject(QObject* obj) { - if (auto* proxy = qobject_cast(obj)) return proxy; - if (auto* iface = qobject_cast(obj)) return iface->proxyWindow(); - return nullptr; -} - void ProxyWindowBase::onReload(QObject* oldInstance) { - if (this->mVisible) this->window = this->retrieveWindow(oldInstance); + this->window = this->retrieveWindow(oldInstance); auto wasVisible = this->window != nullptr && this->window->isVisible(); - - if (this->mVisible) this->ensureQWindow(); + this->ensureQWindow(); // The qml engine will leave the WindowInterface as owner of everything // nested in an item, so we have to make sure the interface's children @@ -84,21 +76,17 @@ void ProxyWindowBase::onReload(QObject* oldInstance) { Reloadable::reloadChildrenRecursive(this, oldInstance); - if (this->mVisible) { - this->connectWindow(); - this->completeWindow(); - } + this->connectWindow(); + this->completeWindow(); this->reloadComplete = true; - if (this->mVisible) { - emit this->windowConnected(); - this->postCompleteWindow(); + emit this->windowConnected(); + this->postCompleteWindow(); - if (wasVisible && this->isVisibleDirect()) { - this->bBackerVisibility = true; - this->onExposed(); - } + if (wasVisible && this->isVisibleDirect()) { + emit this->backerVisibilityChanged(); + this->onExposed(); } } @@ -154,15 +142,6 @@ void ProxyWindowBase::ensureQWindow() { this->window = nullptr; // createQQuickWindow may indirectly reference this->window this->window = this->createQQuickWindow(); this->window->setFormat(format); - - // needed for vulkan dmabuf import, qt ignores these if not applicable - auto graphicsConfig = this->window->graphicsConfiguration(); - graphicsConfig.setDeviceExtensions({ - "VK_KHR_external_memory_fd", - "VK_EXT_external_memory_dma_buf", - "VK_EXT_image_drm_format_modifier", - }); - this->window->setGraphicsConfiguration(graphicsConfig); } void ProxyWindowBase::createWindow() { @@ -174,7 +153,13 @@ void ProxyWindowBase::createWindow() { void ProxyWindowBase::deleteWindow(bool keepItemOwnership) { if (this->window != nullptr) emit this->windowDestroyed(); - if (auto* window = this->disownWindow(keepItemOwnership)) window->deleteLater(); + if (auto* window = this->disownWindow(keepItemOwnership)) { + if (auto* generation = EngineGeneration::findObjectGeneration(this)) { + generation->deregisterIncubationController(window->incubationController()); + } + + window->deleteLater(); + } } ProxiedWindow* ProxyWindowBase::disownWindow(bool keepItemOwnership) { @@ -200,7 +185,7 @@ void ProxyWindowBase::connectWindow() { if (auto* generation = EngineGeneration::findObjectGeneration(this)) { // All windows have effectively the same incubation controller so it dosen't matter // which window it belongs to. We do want to replace the delay one though. - generation->trackWindowIncubationController(this->window); + generation->registerIncubationController(this->window->incubationController()); } this->window->setProxy(this); @@ -229,7 +214,6 @@ void ProxyWindowBase::completeWindow() { this->trySetHeight(this->implicitHeight()); this->setColor(this->mColor); this->updateMask(); - QQuickWindowPrivate::get(this->window)->updatesEnabled = this->mUpdatesEnabled; // notify initial / post-connection geometry emit this->xChanged(); @@ -294,27 +278,24 @@ void ProxyWindowBase::setVisible(bool visible) { void ProxyWindowBase::setVisibleDirect(bool visible) { if (this->deleteOnInvisible()) { + if (visible == this->isVisibleDirect()) return; + if (visible) { - if (visible == this->isVisibleDirect()) return; this->createWindow(); this->polishItems(); this->window->setVisible(true); - this->bBackerVisibility = true; + emit this->backerVisibilityChanged(); } else { - if (this->window != nullptr) this->window->setVisible(false); - this->bBackerVisibility = false; - this->deleteWindow(); - } - } else { - if (visible && this->window == nullptr) { - this->createWindow(); - } - - if (this->window != nullptr) { - if (visible) this->polishItems(); - this->window->setVisible(visible); - this->bBackerVisibility = visible; + if (this->window != nullptr) { + this->window->setVisible(false); + emit this->backerVisibilityChanged(); + this->deleteWindow(); + } } + } else if (this->window != nullptr) { + if (visible) this->polishItems(); + this->window->setVisible(visible); + emit this->backerVisibilityChanged(); } } @@ -486,19 +467,6 @@ void ProxyWindowBase::setSurfaceFormat(QsSurfaceFormat format) { emit this->surfaceFormatChanged(); } -bool ProxyWindowBase::updatesEnabled() const { return this->mUpdatesEnabled; } - -void ProxyWindowBase::setUpdatesEnabled(bool updatesEnabled) { - if (updatesEnabled == this->mUpdatesEnabled) return; - this->mUpdatesEnabled = updatesEnabled; - - if (this->window != nullptr) { - QQuickWindowPrivate::get(this->window)->updatesEnabled = updatesEnabled; - } - - emit this->updatesEnabledChanged(); -} - qreal ProxyWindowBase::devicePixelRatio() const { if (this->window != nullptr) return this->window->devicePixelRatio(); if (this->mScreen != nullptr) return this->mScreen->devicePixelRatio(); diff --git a/src/window/proxywindow.hpp b/src/window/proxywindow.hpp index 9ff66c4..025b970 100644 --- a/src/window/proxywindow.hpp +++ b/src/window/proxywindow.hpp @@ -57,7 +57,6 @@ class ProxyWindowBase: public Reloadable { Q_PROPERTY(QObject* windowTransform READ windowTransform NOTIFY windowTransformChanged); Q_PROPERTY(bool backingWindowVisible READ isVisibleDirect NOTIFY backerVisibilityChanged); Q_PROPERTY(QsSurfaceFormat surfaceFormat READ surfaceFormat WRITE setSurfaceFormat NOTIFY surfaceFormatChanged); - Q_PROPERTY(bool updatesEnabled READ updatesEnabled WRITE setUpdatesEnabled NOTIFY updatesEnabledChanged); Q_PROPERTY(QQmlListProperty data READ data); // clang-format on Q_CLASSINFO("DefaultProperty", "data"); @@ -66,8 +65,6 @@ public: explicit ProxyWindowBase(QObject* parent = nullptr); ~ProxyWindowBase() override; - static ProxyWindowBase* forObject(QObject* obj); - ProxyWindowBase(ProxyWindowBase&) = delete; ProxyWindowBase(ProxyWindowBase&&) = delete; void operator=(ProxyWindowBase&) = delete; @@ -104,10 +101,6 @@ public: virtual void setVisible(bool visible); virtual void setVisibleDirect(bool visible); - [[nodiscard]] QBindable bindableBackerVisibility() const { - return &this->bBackerVisibility; - } - void schedulePolish(); [[nodiscard]] virtual qint32 x() const; @@ -143,9 +136,6 @@ public: [[nodiscard]] QsSurfaceFormat surfaceFormat() const { return this->qsSurfaceFormat; } void setSurfaceFormat(QsSurfaceFormat format); - [[nodiscard]] bool updatesEnabled() const; - void setUpdatesEnabled(bool updatesEnabled); - [[nodiscard]] QObject* windowTransform() const { return nullptr; } // NOLINT [[nodiscard]] QQmlListProperty data(); @@ -169,7 +159,6 @@ signals: void colorChanged(); void maskChanged(); void surfaceFormatChanged(); - void updatesEnabledChanged(); void polished(); protected slots: @@ -194,7 +183,6 @@ protected: ProxyWindowContentItem* mContentItem = nullptr; bool reloadComplete = false; bool ranLints = false; - bool mUpdatesEnabled = true; QsSurfaceFormat qsSurfaceFormat; QSurfaceFormat mSurfaceFormat; @@ -218,13 +206,6 @@ protected: &ProxyWindowBase::implicitHeightChanged ); - Q_OBJECT_BINDABLE_PROPERTY( - ProxyWindowBase, - bool, - bBackerVisibility, - &ProxyWindowBase::backerVisibilityChanged - ); - private: void polishItems(); void updateMask(); diff --git a/src/window/test/manual/parentwindow.qml b/src/window/test/manual/parentwindow.qml deleted file mode 100644 index 214ee25..0000000 --- a/src/window/test/manual/parentwindow.qml +++ /dev/null @@ -1,42 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls.Fusion -import Quickshell - -Scope { - FloatingWindow { - id: control - color: contentItem.palette.window - ColumnLayout { - CheckBox { - id: parentCb - text: "Show parent" - } - - CheckBox { - id: dialogCb - text: "Show dialog" - } - } - } - - FloatingWindow { - id: parentw - Text { - text: "parent" - } - visible: parentCb.checked - color: contentItem.palette.window - - FloatingWindow { - id: dialog - parentWindow: parentw - visible: dialogCb.checked - color: contentItem.palette.window - - Text { - text: "dialog" - } - } - } -} diff --git a/src/window/test/popupwindow.cpp b/src/window/test/popupwindow.cpp index f9498d2..1262044 100644 --- a/src/window/test/popupwindow.cpp +++ b/src/window/test/popupwindow.cpp @@ -13,7 +13,7 @@ void TestPopupWindow::initiallyVisible() { // NOLINT auto parent = ProxyWindowBase(); auto popup = ProxyPopupWindow(); - popup.anchor()->setWindow(&parent); + popup.setParentWindow(&parent); popup.setVisible(true); parent.reload(); @@ -33,7 +33,7 @@ void TestPopupWindow::reloadReparent() { // NOLINT win2->setVisible(true); parent.setVisible(true); - popup.anchor()->setWindow(&parent); + popup.setParentWindow(&parent); popup.setVisible(true); parent.reload(); @@ -43,7 +43,7 @@ void TestPopupWindow::reloadReparent() { // NOLINT auto newParent = ProxyWindowBase(); auto newPopup = ProxyPopupWindow(); - newPopup.anchor()->setWindow(&newParent); + newPopup.setParentWindow(&newParent); newPopup.setVisible(true); auto* oldWindow = popup.backingWindow(); @@ -66,7 +66,7 @@ void TestPopupWindow::reloadUnparent() { // NOLINT auto parent = ProxyWindowBase(); auto popup = ProxyPopupWindow(); - popup.anchor()->setWindow(&parent); + popup.setParentWindow(&parent); popup.setVisible(true); parent.reload(); @@ -80,7 +80,8 @@ void TestPopupWindow::reloadUnparent() { // NOLINT newPopup.reload(&popup); QVERIFY(!newPopup.isVisible()); - QVERIFY(!newPopup.backingWindow() || !newPopup.backingWindow()->isVisible()); + QVERIFY(!newPopup.backingWindow()->isVisible()); + QCOMPARE(newPopup.backingWindow()->transientParent(), nullptr); } void TestPopupWindow::invisibleWithoutParent() { // NOLINT @@ -96,11 +97,9 @@ void TestPopupWindow::moveWithParent() { // NOLINT auto parent = ProxyWindowBase(); auto popup = ProxyPopupWindow(); - popup.anchor()->setWindow(&parent); - auto rect = popup.anchor()->rect(); - rect.x = 10; - rect.y = 10; - popup.anchor()->setRect(rect); + popup.setParentWindow(&parent); + popup.setRelativeX(10); + popup.setRelativeY(10); popup.setVisible(true); parent.reload(); @@ -127,7 +126,7 @@ void TestPopupWindow::attachParentLate() { // NOLINT QVERIFY(!popup.isVisible()); - popup.anchor()->setWindow(&parent); + popup.setParentWindow(&parent); QVERIFY(popup.isVisible()); QVERIFY(popup.backingWindow()->isVisible()); QCOMPARE(popup.backingWindow()->transientParent(), parent.backingWindow()); @@ -137,7 +136,7 @@ void TestPopupWindow::reparentLate() { // NOLINT auto parent = ProxyWindowBase(); auto popup = ProxyPopupWindow(); - popup.anchor()->setWindow(&parent); + popup.setParentWindow(&parent); popup.setVisible(true); parent.reload(); @@ -152,7 +151,7 @@ void TestPopupWindow::reparentLate() { // NOLINT parent2.backingWindow()->setX(10); parent2.backingWindow()->setY(10); - popup.anchor()->setWindow(&parent2); + popup.setParentWindow(&parent2); QVERIFY(popup.isVisible()); QVERIFY(popup.backingWindow()->isVisible()); QCOMPARE(popup.backingWindow()->transientParent(), parent2.backingWindow()); @@ -164,7 +163,7 @@ void TestPopupWindow::xMigrationFix() { // NOLINT auto parent = ProxyWindowBase(); auto popup = ProxyPopupWindow(); - popup.anchor()->setWindow(&parent); + popup.setParentWindow(&parent); popup.setVisible(true); parent.reload(); diff --git a/src/window/windowinterface.cpp b/src/window/windowinterface.cpp index e41afc2..8917f12 100644 --- a/src/window/windowinterface.cpp +++ b/src/window/windowinterface.cpp @@ -127,9 +127,6 @@ void WindowInterface::setMask(PendingRegion* mask) const { this->proxyWindow()-> QsSurfaceFormat WindowInterface::surfaceFormat() const { return this->proxyWindow()->surfaceFormat(); }; void WindowInterface::setSurfaceFormat(QsSurfaceFormat format) const { this->proxyWindow()->setSurfaceFormat(format); }; -bool WindowInterface::updatesEnabled() const { return this->proxyWindow()->updatesEnabled(); }; -void WindowInterface::setUpdatesEnabled(bool updatesEnabled) const { this->proxyWindow()->setUpdatesEnabled(updatesEnabled); }; - QQmlListProperty WindowInterface::data() const { return this->proxyWindow()->data(); }; // clang-format on @@ -151,7 +148,6 @@ void WindowInterface::connectSignals() const { QObject::connect(window, &ProxyWindowBase::colorChanged, this, &WindowInterface::colorChanged); QObject::connect(window, &ProxyWindowBase::maskChanged, this, &WindowInterface::maskChanged); QObject::connect(window, &ProxyWindowBase::surfaceFormatChanged, this, &WindowInterface::surfaceFormatChanged); - QObject::connect(window, &ProxyWindowBase::updatesEnabledChanged, this, &WindowInterface::updatesEnabledChanged); // clang-format on } diff --git a/src/window/windowinterface.hpp b/src/window/windowinterface.hpp index 6f3db20..9e917b9 100644 --- a/src/window/windowinterface.hpp +++ b/src/window/windowinterface.hpp @@ -143,12 +143,6 @@ class WindowInterface: public Reloadable { /// /// > [!NOTE] The surface format cannot be changed after the window is created. Q_PROPERTY(QsSurfaceFormat surfaceFormat READ surfaceFormat WRITE setSurfaceFormat NOTIFY surfaceFormatChanged); - /// If the window should receive render updates. Defaults to true. - /// - /// When set to false, the window will not re-render in response to animations - /// or other visual updates from other windows. This is useful for static windows - /// such as wallpapers that do not need to update frequently, saving GPU cycles. - Q_PROPERTY(bool updatesEnabled READ updatesEnabled WRITE setUpdatesEnabled NOTIFY updatesEnabledChanged); Q_PROPERTY(QQmlListProperty data READ data); // clang-format on Q_CLASSINFO("DefaultProperty", "data"); @@ -237,9 +231,6 @@ public: [[nodiscard]] QsSurfaceFormat surfaceFormat() const; void setSurfaceFormat(QsSurfaceFormat format) const; - [[nodiscard]] bool updatesEnabled() const; - void setUpdatesEnabled(bool updatesEnabled) const; - [[nodiscard]] QQmlListProperty data() const; static QsWindowAttached* qmlAttachedProperties(QObject* object); @@ -267,7 +258,6 @@ signals: void colorChanged(); void maskChanged(); void surfaceFormatChanged(); - void updatesEnabledChanged(); protected: void connectSignals() const; 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 511e8ec..0000000 --- a/src/windowmanager/windowmanager.cpp +++ /dev/null @@ -1,43 +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) { - if (!screen) return nullptr; - - 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..27a4484 100644 --- a/src/x11/i3/ipc/CMakeLists.txt +++ b/src/x11/i3/ipc/CMakeLists.txt @@ -3,8 +3,6 @@ qt_add_library(quickshell-i3-ipc STATIC qml.cpp workspace.cpp monitor.cpp - controller.cpp - listener.cpp ) qt_add_qml_module(quickshell-i3-ipc @@ -17,7 +15,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..ba010ed 100644 --- a/src/x11/i3/ipc/connection.cpp +++ b/src/x11/i3/ipc/connection.cpp @@ -1,4 +1,4 @@ -#include "connection.hpp" +#include #include #include #include @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -14,12 +15,19 @@ #include #include #include +#include #include +#include #include #include #include #include "../../../core/logcat.hpp" +#include "../../../core/model.hpp" +#include "../../../core/qmlscreen.hpp" +#include "connection.hpp" +#include "monitor.hpp" +#include "workspace.hpp" namespace qs::i3::ipc { @@ -28,6 +36,470 @@ QS_LOGGING_CATEGORY(logI3Ipc, "quickshell.I3.ipc", QtWarningMsg); QS_LOGGING_CATEGORY(logI3IpcEvents, "quickshell.I3.ipc.events", QtWarningMsg); } // namespace +void I3Ipc::makeRequest(const QByteArray& request) { + if (!this->valid) { + qCWarning(logI3IpcEvents) << "IPC connection is not open, ignoring request."; + return; + } + this->liveEventSocket.write(request); + this->liveEventSocket.flush(); +} + +void I3Ipc::dispatch(const QString& payload) { + auto message = I3Ipc::buildRequestMessage(EventCode::RunCommand, payload.toLocal8Bit()); + + this->makeRequest(message); +} + +QByteArray I3Ipc::buildRequestMessage(EventCode cmd, const QByteArray& payload) { + auto payloadLength = static_cast(payload.length()); + + auto type = QByteArray(std::bit_cast>(cmd).data(), 4); + auto len = QByteArray(std::bit_cast>(payloadLength).data(), 4); + + return MAGIC.data() + len + type + payload; +} + +I3Ipc::I3Ipc() { + auto sock = qEnvironmentVariable("I3SOCK"); + + if (sock.isEmpty()) { + qCWarning(logI3Ipc) << "$I3SOCK is unset. Trying $SWAYSOCK."; + + sock = qEnvironmentVariable("SWAYSOCK"); + + if (sock.isEmpty()) { + qCWarning(logI3Ipc) << "$SWAYSOCK and I3SOCK are unset. Cannot connect to socket."; + return; + } + } + + this->bFocusedWorkspace.setBinding([this]() -> I3Workspace* { + if (!this->bFocusedMonitor) return nullptr; + return this->bFocusedMonitor->bindableActiveWorkspace().value(); + }); + + this->mSocketPath = sock; + + // clang-format off + QObject::connect(&this->liveEventSocket, &QLocalSocket::errorOccurred, this, &I3Ipc::eventSocketError); + QObject::connect(&this->liveEventSocket, &QLocalSocket::stateChanged, this, &I3Ipc::eventSocketStateChanged); + 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)); + + this->liveEventSocket.connectToServer(this->mSocketPath); +} + +void I3Ipc::subscribe() { + auto payload = QByteArray(R"(["workspace","output"])"); + auto message = I3Ipc::buildRequestMessage(EventCode::Subscribe, payload); + + this->makeRequest(message); + + // Workspaces must be refreshed before monitors or no focus will be + // detected on launch. + this->refreshWorkspaces(); + this->refreshMonitors(); +} + +void I3Ipc::eventSocketReady() { + for (auto& [type, data]: this->parseResponse()) { + this->event.mCode = type; + this->event.mData = data; + + this->onEvent(&this->event); + emit this->rawEvent(&this->event); + } +} + +void I3Ipc::reconnectIPC() { + qCWarning(logI3Ipc) << "Fatal IPC error occured, recreating connection"; + this->liveEventSocket.disconnectFromServer(); + this->liveEventSocket.connectToServer(this->mSocketPath); +} + +QVector I3Ipc::parseResponse() { + QVector> events; + const int magicLen = 6; + + while (!this->liveEventSocketDs.atEnd()) { + this->liveEventSocketDs.startTransaction(); + this->liveEventSocketDs.startTransaction(); + + 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"; + break; + } + + // Importing this makes CI builds fail for some reason. + QJsonParseError e; // NOLINT (misc-include-cleaner) + + auto data = QJsonDocument::fromJson(payload, &e); + if (e.error != QJsonParseError::NoError) { + qCWarning(logI3Ipc) << "Invalid JSON value:" << e.errorString(); + break; + } else { + events.push_back(std::tuple(I3IpcEvent::intToEvent(type), data)); + } + } + + return events; +} + +void I3Ipc::eventSocketError(QLocalSocket::LocalSocketError error) const { + if (!this->valid) { + qCWarning(logI3Ipc) << "Unable to connect to I3 socket:" << error; + } else { + qCWarning(logI3Ipc) << "I3 socket error:" << error; + } +} + +void I3Ipc::eventSocketStateChanged(QLocalSocket::LocalSocketState state) { + if (state == QLocalSocket::ConnectedState) { + qCInfo(logI3Ipc) << "I3 event socket connected."; + emit this->connected(); + } else if (state == QLocalSocket::UnconnectedState && this->valid) { + qCWarning(logI3Ipc) << "I3 event socket disconnected."; + } + + this->valid = state == QLocalSocket::ConnectedState; +} + +QString I3Ipc::socketPath() const { return this->mSocketPath; } + +void I3Ipc::setFocusedMonitor(I3Monitor* monitor) { + auto* oldMonitor = this->bFocusedMonitor.value(); + if (monitor == oldMonitor) return; + + if (oldMonitor != nullptr) { + QObject::disconnect(oldMonitor, nullptr, this, nullptr); + } + + if (monitor != nullptr) { + QObject::connect(monitor, &QObject::destroyed, this, &I3Ipc::onFocusedMonitorDestroyed); + } + + this->bFocusedMonitor = monitor; +} + +void I3Ipc::onFocusedMonitorDestroyed() { this->bFocusedMonitor = nullptr; } + +I3Ipc* I3Ipc::instance() { + static I3Ipc* instance = nullptr; // NOLINT + + if (instance == nullptr) { + instance = new I3Ipc(); + } + + return instance; +} + +void I3Ipc::refreshWorkspaces() { + this->makeRequest(I3Ipc::buildRequestMessage(EventCode::GetWorkspaces)); +} + +void I3Ipc::handleGetWorkspacesEvent(I3IpcEvent* event) { + auto data = event->mData; + + auto workspaces = data.array(); + + const auto& mList = this->mWorkspaces.valueList(); + auto names = QVector(); + + qCDebug(logI3Ipc) << "There are" << workspaces.toVariantList().length() << "workspaces"; + for (auto entry: workspaces) { + auto object = entry.toObject().toVariantMap(); + auto name = object["name"].toString(); + + auto workspaceIter = std::ranges::find_if(mList, [name](I3Workspace* m) { + return m->bindableName().value() == name; + }); + + auto* workspace = workspaceIter == mList.end() ? nullptr : *workspaceIter; + auto existed = workspace != nullptr; + + if (workspace == nullptr) { + workspace = new I3Workspace(this); + } + + workspace->updateFromObject(object); + + if (!existed) { + this->mWorkspaces.insertObjectSorted(workspace, &I3Ipc::compareWorkspaces); + } + + if (!this->bFocusedWorkspace && object.value("focused").value()) { + this->bFocusedMonitor = workspace->bindableMonitor().value(); + } + + names.push_back(name); + } + + auto removedWorkspaces = QVector(); + + for (auto* workspace: mList) { + if (!names.contains(workspace->bindableName().value())) { + removedWorkspaces.push_back(workspace); + } + } + + qCDebug(logI3Ipc) << "Removing" << removedWorkspaces.length() << "deleted workspaces."; + + for (auto* workspace: removedWorkspaces) { + this->mWorkspaces.removeObject(workspace); + delete workspace; + } +} + +void I3Ipc::refreshMonitors() { + this->makeRequest(I3Ipc::buildRequestMessage(EventCode::GetOutputs)); +} + +void I3Ipc::handleGetOutputsEvent(I3IpcEvent* event) { + auto data = event->mData; + + auto monitors = data.array(); + const auto& mList = this->mMonitors.valueList(); + auto names = QVector(); + + qCDebug(logI3Ipc) << "There are" << monitors.toVariantList().length() << "monitors"; + + for (auto elem: monitors) { + auto object = elem.toObject().toVariantMap(); + auto name = object["name"].toString(); + + auto monitorIter = std::ranges::find_if(mList, [name](I3Monitor* m) { + return m->bindableName().value() == name; + }); + + auto* monitor = monitorIter == mList.end() ? nullptr : *monitorIter; + auto existed = monitor != nullptr; + + if (monitor == nullptr) { + monitor = new I3Monitor(this); + } + + monitor->updateFromObject(object); + + if (monitor->bindableFocused().value()) { + this->setFocusedMonitor(monitor); + } + + if (!existed) { + this->mMonitors.insertObject(monitor); + } + + names.push_back(name); + } + + auto removedMonitors = QVector(); + + for (auto* monitor: mList) { + if (!names.contains(monitor->bindableName().value())) { + removedMonitors.push_back(monitor); + } + } + + qCDebug(logI3Ipc) << "Removing" << removedMonitors.length() << "disconnected monitors."; + + for (auto* monitor: removedMonitors) { + this->mMonitors.removeObject(monitor); + delete monitor; + } +} + +void I3Ipc::onEvent(I3IpcEvent* event) { + switch (event->mCode) { + case EventCode::Workspace: this->handleWorkspaceEvent(event); return; + case EventCode::Output: + /// I3 only sends an "unspecified" event, so we have to query the data changes ourselves + qCInfo(logI3Ipc) << "Refreshing Monitors..."; + this->refreshMonitors(); + return; + case EventCode::Subscribe: qCInfo(logI3Ipc) << "Connected to IPC"; return; + case EventCode::GetOutputs: this->handleGetOutputsEvent(event); return; + case EventCode::GetWorkspaces: this->handleGetWorkspacesEvent(event); return; + case EventCode::RunCommand: I3Ipc::handleRunCommand(event); return; + case EventCode::Unknown: + qCWarning(logI3Ipc) << "Unknown event:" << event->type() << event->data(); + return; + default: qCWarning(logI3Ipc) << "Unhandled event:" << event->type(); + } +} + +void I3Ipc::handleRunCommand(I3IpcEvent* event) { + for (auto r: event->mData.array()) { + auto obj = r.toObject(); + const bool success = obj["success"].toBool(); + + if (!success) { + const QString error = obj["error"].toString(); + qCWarning(logI3Ipc) << "Error occured while running command:" << error; + } + } +} + +void I3Ipc::handleWorkspaceEvent(I3IpcEvent* event) { + // If a workspace doesn't exist, and is being switch to, no focus change event is emited, + // only the init one, which does not contain the previously focused workspace + auto change = event->mData["change"]; + + if (change == "init") { + qCInfo(logI3IpcEvents) << "New workspace has been created"; + + auto workspaceData = event->mData["current"]; + + auto* workspace = this->findWorkspaceByID(workspaceData["id"].toInt(-1)); + auto existed = workspace != nullptr; + + if (!existed) { + workspace = new I3Workspace(this); + } + + if (workspaceData.isObject()) { + workspace->updateFromObject(workspaceData.toObject().toVariantMap()); + } + + if (!existed) { + this->mWorkspaces.insertObjectSorted(workspace, &I3Ipc::compareWorkspaces); + qCInfo(logI3Ipc) << "Added workspace" << workspace->bindableName().value() << "to list"; + } + } else if (change == "focus") { + auto oldData = event->mData["old"]; + auto newData = event->mData["current"]; + auto oldName = oldData["name"].toString(); + auto newName = newData["name"].toString(); + + qCInfo(logI3IpcEvents) << "Focus changed: " << oldName << "->" << newName; + + if (auto* oldWorkspace = this->findWorkspaceByName(oldName)) { + oldWorkspace->updateFromObject(oldData.toObject().toVariantMap()); + } + + auto* newWorkspace = this->findWorkspaceByName(newName); + + if (newWorkspace == nullptr) { + newWorkspace = new I3Workspace(this); + } + + newWorkspace->updateFromObject(newData.toObject().toVariantMap()); + + if (newWorkspace->bindableMonitor().value()) { + auto* monitor = newWorkspace->bindableMonitor().value(); + monitor->setFocusedWorkspace(newWorkspace); + this->bFocusedMonitor = monitor; + } + } else if (change == "empty") { + auto name = event->mData["current"]["name"].toString(); + + auto* oldWorkspace = this->findWorkspaceByName(name); + + if (oldWorkspace != nullptr) { + qCInfo(logI3Ipc) << "Deleting" << oldWorkspace->bindableId().value() << name; + + if (this->bFocusedWorkspace == oldWorkspace) { + this->bFocusedMonitor->setFocusedWorkspace(nullptr); + } + + this->workspaces()->removeObject(oldWorkspace); + + delete oldWorkspace; + } else { + qCInfo(logI3Ipc) << "Workspace" << name << "has already been deleted"; + } + } else if (change == "move" || change == "rename" || change == "urgent") { + auto name = event->mData["current"]["name"].toString(); + + auto* workspace = this->findWorkspaceByName(name); + + if (workspace != nullptr) { + auto data = event->mData["current"].toObject().toVariantMap(); + + workspace->updateFromObject(data); + } else { + qCWarning(logI3Ipc) << "Workspace" << name << "doesn't exist"; + } + } else if (change == "reload") { + qCInfo(logI3Ipc) << "Refreshing Workspaces..."; + this->refreshWorkspaces(); + } +} + +I3Monitor* I3Ipc::monitorFor(QuickshellScreenInfo* screen) { + if (screen == nullptr) return nullptr; + + return this->findMonitorByName(screen->name()); +} + +I3Workspace* I3Ipc::findWorkspaceByID(qint32 id) { + auto list = this->mWorkspaces.valueList(); + auto workspaceIter = + std::ranges::find_if(list, [id](I3Workspace* m) { return m->bindableId().value() == id; }); + + return workspaceIter == list.end() ? nullptr : *workspaceIter; +} + +I3Workspace* I3Ipc::findWorkspaceByName(const QString& name) { + auto list = this->mWorkspaces.valueList(); + auto workspaceIter = std::ranges::find_if(list, [name](I3Workspace* m) { + return m->bindableName().value() == name; + }); + + return workspaceIter == list.end() ? nullptr : *workspaceIter; +} + +I3Monitor* I3Ipc::findMonitorByName(const QString& name, bool createIfMissing) { + auto list = this->mMonitors.valueList(); + auto monitorIter = std::ranges::find_if(list, [name](I3Monitor* m) { + return m->bindableName().value() == name; + }); + + if (monitorIter != list.end()) { + return *monitorIter; + } else if (createIfMissing) { + qCDebug(logI3Ipc) << "Monitor" << name << "requested before creation, performing early init"; + auto* monitor = new I3Monitor(this); + monitor->updateInitial(name); + this->mMonitors.insertObject(monitor); + return monitor; + } else { + return nullptr; + } +} + +ObjectModel* I3Ipc::monitors() { return &this->mMonitors; } +ObjectModel* I3Ipc::workspaces() { return &this->mWorkspaces; } + +bool I3Ipc::compareWorkspaces(I3Workspace* a, I3Workspace* b) { + return a->bindableNumber().value() > b->bindableNumber().value(); +} + QString I3IpcEvent::type() const { return I3IpcEvent::eventToString(this->mCode); } QString I3IpcEvent::data() const { return QString::fromUtf8(this->mData.toJson()); } @@ -60,141 +532,8 @@ QString I3IpcEvent::eventToString(EventCode event) { case EventCode::BarStateUpdate: return "bar_state_update"; break; case EventCode::Input: return "input"; break; - default: return "unknown"; break; + case EventCode::Unknown: return "unknown"; break; } } -I3Ipc::I3Ipc(const QList& events): mEvents(events) { - auto sock = qEnvironmentVariable("I3SOCK"); - - if (sock.isEmpty()) { - qCWarning(logI3Ipc) << "$I3SOCK is unset. Trying $SWAYSOCK."; - - sock = qEnvironmentVariable("SWAYSOCK"); - - if (sock.isEmpty()) { - qCWarning(logI3Ipc) << "$SWAYSOCK and I3SOCK are unset. Cannot connect to socket."; - return; - } - } - - this->mSocketPath = sock; - - // clang-format off - QObject::connect(&this->liveEventSocket, &QLocalSocket::errorOccurred, this, &I3Ipc::eventSocketError); - QObject::connect(&this->liveEventSocket, &QLocalSocket::stateChanged, this, &I3Ipc::eventSocketStateChanged); - QObject::connect(&this->liveEventSocket, &QLocalSocket::readyRead, this, &I3Ipc::eventSocketReady); - QObject::connect(&this->liveEventSocket, &QLocalSocket::connected, this, &I3Ipc::subscribe); - // clang-format on -} - -void I3Ipc::makeRequest(const QByteArray& request) { - if (!this->valid) { - qCWarning(logI3IpcEvents) << "IPC connection is not open, ignoring request."; - return; - } - this->liveEventSocket.write(request); - this->liveEventSocket.flush(); -} - -void I3Ipc::dispatch(const QString& payload) { - auto message = I3Ipc::buildRequestMessage(EventCode::RunCommand, payload.toLocal8Bit()); - - this->makeRequest(message); -} - -QByteArray I3Ipc::buildRequestMessage(EventCode cmd, const QByteArray& payload) { - auto payloadLength = static_cast(payload.length()); - - auto type = QByteArray(std::bit_cast>(cmd).data(), 4); - auto len = QByteArray(std::bit_cast>(payloadLength).data(), 4); - - return MAGIC.data() + len + type + payload; -} - -void I3Ipc::subscribe() { - auto jsonArray = QJsonArray::fromStringList(this->mEvents); - auto jsonDoc = QJsonDocument(jsonArray); - auto payload = jsonDoc.toJson(QJsonDocument::Compact); - auto message = I3Ipc::buildRequestMessage(EventCode::Subscribe, payload); - - this->makeRequest(message); -} - -void I3Ipc::eventSocketReady() { - for (auto& [type, data]: this->parseResponse()) { - this->event.mCode = type; - this->event.mData = data; - - emit this->rawEvent(&this->event); - } -} - -void I3Ipc::connect() { this->liveEventSocket.connectToServer(this->mSocketPath); } - -void I3Ipc::reconnectIPC() { - qCWarning(logI3Ipc) << "Fatal IPC error occured, recreating connection"; - this->liveEventSocket.disconnectFromServer(); - this->connect(); -} - -QVector I3Ipc::parseResponse() { - QVector events; - - 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; - - if (magic.size() < 6 || strncmp(magic.data(), MAGIC.data(), 6) != 0) { - qCWarning(logI3Ipc) << "No magic sequence found in string."; - this->reconnectIPC(); - break; - } - - if (I3IpcEvent::intToEvent(type) == EventCode::Unknown) { - qCWarning(logI3Ipc) << "Received unknown event"; - break; - } - - // Importing this makes CI builds fail for some reason. - QJsonParseError e; // NOLINT (misc-include-cleaner) - - auto data = QJsonDocument::fromJson(payload, &e); - if (e.error != QJsonParseError::NoError) { - qCWarning(logI3Ipc) << "Invalid JSON value:" << e.errorString(); - break; - } else { - events.push_back(std::tuple(I3IpcEvent::intToEvent(type), data)); - } - } - - return events; -} - -void I3Ipc::eventSocketError(QLocalSocket::LocalSocketError error) const { - if (!this->valid) { - qCWarning(logI3Ipc) << "Unable to connect to I3 socket:" << error; - } else { - qCWarning(logI3Ipc) << "I3 socket error:" << error; - } -} - -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) { - qCWarning(logI3Ipc) << "I3 event socket disconnected."; - } - - this->valid = state == QLocalSocket::ConnectedState; -} - -QString I3Ipc::socketPath() const { return this->mSocketPath; } - } // namespace qs::i3::ipc diff --git a/src/x11/i3/ipc/connection.hpp b/src/x11/i3/ipc/connection.hpp index 7d03ecd..af480c5 100644 --- a/src/x11/i3/ipc/connection.hpp +++ b/src/x11/i3/ipc/connection.hpp @@ -2,13 +2,26 @@ #include #include +#include #include #include +#include +#include #include #include #include -#include "../../../core/streamreader.hpp" +#include "../../../core/model.hpp" +#include "../../../core/qmlscreen.hpp" + +namespace qs::i3::ipc { + +class I3Workspace; +class I3Monitor; +} // namespace qs::i3::ipc + +Q_DECLARE_OPAQUE_POINTER(qs::i3::ipc::I3Workspace*); +Q_DECLARE_OPAQUE_POINTER(qs::i3::ipc::I3Monitor*); namespace qs::i3::ipc { @@ -41,7 +54,9 @@ using Event = std::tuple; class I3IpcEvent: public QObject { Q_OBJECT; + /// The name of the event Q_PROPERTY(QString type READ type CONSTANT); + /// The payload of the event in JSON format. Q_PROPERTY(QString data READ data CONSTANT); QML_NAMED_ELEMENT(I3Event); @@ -60,48 +75,90 @@ public: static QString eventToString(EventCode event); }; -/// Base class that manages the IPC socket, subscriptions and event reception. class I3Ipc: public QObject { Q_OBJECT; public: - explicit I3Ipc(const QList& events); + static I3Ipc* instance(); [[nodiscard]] QString socketPath() const; void makeRequest(const QByteArray& request); void dispatch(const QString& payload); - void connect(); - [[nodiscard]] QByteArray static buildRequestMessage( - EventCode cmd, - const QByteArray& payload = QByteArray() - ); + static QByteArray buildRequestMessage(EventCode cmd, const QByteArray& payload = QByteArray()); + + I3Workspace* findWorkspaceByName(const QString& name); + I3Monitor* findMonitorByName(const QString& name, bool createIfMissing = false); + I3Workspace* findWorkspaceByID(qint32 id); + + void setFocusedMonitor(I3Monitor* monitor); + + void refreshWorkspaces(); + void refreshMonitors(); + + I3Monitor* monitorFor(QuickshellScreenInfo* screen); + + [[nodiscard]] QBindable bindableFocusedMonitor() const { + return &this->bFocusedMonitor; + }; + + [[nodiscard]] QBindable bindableFocusedWorkspace() const { + return &this->bFocusedWorkspace; + }; + + [[nodiscard]] ObjectModel* monitors(); + [[nodiscard]] ObjectModel* workspaces(); signals: void connected(); void rawEvent(I3IpcEvent* event); + void focusedWorkspaceChanged(); + void focusedMonitorChanged(); -protected slots: +private slots: void eventSocketError(QLocalSocket::LocalSocketError error) const; void eventSocketStateChanged(QLocalSocket::LocalSocketState state); void eventSocketReady(); void subscribe(); -protected: + void onFocusedMonitorDestroyed(); + +private: + explicit I3Ipc(); + + void onEvent(I3IpcEvent* event); + + void handleWorkspaceEvent(I3IpcEvent* event); + void handleGetWorkspacesEvent(I3IpcEvent* event); + void handleGetOutputsEvent(I3IpcEvent* event); + static void handleRunCommand(I3IpcEvent* event); + static bool compareWorkspaces(I3Workspace* a, I3Workspace* b); + void reconnectIPC(); + QVector> parseResponse(); QLocalSocket liveEventSocket; - StreamReader eventReader; + QDataStream liveEventSocketDs; QString mSocketPath; + bool valid = false; + ObjectModel mMonitors {this}; + ObjectModel mWorkspaces {this}; + I3IpcEvent event {this}; -private: - QList mEvents; + Q_OBJECT_BINDABLE_PROPERTY(I3Ipc, I3Monitor*, bFocusedMonitor, &I3Ipc::focusedMonitorChanged); + + Q_OBJECT_BINDABLE_PROPERTY( + I3Ipc, + I3Workspace*, + bFocusedWorkspace, + &I3Ipc::focusedWorkspaceChanged + ); }; } // namespace qs::i3::ipc diff --git a/src/x11/i3/ipc/controller.cpp b/src/x11/i3/ipc/controller.cpp deleted file mode 100644 index a83afd4..0000000 --- a/src/x11/i3/ipc/controller.cpp +++ /dev/null @@ -1,361 +0,0 @@ -#include "controller.hpp" -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../../core/logcat.hpp" -#include "../../../core/model.hpp" -#include "../../../core/qmlscreen.hpp" -#include "connection.hpp" -#include "monitor.hpp" -#include "workspace.hpp" - -namespace qs::i3::ipc { - -namespace { -QS_LOGGING_CATEGORY(logI3Ipc, "quickshell.I3.ipc", QtWarningMsg); -QS_LOGGING_CATEGORY(logI3IpcEvents, "quickshell.I3.ipc.events", QtWarningMsg); -} // namespace - -I3IpcController::I3IpcController(): I3Ipc({"workspace", "output"}) { - // bind focused workspace to focused monitor's active workspace - this->bFocusedWorkspace.setBinding([this]() -> I3Workspace* { - if (!this->bFocusedMonitor) return nullptr; - return this->bFocusedMonitor->bindableActiveWorkspace().value(); - }); - - // clang-format off - QObject::connect(this, &I3Ipc::rawEvent, this, &I3IpcController::onEvent); - QObject::connect(&this->liveEventSocket, &QLocalSocket::connected, this, &I3IpcController::onConnected); - // clang-format on -} - -void I3IpcController::onConnected() { - // Workspaces must be refreshed before monitors or no focus will be - // detected on launch. - this->refreshWorkspaces(); - this->refreshMonitors(); -} - -void I3IpcController::setFocusedMonitor(I3Monitor* monitor) { - auto* oldMonitor = this->bFocusedMonitor.value(); - if (monitor == oldMonitor) return; - - if (oldMonitor != nullptr) { - QObject::disconnect(oldMonitor, nullptr, this, nullptr); - } - - if (monitor != nullptr) { - QObject::connect( - monitor, - &QObject::destroyed, - this, - &I3IpcController::onFocusedMonitorDestroyed - ); - } - - this->bFocusedMonitor = monitor; -} - -void I3IpcController::onFocusedMonitorDestroyed() { this->bFocusedMonitor = nullptr; } - -I3IpcController* I3IpcController::instance() { - static I3IpcController* instance = nullptr; // NOLINT - - if (instance == nullptr) { - instance = new I3IpcController(); - instance->connect(); - } - - return instance; -} - -void I3IpcController::refreshWorkspaces() { - this->makeRequest(I3Ipc::buildRequestMessage(EventCode::GetWorkspaces)); -} - -void I3IpcController::handleGetWorkspacesEvent(I3IpcEvent* event) { - auto data = event->mData; - - auto workspaces = data.array(); - - const auto& mList = this->mWorkspaces.valueList(); - auto names = QVector(); - - qCDebug(logI3Ipc) << "There are" << workspaces.toVariantList().length() << "workspaces"; - for (auto entry: workspaces) { - auto object = entry.toObject().toVariantMap(); - auto name = object["name"].toString(); - - auto workspaceIter = std::ranges::find_if(mList, [name](I3Workspace* m) { - return m->bindableName().value() == name; - }); - - auto* workspace = workspaceIter == mList.end() ? nullptr : *workspaceIter; - auto existed = workspace != nullptr; - - if (workspace == nullptr) { - workspace = new I3Workspace(this); - } - - workspace->updateFromObject(object); - - if (!existed) { - this->mWorkspaces.insertObjectSorted(workspace, &I3IpcController::compareWorkspaces); - } - - if (!this->bFocusedWorkspace && object.value("focused").value()) { - this->bFocusedMonitor = workspace->bindableMonitor().value(); - } - - names.push_back(name); - } - - auto removedWorkspaces = QVector(); - - for (auto* workspace: mList) { - if (!names.contains(workspace->bindableName().value())) { - removedWorkspaces.push_back(workspace); - } - } - - qCDebug(logI3Ipc) << "Removing" << removedWorkspaces.length() << "deleted workspaces."; - - for (auto* workspace: removedWorkspaces) { - this->mWorkspaces.removeObject(workspace); - delete workspace; - } -} - -void I3IpcController::refreshMonitors() { - this->makeRequest(I3Ipc::buildRequestMessage(EventCode::GetOutputs)); -} - -void I3IpcController::handleGetOutputsEvent(I3IpcEvent* event) { - auto data = event->mData; - - auto monitors = data.array(); - const auto& mList = this->mMonitors.valueList(); - auto names = QVector(); - - qCDebug(logI3Ipc) << "There are" << monitors.toVariantList().length() << "monitors"; - - for (auto elem: monitors) { - auto object = elem.toObject().toVariantMap(); - auto name = object["name"].toString(); - - auto monitorIter = std::ranges::find_if(mList, [name](I3Monitor* m) { - return m->bindableName().value() == name; - }); - - auto* monitor = monitorIter == mList.end() ? nullptr : *monitorIter; - auto existed = monitor != nullptr; - - if (monitor == nullptr) { - monitor = new I3Monitor(this); - } - - monitor->updateFromObject(object); - - if (monitor->bindableFocused().value()) { - this->setFocusedMonitor(monitor); - } - - if (!existed) { - this->mMonitors.insertObject(monitor); - } - - names.push_back(name); - } - - auto removedMonitors = QVector(); - - for (auto* monitor: mList) { - if (!names.contains(monitor->bindableName().value())) { - removedMonitors.push_back(monitor); - } - } - - qCDebug(logI3Ipc) << "Removing" << removedMonitors.length() << "disconnected monitors."; - - for (auto* monitor: removedMonitors) { - this->mMonitors.removeObject(monitor); - delete monitor; - } -} - -void I3IpcController::onEvent(I3IpcEvent* event) { - switch (event->mCode) { - case EventCode::Workspace: this->handleWorkspaceEvent(event); return; - case EventCode::Output: - /// I3 only sends an "unspecified" event, so we have to query the data changes ourselves - qCInfo(logI3Ipc) << "Refreshing Monitors..."; - this->refreshMonitors(); - return; - case EventCode::Subscribe: qCInfo(logI3Ipc) << "Connected to IPC"; return; - case EventCode::GetOutputs: this->handleGetOutputsEvent(event); return; - case EventCode::GetWorkspaces: this->handleGetWorkspacesEvent(event); return; - case EventCode::RunCommand: I3IpcController::handleRunCommand(event); return; - case EventCode::Unknown: - qCWarning(logI3Ipc) << "Unknown event:" << event->type() << event->data(); - return; - default: qCWarning(logI3Ipc) << "Unhandled event:" << event->type(); - } -} - -void I3IpcController::handleRunCommand(I3IpcEvent* event) { - for (auto r: event->mData.array()) { - auto obj = r.toObject(); - const bool success = obj["success"].toBool(); - - if (!success) { - const QString error = obj["error"].toString(); - qCWarning(logI3Ipc) << "Error occured while running command:" << error; - } - } -} - -void I3IpcController::handleWorkspaceEvent(I3IpcEvent* event) { - // If a workspace doesn't exist, and is being switch to, no focus change event is emited, - // only the init one, which does not contain the previously focused workspace - auto change = event->mData["change"]; - - if (change == "init") { - qCInfo(logI3IpcEvents) << "New workspace has been created"; - - auto workspaceData = event->mData["current"]; - - auto* workspace = this->findWorkspaceByID(workspaceData["id"].toInt(-1)); - auto existed = workspace != nullptr; - - if (!existed) { - workspace = new I3Workspace(this); - } - - if (workspaceData.isObject()) { - workspace->updateFromObject(workspaceData.toObject().toVariantMap()); - } - - if (!existed) { - this->mWorkspaces.insertObjectSorted(workspace, &I3IpcController::compareWorkspaces); - qCInfo(logI3Ipc) << "Added workspace" << workspace->bindableName().value() << "to list"; - } - } else if (change == "focus") { - auto oldData = event->mData["old"]; - auto newData = event->mData["current"]; - auto oldName = oldData["name"].toString(); - auto newName = newData["name"].toString(); - - qCInfo(logI3IpcEvents) << "Focus changed: " << oldName << "->" << newName; - - if (auto* oldWorkspace = this->findWorkspaceByName(oldName)) { - oldWorkspace->updateFromObject(oldData.toObject().toVariantMap()); - } - - auto* newWorkspace = this->findWorkspaceByName(newName); - - if (newWorkspace == nullptr) { - newWorkspace = new I3Workspace(this); - } - - newWorkspace->updateFromObject(newData.toObject().toVariantMap()); - - if (newWorkspace->bindableMonitor().value()) { - auto* monitor = newWorkspace->bindableMonitor().value(); - monitor->setActiveWorkspace(newWorkspace); - this->bFocusedMonitor = monitor; - } - } else if (change == "empty") { - auto name = event->mData["current"]["name"].toString(); - - auto* oldWorkspace = this->findWorkspaceByName(name); - - if (oldWorkspace != nullptr) { - qCInfo(logI3Ipc) << "Deleting" << oldWorkspace->bindableId().value() << name; - this->workspaces()->removeObject(oldWorkspace); - delete oldWorkspace; - } else { - qCInfo(logI3Ipc) << "Workspace" << name << "has already been deleted"; - } - } else if (change == "move" || change == "rename" || change == "urgent") { - auto name = event->mData["current"]["name"].toString(); - - auto* workspace = this->findWorkspaceByName(name); - - if (workspace != nullptr) { - auto data = event->mData["current"].toObject().toVariantMap(); - - workspace->updateFromObject(data); - } else { - qCWarning(logI3Ipc) << "Workspace" << name << "doesn't exist"; - } - } else if (change == "reload") { - qCInfo(logI3Ipc) << "Refreshing Workspaces..."; - this->refreshWorkspaces(); - } -} - -I3Monitor* I3IpcController::monitorFor(QuickshellScreenInfo* screen) { - if (screen == nullptr) return nullptr; - - return this->findMonitorByName(screen->name()); -} - -I3Workspace* I3IpcController::findWorkspaceByID(qint32 id) { - auto list = this->mWorkspaces.valueList(); - auto workspaceIter = - std::ranges::find_if(list, [id](I3Workspace* m) { return m->bindableId().value() == id; }); - - return workspaceIter == list.end() ? nullptr : *workspaceIter; -} - -I3Workspace* I3IpcController::findWorkspaceByName(const QString& name) { - auto list = this->mWorkspaces.valueList(); - auto workspaceIter = std::ranges::find_if(list, [name](I3Workspace* m) { - return m->bindableName().value() == name; - }); - - return workspaceIter == list.end() ? nullptr : *workspaceIter; -} - -I3Monitor* I3IpcController::findMonitorByName(const QString& name, bool createIfMissing) { - auto list = this->mMonitors.valueList(); - auto monitorIter = std::ranges::find_if(list, [name](I3Monitor* m) { - return m->bindableName().value() == name; - }); - - if (monitorIter != list.end()) { - return *monitorIter; - } else if (createIfMissing) { - qCDebug(logI3Ipc) << "Monitor" << name << "requested before creation, performing early init"; - auto* monitor = new I3Monitor(this); - monitor->updateInitial(name); - this->mMonitors.insertObject(monitor); - return monitor; - } else { - return nullptr; - } -} - -ObjectModel* I3IpcController::monitors() { return &this->mMonitors; } -ObjectModel* I3IpcController::workspaces() { return &this->mWorkspaces; } - -bool I3IpcController::compareWorkspaces(I3Workspace* a, I3Workspace* b) { - return a->bindableNumber().value() > b->bindableNumber().value(); -} - -} // namespace qs::i3::ipc diff --git a/src/x11/i3/ipc/controller.hpp b/src/x11/i3/ipc/controller.hpp deleted file mode 100644 index 464f6f6..0000000 --- a/src/x11/i3/ipc/controller.hpp +++ /dev/null @@ -1,94 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../../core/model.hpp" -#include "../../../core/qmlscreen.hpp" -#include "connection.hpp" - -namespace qs::i3::ipc { - -class I3Workspace; -class I3Monitor; -} // namespace qs::i3::ipc - -Q_DECLARE_OPAQUE_POINTER(qs::i3::ipc::I3Workspace*); -Q_DECLARE_OPAQUE_POINTER(qs::i3::ipc::I3Monitor*); - -namespace qs::i3::ipc { - -/// I3/Sway IPC controller that manages workspaces and monitors -class I3IpcController: public I3Ipc { - Q_OBJECT; - -public: - static I3IpcController* instance(); - - I3Workspace* findWorkspaceByName(const QString& name); - I3Monitor* findMonitorByName(const QString& name, bool createIfMissing = false); - I3Workspace* findWorkspaceByID(qint32 id); - - void setFocusedMonitor(I3Monitor* monitor); - - void refreshWorkspaces(); - void refreshMonitors(); - - I3Monitor* monitorFor(QuickshellScreenInfo* screen); - - [[nodiscard]] QBindable bindableFocusedMonitor() const { - return &this->bFocusedMonitor; - }; - - [[nodiscard]] QBindable bindableFocusedWorkspace() const { - return &this->bFocusedWorkspace; - }; - - [[nodiscard]] ObjectModel* monitors(); - [[nodiscard]] ObjectModel* workspaces(); - -signals: - void focusedWorkspaceChanged(); - void focusedMonitorChanged(); - -private slots: - void onFocusedMonitorDestroyed(); - - void onEvent(I3IpcEvent* event); - void onConnected(); - -private: - explicit I3IpcController(); - - void handleWorkspaceEvent(I3IpcEvent* event); - void handleGetWorkspacesEvent(I3IpcEvent* event); - void handleGetOutputsEvent(I3IpcEvent* event); - static void handleRunCommand(I3IpcEvent* event); - static bool compareWorkspaces(I3Workspace* a, I3Workspace* b); - - ObjectModel mMonitors {this}; - ObjectModel mWorkspaces {this}; - - Q_OBJECT_BINDABLE_PROPERTY( - I3IpcController, - I3Monitor*, - bFocusedMonitor, - &I3IpcController::focusedMonitorChanged - ); - - Q_OBJECT_BINDABLE_PROPERTY( - I3IpcController, - I3Workspace*, - bFocusedWorkspace, - &I3IpcController::focusedWorkspaceChanged - ); -}; - -} // namespace qs::i3::ipc diff --git a/src/x11/i3/ipc/listener.cpp b/src/x11/i3/ipc/listener.cpp deleted file mode 100644 index aa7719c..0000000 --- a/src/x11/i3/ipc/listener.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "listener.hpp" -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "connection.hpp" - -namespace qs::i3::ipc { - -I3IpcListener::~I3IpcListener() { this->freeI3Ipc(); } - -void I3IpcListener::onPostReload() { this->startListening(); } - -QList I3IpcListener::subscriptions() const { return this->mSubscriptions; } -void I3IpcListener::setSubscriptions(QList subscriptions) { - if (this->mSubscriptions == subscriptions) return; - this->mSubscriptions = std::move(subscriptions); - - emit this->subscriptionsChanged(); - this->startListening(); -} - -void I3IpcListener::startListening() { - this->freeI3Ipc(); - if (this->mSubscriptions.isEmpty()) return; - - this->i3Ipc = new I3Ipc(this->mSubscriptions); - - // clang-format off - QObject::connect(this->i3Ipc, &I3Ipc::rawEvent, this, &I3IpcListener::receiveEvent); - // clang-format on - - this->i3Ipc->connect(); -} - -void I3IpcListener::receiveEvent(I3IpcEvent* event) { emit this->ipcEvent(event); } - -void I3IpcListener::freeI3Ipc() { - delete this->i3Ipc; - this->i3Ipc = nullptr; -} - -} // namespace qs::i3::ipc diff --git a/src/x11/i3/ipc/listener.hpp b/src/x11/i3/ipc/listener.hpp deleted file mode 100644 index 9cb40bb..0000000 --- a/src/x11/i3/ipc/listener.hpp +++ /dev/null @@ -1,68 +0,0 @@ -#pragma once - -#include // NOLINT -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../../core/doc.hpp" -#include "../../../core/generation.hpp" -#include "../../../core/qmlglobal.hpp" -#include "../../../core/reload.hpp" -#include "connection.hpp" - -namespace qs::i3::ipc { - -///! I3/Sway IPC event listener -/// #### Example -/// ```qml -/// I3IpcListener { -/// subscriptions: ["input"] -/// onIpcEvent: function (event) { -/// handleInputEvent(event.data) -/// } -/// } -/// ``` -class I3IpcListener: public PostReloadHook { - Q_OBJECT; - // clang-format off - /// List of [I3/Sway events](https://man.archlinux.org/man/sway-ipc.7.en#EVENTS) to subscribe to. - Q_PROPERTY(QList subscriptions READ subscriptions WRITE setSubscriptions NOTIFY subscriptionsChanged); - // clang-format on - QML_ELEMENT; - -public: - explicit I3IpcListener(QObject* parent = nullptr): PostReloadHook(parent) {} - ~I3IpcListener() override; - Q_DISABLE_COPY_MOVE(I3IpcListener); - - void onPostReload() override; - - [[nodiscard]] QList subscriptions() const; - void setSubscriptions(QList subscriptions); - -signals: - void ipcEvent(I3IpcEvent* event); - void subscriptionsChanged(); - -private: - void startListening(); - void receiveEvent(I3IpcEvent* event); - - void freeI3Ipc(); - - QList mSubscriptions; - I3Ipc* i3Ipc = nullptr; -}; - -} // namespace qs::i3::ipc diff --git a/src/x11/i3/ipc/monitor.cpp b/src/x11/i3/ipc/monitor.cpp index 7afb68e..1bc593c 100644 --- a/src/x11/i3/ipc/monitor.cpp +++ b/src/x11/i3/ipc/monitor.cpp @@ -7,12 +7,12 @@ #include #include -#include "controller.hpp" +#include "connection.hpp" #include "workspace.hpp" namespace qs::i3::ipc { -I3Monitor::I3Monitor(I3IpcController* ipc): QObject(ipc), ipc(ipc) { +I3Monitor::I3Monitor(I3Ipc* ipc): QObject(ipc), ipc(ipc) { // clang-format off this->bFocused.setBinding([this]() { return this->ipc->bindableFocusedMonitor().value() == this; }); // clang-format on @@ -40,37 +40,21 @@ void I3Monitor::updateFromObject(const QVariantMap& obj) { this->bHeight = rect.value("height").value(); this->bScale = obj.value("scale").value(); - auto* activeWorkspace = this->bActiveWorkspace.value(); - if (!activeWorkspace || activeWorkspaceName != activeWorkspace->bindableName().value()) { + if (!this->bActiveWorkspace + || activeWorkspaceName != this->bActiveWorkspace->bindableName().value()) + { if (activeWorkspaceName.isEmpty()) { - activeWorkspace = nullptr; + this->bActiveWorkspace = nullptr; } else { - activeWorkspace = this->ipc->findWorkspaceByName(activeWorkspaceName); + this->bActiveWorkspace = this->ipc->findWorkspaceByName(activeWorkspaceName); } }; - this->setActiveWorkspace(activeWorkspace); - Qt::endPropertyUpdateGroup(); } void I3Monitor::updateInitial(const QString& name) { this->bName = name; } -void I3Monitor::setActiveWorkspace(I3Workspace* workspace) { - auto* oldWorkspace = this->bActiveWorkspace.value(); - if (oldWorkspace == workspace) return; - - if (oldWorkspace) { - QObject::disconnect(oldWorkspace, nullptr, this, nullptr); - } - - if (workspace) { - QObject::connect(workspace, &QObject::destroyed, this, &I3Monitor::onActiveWorkspaceDestroyed); - } - - this->bActiveWorkspace = workspace; -} - -void I3Monitor::onActiveWorkspaceDestroyed() { this->bActiveWorkspace = nullptr; } +void I3Monitor::setFocusedWorkspace(I3Workspace* workspace) { this->bActiveWorkspace = workspace; }; } // namespace qs::i3::ipc diff --git a/src/x11/i3/ipc/monitor.hpp b/src/x11/i3/ipc/monitor.hpp index c328d8b..00269a1 100644 --- a/src/x11/i3/ipc/monitor.hpp +++ b/src/x11/i3/ipc/monitor.hpp @@ -4,7 +4,6 @@ #include #include "connection.hpp" -#include "controller.hpp" namespace qs::i3::ipc { @@ -40,10 +39,10 @@ class I3Monitor: public QObject { Q_PROPERTY(QVariantMap lastIpcObject READ lastIpcObject NOTIFY lastIpcObjectChanged); // clang-format on QML_ELEMENT; - QML_UNCREATABLE("I3Monitors must be retrieved from the I3IpcController object."); + QML_UNCREATABLE("I3Monitors must be retrieved from the I3Ipc object."); public: - explicit I3Monitor(I3IpcController* ipc); + explicit I3Monitor(I3Ipc* ipc); [[nodiscard]] QBindable bindableId() { return &this->bId; } [[nodiscard]] QBindable bindableName() { return &this->bName; } @@ -55,7 +54,7 @@ public: [[nodiscard]] QBindable bindableScale() { return &this->bScale; } [[nodiscard]] QBindable bindableFocused() { return &this->bFocused; } - [[nodiscard]] QBindable bindableActiveWorkspace() const { + [[nodiscard]] QBindable bindableActiveWorkspace() { return &this->bActiveWorkspace; } @@ -64,7 +63,7 @@ public: void updateFromObject(const QVariantMap& obj); void updateInitial(const QString& name); - void setActiveWorkspace(I3Workspace* workspace); + void setFocusedWorkspace(I3Workspace* workspace); signals: void idChanged(); @@ -79,11 +78,8 @@ signals: void lastIpcObjectChanged(); void focusedChanged(); -private slots: - void onActiveWorkspaceDestroyed(); - private: - I3IpcController* ipc; + I3Ipc* ipc; QVariantMap mLastIpcObject; diff --git a/src/x11/i3/ipc/qml.cpp b/src/x11/i3/ipc/qml.cpp index d835cbd..2804161 100644 --- a/src/x11/i3/ipc/qml.cpp +++ b/src/x11/i3/ipc/qml.cpp @@ -7,49 +7,46 @@ #include "../../../core/model.hpp" #include "../../../core/qmlscreen.hpp" #include "connection.hpp" -#include "controller.hpp" #include "workspace.hpp" namespace qs::i3::ipc { I3IpcQml::I3IpcQml() { - auto* instance = I3IpcController::instance(); + auto* instance = I3Ipc::instance(); // clang-format off QObject::connect(instance, &I3Ipc::rawEvent, this, &I3IpcQml::rawEvent); QObject::connect(instance, &I3Ipc::connected, this, &I3IpcQml::connected); - QObject::connect(instance, &I3IpcController::focusedWorkspaceChanged, this, &I3IpcQml::focusedWorkspaceChanged); - QObject::connect(instance, &I3IpcController::focusedMonitorChanged, this, &I3IpcQml::focusedMonitorChanged); + QObject::connect(instance, &I3Ipc::focusedWorkspaceChanged, this, &I3IpcQml::focusedWorkspaceChanged); + QObject::connect(instance, &I3Ipc::focusedMonitorChanged, this, &I3IpcQml::focusedMonitorChanged); // clang-format on } -void I3IpcQml::dispatch(const QString& request) { I3IpcController::instance()->dispatch(request); } -void I3IpcQml::refreshMonitors() { I3IpcController::instance()->refreshMonitors(); } -void I3IpcQml::refreshWorkspaces() { I3IpcController::instance()->refreshWorkspaces(); } -QString I3IpcQml::socketPath() { return I3IpcController::instance()->socketPath(); } -ObjectModel* I3IpcQml::monitors() { return I3IpcController::instance()->monitors(); } -ObjectModel* I3IpcQml::workspaces() { - return I3IpcController::instance()->workspaces(); -} +void I3IpcQml::dispatch(const QString& request) { I3Ipc::instance()->dispatch(request); } +void I3IpcQml::refreshMonitors() { I3Ipc::instance()->refreshMonitors(); } +void I3IpcQml::refreshWorkspaces() { I3Ipc::instance()->refreshWorkspaces(); } +QString I3IpcQml::socketPath() { return I3Ipc::instance()->socketPath(); } +ObjectModel* I3IpcQml::monitors() { return I3Ipc::instance()->monitors(); } +ObjectModel* I3IpcQml::workspaces() { return I3Ipc::instance()->workspaces(); } QBindable I3IpcQml::bindableFocusedWorkspace() { - return I3IpcController::instance()->bindableFocusedWorkspace(); + return I3Ipc::instance()->bindableFocusedWorkspace(); } QBindable I3IpcQml::bindableFocusedMonitor() { - return I3IpcController::instance()->bindableFocusedMonitor(); + return I3Ipc::instance()->bindableFocusedMonitor(); } I3Workspace* I3IpcQml::findWorkspaceByName(const QString& name) { - return I3IpcController::instance()->findWorkspaceByName(name); + return I3Ipc::instance()->findWorkspaceByName(name); } I3Monitor* I3IpcQml::findMonitorByName(const QString& name) { - return I3IpcController::instance()->findMonitorByName(name); + return I3Ipc::instance()->findMonitorByName(name); } I3Monitor* I3IpcQml::monitorFor(QuickshellScreenInfo* screen) { - return I3IpcController::instance()->monitorFor(screen); + return I3Ipc::instance()->monitorFor(screen); } } // namespace qs::i3::ipc diff --git a/src/x11/i3/ipc/qml.hpp b/src/x11/i3/ipc/qml.hpp index 2e7c81c..804e42a 100644 --- a/src/x11/i3/ipc/qml.hpp +++ b/src/x11/i3/ipc/qml.hpp @@ -7,7 +7,6 @@ #include "../../../core/doc.hpp" #include "../../../core/qmlscreen.hpp" #include "connection.hpp" -#include "controller.hpp" namespace qs::i3::ipc { diff --git a/src/x11/i3/ipc/workspace.cpp b/src/x11/i3/ipc/workspace.cpp index 530f0a2..7d0b730 100644 --- a/src/x11/i3/ipc/workspace.cpp +++ b/src/x11/i3/ipc/workspace.cpp @@ -7,12 +7,12 @@ #include #include -#include "controller.hpp" +#include "connection.hpp" #include "monitor.hpp" namespace qs::i3::ipc { -I3Workspace::I3Workspace(I3IpcController* ipc): QObject(ipc), ipc(ipc) { +I3Workspace::I3Workspace(I3Ipc* ipc): QObject(ipc), ipc(ipc) { Qt::beginPropertyUpdateGroup(); this->bActive.setBinding([this]() { @@ -43,17 +43,14 @@ void I3Workspace::updateFromObject(const QVariantMap& obj) { auto monitorName = obj.value("output").value(); - auto* monitor = this->bMonitor.value(); - if (!monitor || monitorName != monitor->bindableName().value()) { + if (!this->bMonitor || monitorName != this->bMonitor->bindableName().value()) { if (monitorName.isEmpty()) { - monitor = nullptr; + this->bMonitor = nullptr; } else { - monitor = this->ipc->findMonitorByName(monitorName, true); + this->bMonitor = this->ipc->findMonitorByName(monitorName, true); } } - this->setMonitor(monitor); - Qt::endPropertyUpdateGroup(); } @@ -61,21 +58,4 @@ void I3Workspace::activate() { this->ipc->dispatch(QString("workspace number %1").arg(this->bNumber.value())); } -void I3Workspace::setMonitor(I3Monitor* monitor) { - auto* oldMonitor = this->bMonitor.value(); - if (oldMonitor == monitor) return; - - if (oldMonitor) { - QObject::disconnect(oldMonitor, nullptr, this, nullptr); - } - - if (monitor) { - QObject::connect(monitor, &QObject::destroyed, this, &I3Workspace::onMonitorDestroyed); - } - - this->bMonitor = monitor; -} - -void I3Workspace::onMonitorDestroyed() { this->bMonitor = nullptr; } - } // namespace qs::i3::ipc diff --git a/src/x11/i3/ipc/workspace.hpp b/src/x11/i3/ipc/workspace.hpp index c08e926..c9cd029 100644 --- a/src/x11/i3/ipc/workspace.hpp +++ b/src/x11/i3/ipc/workspace.hpp @@ -5,7 +5,6 @@ #include #include "connection.hpp" -#include "controller.hpp" namespace qs::i3::ipc { @@ -41,7 +40,7 @@ class I3Workspace: public QObject { QML_UNCREATABLE("I3Workspaces must be retrieved from the I3 object."); public: - I3Workspace(I3IpcController* ipc); + I3Workspace(I3Ipc* ipc); /// Activate the workspace. /// @@ -57,13 +56,11 @@ public: [[nodiscard]] QBindable bindableActive() { return &this->bActive; } [[nodiscard]] QBindable bindableFocused() { return &this->bFocused; } [[nodiscard]] QBindable bindableUrgent() { return &this->bUrgent; } - [[nodiscard]] QBindable bindableMonitor() const { return &this->bMonitor; } + [[nodiscard]] QBindable bindableMonitor() { return &this->bMonitor; } [[nodiscard]] QVariantMap lastIpcObject() const; void updateFromObject(const QVariantMap& obj); - void setMonitor(I3Monitor* monitor); - signals: void idChanged(); void nameChanged(); @@ -74,11 +71,8 @@ signals: void monitorChanged(); void lastIpcObjectChanged(); -private slots: - void onMonitorDestroyed(); - private: - I3IpcController* ipc; + I3Ipc* ipc; QVariantMap mLastIpcObject; diff --git a/src/x11/i3/module.md b/src/x11/i3/module.md index 1dc6180..10afb98 100644 --- a/src/x11/i3/module.md +++ b/src/x11/i3/module.md @@ -2,10 +2,8 @@ name = "Quickshell.I3" description = "I3 specific Quickshell types" headers = [ "ipc/connection.hpp", - "ipc/controller.hpp", "ipc/qml.hpp", "ipc/workspace.hpp", "ipc/monitor.hpp", - "ipc/listener.hpp", ] ----- diff --git a/src/x11/panel_window.cpp b/src/x11/panel_window.cpp index c78b548..5d53fdd 100644 --- a/src/x11/panel_window.cpp +++ b/src/x11/panel_window.cpp @@ -115,8 +115,6 @@ XPanelWindow::XPanelWindow(QObject* parent): ProxyWindowBase(parent) { return 0; } } - - return 0; }); this->bcExclusionEdge.setBinding([this] { return this->bAnchors.value().exclusionEdge(); });