diff --git a/.clang-tidy b/.clang-tidy index 1da445cd..6362e662 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -5,7 +5,6 @@ Checks: > -*, bugprone-*, -bugprone-easily-swappable-parameters, - -bugprone-forward-declararion-namespace, concurrency-*, cppcoreguidelines-*, -cppcoreguidelines-owning-memory, diff --git a/.gitignore b/.gitignore index dcdefe39..1933837e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,3 @@ -# related repos -/docs -/examples - # build files /result /build/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..74013769 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "docs"] + path = docs + url = https://git.outfoxxed.me/outfoxxed/quickshell-docs +[submodule "examples"] + path = examples + url = https://git.outfoxxed.me/outfoxxed/quickshell-examples diff --git a/BUILD.md b/BUILD.md deleted file mode 100644 index 3c3e7125..00000000 --- a/BUILD.md +++ /dev/null @@ -1,165 +0,0 @@ -# Build instructions -Instructions for building from source and distro packagers. We highly recommend -distro packagers read through this page fully. - -## Dependencies -Quickshell has a set of base dependencies you will always need, names vary by distro: - -- `cmake` -- `qt6base` -- `qt6declarative` -- `pkg-config` - -We recommend an implicit dependency on `qt6svg`. If it is not installed, svg images and -svg icons will not work, including system ones. - -At least Qt 6.6 is required. - -All features are enabled by default and some have their own dependencies. - -##### Additional note to packagers: -If your package manager supports enabling some features but not others, -we recommend not exposing the subfeatures and just the main ones that introduce -new dependencies: `wayland`, `x11`, `pipewire`, `hyprland` - -### Jemalloc -We recommend leaving Jemalloc enabled as it will mask memory fragmentation caused -by the QML engine, which results in much lower memory usage. Without this you -will get a perceived memory leak. - -To disable: `-DUSE_JEMALLOC=OFF` - -Dependencies: `jemalloc` - -### Unix Sockets -This feature allows interaction with unix sockets and creating socket servers -which is useful for IPC and has no additional dependencies. - -WARNING: Disabling unix sockets will NOT make it safe to run arbitrary code using quickshell. -There are many vectors which mallicious code can use to escape into your system. - -To disable: `-DSOCKETS=OFF` - -### Wayland -This feature enables wayland support. Subfeatures exist for each particular wayland integration. - -WARNING: Wayland integration relies on features that are not part of the public Qt API and which -may break in minor releases. Updating quickshell's dependencies without ensuring without ensuring -that the current Qt version is supported WILL result in quickshell failing to build or misbehaving -at runtime. - -Currently supported Qt versions: `6.6`, `6.7`. - -To disable: `-DWAYLAND=OFF` - -Dependencies: - - `qt6wayland` - - `wayland` (libwayland-client) - - `wayland-scanner` (may be part of your distro's wayland package) - - `wayland-protocols` - -#### Wlroots Layershell -Enables wlroots layershell integration through the [zwlr-layer-shell-v1] protocol, -enabling use cases such as bars overlays and backgrounds. -This feature has no extra dependencies. - -To disable: `-DWAYLAND_WLR_LAYERSHELL=OFF` - -[zwlr-layer-shell-v1]: https://wayland.app/protocols/wlr-layer-shell-unstable-v1 - -#### Session Lock -Enables session lock support through the [ext-session-lock-v1] protocol, -which allows quickshell to be used as a session lock under compatible wayland compositors. - -To disable: `-DWAYLAND_SESSION_LOCK=OFF` - -[ext-session-lock-v1]: https://wayland.app/protocols/ext-session-lock-v1 - - -#### Foreign Toplevel Management -Enables management of windows of other clients through the [zwlr-foreign-toplevel-management-v1] protocol, -which allows quickshell to be used as a session lock under compatible wayland compositors. - -[zwlr-foreign-toplevel-management-v1]: https://wayland.app/protocols/wlr-foreign-toplevel-management-unstable-v1 - -To disable: `-DWAYLAND_TOPLEVEL_MANAGEMENT=OFF` - -### X11 -This feature enables x11 support. Currently this implements panel windows for X11 similarly -to the wlroots layershell above. - -To disable: `-DX11=OFF` - -Dependencies: `libxcb` - -### Pipewire -This features enables viewing and management of pipewire nodes. - -To disable: `-DSERVICE_PIPEWIRE=OFF` - -Dependencies: `libpipewire` - -### StatusNotifier / System Tray -This feature enables system tray support using the status notifier dbus protocol. - -To disable: `-DSERVICE_STATUS_NOTIFIER=OFF` - -Dependencies: `qt6dbus` (usually part of qt6base) - -### MPRIS -This feature enables access to MPRIS compatible media players using its dbus protocol. - -To disable: `-DSERVICE_MPRIS=OFF` - -Dependencies: `qt6dbus` (usually part of qt6base) - -### Hyprland -This feature enables hyprland specific integrations. It requires wayland support -but has no extra dependencies. - -To disable: `-DHYPRLAND=OFF` - -#### Hyprland Global Shortcuts -Enables creation of global shortcuts under hyprland through the [hyprland-global-shortcuts-v1] -protocol. Generally a much nicer alternative to using unix sockets to implement the same thing. -This feature has no extra dependencies. - -To disable: `-DHYPRLAND_GLOBAL_SHORTCUTS=OFF` - -[hyprland-global-shortcuts-v1]: https://github.com/hyprwm/hyprland-protocols/blob/main/protocols/hyprland-global-shortcuts-v1.xml - -#### Hyprland Focus Grab -Enables windows to grab focus similarly to a context menu undr hyprland through the -[hyprland-focus-grab-v1] protocol. This feature has no extra dependencies. - -To disable: `-DHYPRLAND_FOCUS_GRAB=OFF` - -[hyprland-focus-grab-v1]: https://github.com/hyprwm/hyprland-protocols/blob/main/protocols/hyprland-focus-grab-v1.xml - -## Building -*For developers and prospective contributors: See [CONTRIBUTING.md](CONTRIBUTING.md).* - -We highly recommend using `ninja` to run the build, but you can use makefiles if you must. - -#### Configuring the build -```sh -$ 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 -or quickshell will fail to build. - -Additionally, note that clang builds much faster than gcc if you care. - -You may disable debug information but it's only a couple megabytes and is extremely helpful -for helping us fix problems when they do arise. - -#### Building -```sh -$ cmake --build build -``` - -#### Installing -```sh -$ cmake --install build -``` diff --git a/CMakeLists.txt b/CMakeLists.txt index 7af6b6cf..2d17758b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,15 +9,13 @@ option(BUILD_TESTING "Build tests" OFF) option(ASAN "Enable ASAN" OFF) option(FRAME_POINTERS "Always keep frame pointers" ${ASAN}) -option(USE_JEMALLOC "Use jemalloc over the system malloc implementation" ON) +option(NVIDIA_COMPAT "Workarounds for nvidia gpus" OFF) option(SOCKETS "Enable unix socket support" ON) option(WAYLAND "Enable wayland support" ON) option(WAYLAND_WLR_LAYERSHELL "Support the zwlr_layer_shell_v1 wayland protocol" ON) option(WAYLAND_SESSION_LOCK "Support the ext_session_lock_v1 wayland protocol" ON) -option(WAYLAND_TOPLEVEL_MANAGEMENT "Support the zwlr_foreign_toplevel_management_v1 wayland protocol" ON) option(X11 "Enable X11 support" ON) option(HYPRLAND "Support hyprland specific features" ON) -option(HYPRLAND_IPC "Hyprland IPC" ON) option(HYPRLAND_GLOBAL_SHORTCUTS "Hyprland Global Shortcuts" ON) option(HYPRLAND_FOCUS_GRAB "Hyprland Focus Grabbing" ON) option(SERVICE_STATUS_NOTIFIER "StatusNotifierItem service" ON) @@ -25,14 +23,13 @@ option(SERVICE_PIPEWIRE "PipeWire service" ON) option(SERVICE_MPRIS "Mpris service" ON) message(STATUS "Quickshell configuration") -message(STATUS " Jemalloc: ${USE_JEMALLOC}") +message(STATUS " NVIDIA workarounds: ${NVIDIA_COMPAT}") message(STATUS " Build tests: ${BUILD_TESTING}") message(STATUS " Sockets: ${SOCKETS}") message(STATUS " Wayland: ${WAYLAND}") if (WAYLAND) message(STATUS " Wlroots Layershell: ${WAYLAND_WLR_LAYERSHELL}") message(STATUS " Session Lock: ${WAYLAND_SESSION_LOCK}") - message(STATUS " Toplevel Management: ${WAYLAND_TOPLEVEL_MANAGEMENT}") endif () message(STATUS " X11: ${X11}") message(STATUS " Services") @@ -41,7 +38,6 @@ message(STATUS " PipeWire: ${SERVICE_PIPEWIRE}") message(STATUS " Mpris: ${SERVICE_MPRIS}") message(STATUS " Hyprland: ${HYPRLAND}") if (HYPRLAND) - message(STATUS " IPC: ${HYPRLAND_IPC}") message(STATUS " Focus Grabbing: ${HYPRLAND_FOCUS_GRAB}") message(STATUS " Global Shortcuts: ${HYPRLAND_GLOBAL_SHORTCUTS}") endif() @@ -136,11 +132,8 @@ function (qs_pch target) endif() endfunction() -add_subdirectory(src) - -if (USE_JEMALLOC) - find_package(PkgConfig REQUIRED) - # IMPORTED_TARGET not working for some reason - pkg_check_modules(JEMALLOC REQUIRED jemalloc) - target_link_libraries(quickshell PRIVATE ${JEMALLOC_LIBRARIES}) +if (NVIDIA_COMPAT) + add_compile_definitions(NVIDIA_COMPAT) endif() + +add_subdirectory(src) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 6fdef09c..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,99 +0,0 @@ -# Contributing / Development -Instructions for development setup and upstreaming patches. - -If you just want to build or package quickshell see [BUILD.md](BUILD.md). - -## 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. - -Quickshell also uses `just` for common development command aliases. - -The dependencies are also available as a nix shell or nix flake which we recommend -using with nix-direnv. - -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`. - -### 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 -``` - -If the linter is complaining about something that you think it should not, -please disable the lint in your MR and explain your reasoning. - -### 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. -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/README.md b/README.md index 4def09ed..c17af3a8 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,22 @@ Hosted on: [outfoxxed's gitea], [github] Documentation available at [quickshell.outfoxxed.me](https://quickshell.outfoxxed.me) or can be built from the [quickshell-docs](https://git.outfoxxed.me/outfoxxed/quickshell-docs) repo. -Some fully working examples can be found in the [quickshell-examples](https://git.outfoxxed.me/outfoxxed/quickshell-examples) +Some fully working examples can be found in the [quickshell-examples](https://git.outfoxxed.me/outfoxxed/quickshell-examples) repo. +Both the documentation and examples are included as submodules with revisions that work with the current +version of quickshell. + +You can clone everything with +``` +$ git clone --recursive https://git.outfoxxed.me/outfoxxed/quickshell.git +``` + +Or clone missing submodules later with +``` +$ git submodule update --init --recursive +``` + # Installation ## Nix @@ -35,33 +48,81 @@ This repo has a nix flake you can use to install the package directly: Quickshell's binary is available at `quickshell.packages..default` to be added to lists such as `environment.systemPackages` or `home.packages`. -The package contains several features detailed in [BUILD.md](BUILD.md) which can be enabled -or disabled with overrides: - -```nix -quickshell.packages..default.override { - withJemalloc = true; - withQtSvg = true; - withWayland = true; - withX11 = true; - withPipewire = true; - withHyprland = true; -} -``` +`quickshell.packages..nvidia` is also available for nvidia users which fixes some +common crashes. Note: by default this package is built with clang as it is significantly faster. -## Arch (AUR) -Quickshell has a third party [AUR package] available under the same name. -As is usual with the AUR it is not maintained by us and should be looked over before use. +## Manual -[AUR package]: https://aur.archlinux.org/packages/quickshell +If not using nix, you'll have to build from source. -## Anything else -See [BUILD.md](BUILD.md) for instructions on building and packaging quickshell. +### Dependencies +To build quickshell at all, you will need the following packages (names may vary by distro) -# Contributing / Development -See [CONTRIBUTING.md](CONTRIBUTING.md) for details. +- just +- cmake +- ninja +- Qt6 [ QtBase, QtDeclarative ] + +To build with wayland support you will additionally need: +- pkg-config +- wayland +- wayland-scanner (may be part of wayland on some distros) +- wayland-protocols +- Qt6 [ QtWayland ] + +To build with x11 support you will additionally need: +- libxcb + +To build with pipewire support you will additionally need: +- libpipewire + +### Building + +To make a release build of quickshell run: +```sh +$ just release +``` + +If running an nvidia GPU, instead run: +```sh +$ just configure release -DNVIDIA_COMPAT=ON +$ just build +``` + +(These commands are just aliases for cmake commands you can run directly, +see the Justfile for more information.) + +If you have all the dependencies installed and they are in expected +locations this will build correctly. + +To install to /usr/local/bin run as root (usually `sudo`) in the same folder: +``` +$ just install +``` + +### Building (Nix) + +You can build directly using the provided nix flake or nix package. +``` +nix build +nix build -f package.nix # calls default.nix with a basic callPackage expression +``` + +# Development + +For nix there is a devshell available from `shell.nix` and as a devShell +output from the flake. + +The Justfile contains various useful aliases: +- `just configure [ [extra cmake args]]` +- `just build` (runs configure for debug mode) +- `just run [args]` +- `just clean` +- `just test [args]` (configure with `-DBUILD_TESTING=ON` first) +- `just fmt` +- `just lint` #### License diff --git a/default.nix b/default.nix index 01624c4a..0985d843 100644 --- a/default.nix +++ b/default.nix @@ -8,7 +8,6 @@ cmake, ninja, qt6, - jemalloc, wayland, wayland-protocols, xorg, @@ -26,12 +25,11 @@ else "unknown"), debug ? false, - withJemalloc ? true, # masks heap fragmentation - withQtSvg ? true, - withWayland ? true, - withX11 ? true, - withPipewire ? true, - withHyprland ? true, + enableWayland ? true, + enableX11 ? true, + enablePipewire ? true, + nvidiaCompat ? false, + svgSupport ? true, # you almost always want this }: buildStdenv.mkDerivation { pname = "quickshell${lib.optionalString debug "-debug"}"; version = "0.1.0"; @@ -41,8 +39,8 @@ cmake ninja qt6.wrapQtAppsHook + ] ++ (lib.optionals enableWayland [ pkg-config - ] ++ (lib.optionals withWayland [ wayland-protocols wayland-scanner ]); @@ -51,13 +49,12 @@ qt6.qtbase qt6.qtdeclarative ] - ++ (lib.optional withJemalloc jemalloc) - ++ (lib.optional withQtSvg qt6.qtsvg) - ++ (lib.optionals withWayland [ qt6.qtwayland wayland ]) - ++ (lib.optional withX11 xorg.libxcb) - ++ (lib.optional withPipewire pipewire); + ++ (lib.optionals enableWayland [ qt6.qtwayland wayland ]) + ++ (lib.optionals enableX11 [ xorg.libxcb ]) + ++ (lib.optionals svgSupport [ qt6.qtsvg ]) + ++ (lib.optionals enablePipewire [ pipewire ]); - QTWAYLANDSCANNER = lib.optionalString withWayland "${qt6.qtwayland}/libexec/qtwaylandscanner"; + QTWAYLANDSCANNER = lib.optionalString enableWayland "${qt6.qtwayland}/libexec/qtwaylandscanner"; configurePhase = let cmakeBuildType = if debug @@ -70,11 +67,9 @@ cmakeFlags = [ "-DGIT_REVISION=${gitRev}" - ] - ++ lib.optional (!withJemalloc) "-DUSE_JEMALLOC=OFF" - ++ lib.optional (!withWayland) "-DWAYLAND=OFF" - ++ lib.optional (!withPipewire) "-DSERVICE_PIPEWIRE=OFF" - ++ lib.optional (!withHyprland) "-DHYPRLAND=OFF"; + ] ++ lib.optional (!enableWayland) "-DWAYLAND=OFF" + ++ lib.optional nvidiaCompat "-DNVIDIA_COMPAT=ON" + ++ lib.optional (!enablePipewire) "-DSERVICE_PIPEWIRE=OFF"; buildPhase = "ninjaBuildPhase"; enableParallelBuilding = true; diff --git a/docs b/docs new file mode 160000 index 00000000..ff5da84a --- /dev/null +++ b/docs @@ -0,0 +1 @@ +Subproject commit ff5da84a8b258a9b2caaf978ddb6de23635d8903 diff --git a/examples b/examples new file mode 160000 index 00000000..b9e744b5 --- /dev/null +++ b/examples @@ -0,0 +1 @@ +Subproject commit b9e744b50673304dfddb68f3da2a2e906d028b96 diff --git a/flake.nix b/flake.nix index a0bc18d4..5bb5069e 100644 --- a/flake.nix +++ b/flake.nix @@ -12,8 +12,10 @@ quickshell = pkgs.callPackage ./default.nix { gitRev = self.rev or self.dirtyRev; }; + quickshell-nvidia = quickshell.override { nvidiaCompat = true; }; default = quickshell; + nvidia = quickshell-nvidia; }); devShells = forEachSystem (system: pkgs: rec { diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 24d2e685..b40b807f 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -26,8 +26,6 @@ qt_add_library(quickshell-core STATIC imageprovider.cpp transformwatcher.cpp boundcomponent.cpp - model.cpp - elapsedtimer.cpp ) set_source_files_properties(main.cpp PROPERTIES COMPILE_DEFINITIONS GIT_REVISION="${GIT_REVISION}") diff --git a/src/core/doc.hpp b/src/core/doc.hpp index e1f2ee4c..b619b0a6 100644 --- a/src/core/doc.hpp +++ b/src/core/doc.hpp @@ -10,8 +10,5 @@ #define QSDOC_ELEMENT #define QSDOC_NAMED_ELEMENT(name) -// change the cname used for this type -#define QSDOC_CNAME(name) - // overridden properties #define QSDOC_PROPERTY_OVERRIDE(...) diff --git a/src/core/elapsedtimer.cpp b/src/core/elapsedtimer.cpp deleted file mode 100644 index 91321122..00000000 --- a/src/core/elapsedtimer.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "elapsedtimer.hpp" - -#include - -ElapsedTimer::ElapsedTimer() { this->timer.start(); } - -qreal ElapsedTimer::elapsed() { return static_cast(this->elapsedNs()) / 1000000000.0; } - -qreal ElapsedTimer::restart() { return static_cast(this->restartNs()) / 1000000000.0; } - -qint64 ElapsedTimer::elapsedMs() { return this->timer.elapsed(); } - -qint64 ElapsedTimer::restartMs() { return this->timer.restart(); } - -qint64 ElapsedTimer::elapsedNs() { return this->timer.nsecsElapsed(); } - -qint64 ElapsedTimer::restartNs() { - // see qelapsedtimer.cpp - auto old = this->timer; - this->timer.start(); - return old.durationTo(this->timer).count(); -} diff --git a/src/core/elapsedtimer.hpp b/src/core/elapsedtimer.hpp deleted file mode 100644 index 85850963..00000000 --- a/src/core/elapsedtimer.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -///! Measures time between events -/// The ElapsedTimer measures time since its last restart, and is useful -/// for determining the time between events that don't supply it. -class ElapsedTimer: public QObject { - Q_OBJECT; - QML_ELEMENT; - -public: - explicit ElapsedTimer(); - - /// Return the number of seconds since the timer was last - /// started or restarted, with nanosecond precision. - Q_INVOKABLE qreal elapsed(); - - /// Restart the timer, returning the number of seconds since - /// the timer was last started or restarted, with nanosecond precision. - Q_INVOKABLE qreal restart(); - - /// Return the number of milliseconds since the timer was last - /// started or restarted. - Q_INVOKABLE qint64 elapsedMs(); - - /// Restart the timer, returning the number of milliseconds since - /// the timer was last started or restarted. - Q_INVOKABLE qint64 restartMs(); - - /// Return the number of nanoseconds since the timer was last - /// started or restarted. - Q_INVOKABLE qint64 elapsedNs(); - - /// Restart the timer, returning the number of nanoseconds since - /// the timer was last started or restarted. - Q_INVOKABLE qint64 restartNs(); - -private: - QElapsedTimer timer; -}; diff --git a/src/core/generation.cpp b/src/core/generation.cpp index e43db6ee..77e4a9cb 100644 --- a/src/core/generation.cpp +++ b/src/core/generation.cpp @@ -4,8 +4,6 @@ #include #include #include -#include -#include #include #include #include @@ -14,6 +12,7 @@ #include #include #include +#include #include #include "iconimageprovider.hpp" @@ -26,10 +25,8 @@ static QHash g_generations; // NOLINT -EngineGeneration::EngineGeneration(const QDir& rootPath, QmlScanner scanner) - : rootPath(rootPath) - , scanner(std::move(scanner)) - , urlInterceptor(this->rootPath) +EngineGeneration::EngineGeneration(QmlScanner scanner) + : scanner(std::move(scanner)) , interceptNetFactory(this->scanner.qmldirIntercepts) , engine(new QQmlEngine()) { g_generations.insert(this->engine, this); @@ -46,41 +43,36 @@ EngineGeneration::EngineGeneration(const QDir& rootPath, QmlScanner scanner) } EngineGeneration::~EngineGeneration() { - if (this->engine != nullptr) { - qFatal() << this << "destroyed without calling destroy()"; - } + g_generations.remove(this->engine); + delete this->engine; } void EngineGeneration::destroy() { - if (this->watcher != nullptr) { - // Multiple generations can detect a reload at the same time. - QObject::disconnect(this->watcher, nullptr, this, nullptr); - this->watcher->deleteLater(); - this->watcher = nullptr; - } + // Multiple generations can detect a reload at the same time. + delete this->watcher; + this->watcher = nullptr; - if (this->root != nullptr) { + // Yes all of this is actually necessary. + if (this->engine != nullptr && this->root != nullptr) { QObject::connect(this->root, &QObject::destroyed, this, [this]() { - // prevent further js execution between garbage collection and engine destruction. - this->engine->setInterrupted(true); + // The timer seems to fix *one* of the possible qml item destructor crashes. + QTimer::singleShot(0, [this]() { + // Garbage is not collected during engine destruction. + this->engine->collectGarbage(); - g_generations.remove(this->engine); + QObject::connect(this->engine, &QObject::destroyed, this, [this]() { delete this; }); - // Garbage is not collected during engine destruction. - this->engine->collectGarbage(); - - delete this->engine; - this->engine = nullptr; - delete this; + // Even after all of that there's still multiple failing assertions and segfaults. + // Pray you don't hit one. + // Note: it appeats *some* of the crashes are related to values owned by the generation. + // Test by commenting the connect() above. + this->engine->deleteLater(); + this->engine = nullptr; + }); }); this->root->deleteLater(); this->root = nullptr; - } else { - // the engine has never been used, no need to clean up - delete this->engine; - this->engine = nullptr; - delete this; } } @@ -125,21 +117,13 @@ void EngineGeneration::setWatchingFiles(bool watching) { for (auto& file: this->scanner.scannedFiles) { this->watcher->addPath(file); - this->watcher->addPath(QFileInfo(file).dir().absolutePath()); } QObject::connect( this->watcher, &QFileSystemWatcher::fileChanged, this, - &EngineGeneration::onFileChanged - ); - - QObject::connect( - this->watcher, - &QFileSystemWatcher::directoryChanged, - this, - &EngineGeneration::onDirectoryChanged + &EngineGeneration::filesChanged ); } } else { @@ -150,24 +134,6 @@ void EngineGeneration::setWatchingFiles(bool watching) { } } -void EngineGeneration::onFileChanged(const QString& name) { - if (!this->watcher->files().contains(name)) { - this->deletedWatchedFiles.push_back(name); - } else { - emit this->filesChanged(); - } -} - -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()) { - emit this->filesChanged(); - break; - } - } -} - void EngineGeneration::registerIncubationController(QQmlIncubationController* controller) { auto* obj = dynamic_cast(controller); @@ -269,16 +235,12 @@ void EngineGeneration::assignIncubationController() { this->engine->setIncubationController(controller); } -EngineGeneration* EngineGeneration::findEngineGeneration(QQmlEngine* engine) { - return g_generations.value(engine); -} - EngineGeneration* EngineGeneration::findObjectGeneration(QObject* object) { while (object != nullptr) { auto* context = QQmlEngine::contextForObject(object); if (context != nullptr) { - if (auto* generation = EngineGeneration::findEngineGeneration(context->engine())) { + if (auto* generation = g_generations.value(context->engine())) { return generation; } } diff --git a/src/core/generation.hpp b/src/core/generation.hpp index f757113e..11ebf0be 100644 --- a/src/core/generation.hpp +++ b/src/core/generation.hpp @@ -1,11 +1,9 @@ #pragma once #include -#include #include #include #include -#include #include #include @@ -16,13 +14,12 @@ #include "singleton.hpp" class RootWrapper; -class QuickshellGlobal; class EngineGeneration: public QObject { Q_OBJECT; public: - explicit EngineGeneration(const QDir& rootPath, QmlScanner scanner); + explicit EngineGeneration(QmlScanner scanner); ~EngineGeneration() override; Q_DISABLE_COPY_MOVE(EngineGeneration); @@ -33,11 +30,9 @@ public: void registerIncubationController(QQmlIncubationController* controller); void deregisterIncubationController(QQmlIncubationController* controller); - static EngineGeneration* findEngineGeneration(QQmlEngine* engine); static EngineGeneration* findObjectGeneration(QObject* object); RootWrapper* wrapper = nullptr; - QDir rootPath; QmlScanner scanner; QsUrlInterceptor urlInterceptor; QsInterceptNetworkAccessManagerFactory interceptNetFactory; @@ -45,10 +40,8 @@ public: ShellRoot* root = nullptr; SingletonRegistry singletonRegistry; QFileSystemWatcher* watcher = nullptr; - QVector deletedWatchedFiles; DelayedQmlIncubationController delayedIncubationController; bool reloadComplete = false; - QuickshellGlobal* qsgInstance = nullptr; void destroy(); @@ -57,8 +50,6 @@ signals: void reloadFinished(); private slots: - void onFileChanged(const QString& name); - void onDirectoryChanged(); void incubationControllerDestroyed(); private: diff --git a/src/core/main.cpp b/src/core/main.cpp index 220bde30..2cfd4d9c 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -30,9 +29,6 @@ int qs_main(int argc, char** argv) { auto desktopSettingsAware = true; QHash envOverrides; - int debugPort = -1; - bool waitForDebug = false; - { const auto app = QCoreApplication(argc, argv); QCoreApplication::setApplicationName("quickshell"); @@ -48,8 +44,6 @@ int qs_main(int argc, char** argv) { auto configOption = QCommandLineOption({"c", "config"}, "Name of a configuration in the manifest.", "name"); auto pathOption = QCommandLineOption({"p", "path"}, "Path to a configuration file.", "path"); auto workdirOption = QCommandLineOption({"d", "workdir"}, "Initial working directory.", "path"); - auto debugPortOption = QCommandLineOption("debugport", "Enable the QML debugger.", "port"); - auto debugWaitOption = QCommandLineOption("waitfordebug", "Wait for debugger connection before launching."); // clang-format on parser.addOption(currentOption); @@ -57,30 +51,8 @@ int qs_main(int argc, char** argv) { parser.addOption(configOption); parser.addOption(pathOption); parser.addOption(workdirOption); - parser.addOption(debugPortOption); - parser.addOption(debugWaitOption); parser.process(app); - auto debugPortStr = parser.value(debugPortOption); - if (!debugPortStr.isEmpty()) { - auto ok = false; - debugPort = debugPortStr.toInt(&ok); - - if (!ok) { - qCritical() << "Debug port must be a valid port number."; - return -1; - } - } - - if (parser.isSet(debugWaitOption)) { - if (debugPort == -1) { - qCritical() << "Cannot wait for debugger without a debug port set."; - return -1; - } - - waitForDebug = true; - } - { auto printCurrent = parser.isSet(currentOption); @@ -326,13 +298,6 @@ int qs_main(int argc, char** argv) { qputenv(var.toUtf8(), val.toUtf8()); } - // The simple animation driver seems to work far better than the default one - // when more than one window is in use, and even with a single window appears - // to improve animation quality. - if (!qEnvironmentVariableIsSet("QSG_USE_SIMPLE_ANIMATION_DRIVER")) { - qputenv("QSG_USE_SIMPLE_ANIMATION_DRIVER", "1"); - } - QGuiApplication::setDesktopSettingsAware(desktopSettingsAware); QGuiApplication* app = nullptr; @@ -343,13 +308,6 @@ int qs_main(int argc, char** argv) { app = new QGuiApplication(argc, argv); } - if (debugPort != -1) { - QQmlDebuggingEnabler::enableDebugging(true); - auto wait = waitForDebug ? QQmlDebuggingEnabler::WaitForClient - : QQmlDebuggingEnabler::DoNotWaitForClient; - QQmlDebuggingEnabler::startTcpDebugServer(debugPort, wait); - } - if (!workingDirectory.isEmpty()) { QDir::setCurrent(workingDirectory); } diff --git a/src/core/model.cpp b/src/core/model.cpp deleted file mode 100644 index 64f7d765..00000000 --- a/src/core/model.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include "model.hpp" - -#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 != 0) return QVariant(); - return QVariant::fromValue(this->valuesList.at(index.row())); -} - -QHash UntypedObjectModel::roleNames() const { return {{0, "modelData"}}; } - -QQmlListProperty UntypedObjectModel::values() { - return QQmlListProperty( - this, - nullptr, - &UntypedObjectModel::valuesCount, - &UntypedObjectModel::valueAt - ); -} - -qsizetype UntypedObjectModel::valuesCount(QQmlListProperty* property) { - return static_cast(property->object)->valuesList.count(); // NOLINT -} - -QObject* UntypedObjectModel::valueAt(QQmlListProperty* property, qsizetype index) { - return static_cast(property->object)->valuesList.at(index); // NOLINT -} - -void UntypedObjectModel::insertObject(QObject* object, qsizetype index) { - auto iindex = index == -1 ? this->valuesList.length() : index; - emit this->objectInsertedPre(object, index); - - auto intIndex = static_cast(iindex); - this->beginInsertRows(QModelIndex(), intIndex, intIndex); - this->valuesList.insert(iindex, object); - this->endInsertRows(); - - emit this->valuesChanged(); - emit this->objectInsertedPost(object, index); -} - -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; -} - -qsizetype UntypedObjectModel::indexOf(QObject* object) { return this->valuesList.indexOf(object); } diff --git a/src/core/model.hpp b/src/core/model.hpp deleted file mode 100644 index 10465bba..00000000 --- a/src/core/model.hpp +++ /dev/null @@ -1,94 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "doc.hpp" - -///! View into a list of objets -/// Typed view into a list of objects. -/// -/// An ObjectModel works as a QML [Data Model], allowing efficient interaction with -/// components that act on models. It has a single role named `modelData`, to match the -/// behavior of lists. -/// The same information contained in the list model is available as a normal list -/// via the `values` property. -/// -/// #### Differences from a list -/// Unlike with a list, the following property binding will never be updated when `model[3]` changes. -/// ```qml -/// // will not update reactively -/// property var foo: model[3] -/// ``` -/// -/// You can work around this limitation using the `values` property of the model to view it as a list. -/// ```qml -/// // will update reactively -/// property var foo: model.values[3] -/// ``` -/// -/// [Data Model]: https://doc.qt.io/qt-6/qtquick-modelviewsdata-modelview.html#qml-data-models -class UntypedObjectModel: public QAbstractListModel { - QSDOC_CNAME(ObjectModel); - Q_OBJECT; - /// The content of the object model, as a QML list. - /// The values of this property will always be of the type of the model. - Q_PROPERTY(QQmlListProperty values READ values NOTIFY valuesChanged); - QML_NAMED_ELEMENT(ObjectModel); - QML_UNCREATABLE("ObjectModels cannot be created directly."); - -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]] QQmlListProperty values(); - void removeAt(qsizetype index); - - Q_INVOKABLE qsizetype indexOf(QObject* object); - -signals: - void valuesChanged(); - /// Sent immediately before an object is inserted into the list. - void objectInsertedPre(QObject* object, qsizetype index); - /// Sent immediately after an object is inserted into the list. - void objectInsertedPost(QObject* object, qsizetype index); - /// Sent immediately before an object is removed from the list. - void objectRemovedPre(QObject* object, qsizetype index); - /// 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); - - QVector valuesList; - -private: - static qsizetype valuesCount(QQmlListProperty* property); - static QObject* valueAt(QQmlListProperty* property, qsizetype index); -}; - -template -class ObjectModel: public UntypedObjectModel { -public: - explicit ObjectModel(QObject* parent): UntypedObjectModel(parent) {} - - [[nodiscard]] const QVector& valueList() const { - return *reinterpret_cast*>(&this->valuesList); // NOLINT - } - - void insertObject(T* object, qsizetype index = -1) { - this->UntypedObjectModel::insertObject(object, index); - } - - void removeObject(const T* object) { this->UntypedObjectModel::removeObject(object); } -}; diff --git a/src/core/module.md b/src/core/module.md index 13218610..8eb9b638 100644 --- a/src/core/module.md +++ b/src/core/module.md @@ -18,7 +18,5 @@ headers = [ "easingcurve.hpp", "transformwatcher.hpp", "boundcomponent.hpp", - "model.hpp", - "elapsedtimer.hpp", ] ----- diff --git a/src/core/proxywindow.cpp b/src/core/proxywindow.cpp index 4eef5f38..50370d9d 100644 --- a/src/core/proxywindow.cpp +++ b/src/core/proxywindow.cpp @@ -156,7 +156,16 @@ void ProxyWindowBase::completeWindow() { emit this->screenChanged(); } -bool ProxyWindowBase::deleteOnInvisible() const { return false; } +bool ProxyWindowBase::deleteOnInvisible() const { +#ifdef NVIDIA_COMPAT + // Nvidia drivers and Qt do not play nice when hiding and showing a window + // so for nvidia compatibility we can never reuse windows if they have been + // hidden. + return true; +#else + return false; +#endif +} QQuickWindow* ProxyWindowBase::backingWindow() const { return this->window; } QQuickItem* ProxyWindowBase::contentItem() const { return this->mContentItem; } diff --git a/src/core/qmlglobal.cpp b/src/core/qmlglobal.cpp index 05197f26..70d7b416 100644 --- a/src/core/qmlglobal.cpp +++ b/src/core/qmlglobal.cpp @@ -187,14 +187,3 @@ QVariant QuickshellGlobal::env(const QString& variable) { // NOLINT return qEnvironmentVariable(vstr.data()); } - -QuickshellGlobal* QuickshellGlobal::create(QQmlEngine* engine, QJSEngine* /*unused*/) { - auto* qsg = new QuickshellGlobal(); - auto* generation = EngineGeneration::findEngineGeneration(engine); - - if (generation->qsgInstance == nullptr) { - generation->qsgInstance = qsg; - } - - return qsg; -} diff --git a/src/core/qmlglobal.hpp b/src/core/qmlglobal.hpp index 8de55fc2..83ef68d4 100644 --- a/src/core/qmlglobal.hpp +++ b/src/core/qmlglobal.hpp @@ -110,6 +110,8 @@ class QuickshellGlobal: public QObject { public: [[nodiscard]] qint32 processId() const; + QuickshellGlobal(QObject* parent = nullptr); + QQmlListProperty screens(); /// Reload the shell from the [ShellRoot]. @@ -131,25 +133,17 @@ public: [[nodiscard]] bool watchFiles() const; void setWatchFiles(bool watchFiles); - static QuickshellGlobal* create(QQmlEngine* engine, QJSEngine* /*unused*/); - signals: /// Sent when the last window is closed. /// /// To make the application exit when the last window is closed run `Qt.quit()`. void lastWindowClosed(); - /// The reload sequence has completed successfully. - void reloadCompleted(); - /// The reload sequence has failed. - void reloadFailed(QString errorString); void screensChanged(); void workingDirectoryChanged(); void watchFilesChanged(); private: - QuickshellGlobal(QObject* parent = nullptr); - static qsizetype screensCount(QQmlListProperty* prop); static QuickshellScreenInfo* screenAt(QQmlListProperty* prop, qsizetype i); }; diff --git a/src/core/qsintercept.cpp b/src/core/qsintercept.cpp index ba46ab7b..2eaf498e 100644 --- a/src/core/qsintercept.cpp +++ b/src/core/qsintercept.cpp @@ -16,22 +16,7 @@ Q_LOGGING_CATEGORY(logQsIntercept, "quickshell.interceptor", QtWarningMsg); -QUrl QsUrlInterceptor::intercept( - const QUrl& originalUrl, - QQmlAbstractUrlInterceptor::DataType type -) { - auto url = originalUrl; - - if (url.scheme() == "root") { - url.setScheme("qsintercept"); - - auto path = url.path(); - if (path.startsWith('/')) path = path.sliced(1); - url.setPath(this->configRoot.filePath(path)); - - qCDebug(logQsIntercept) << "Rewrote root intercept" << originalUrl << "to" << url; - } - +QUrl QsUrlInterceptor::intercept(const QUrl& url, QQmlAbstractUrlInterceptor::DataType type) { // Some types such as Image take into account where they are loading from, and force // asynchronous loading over a network. qsintercept is considered to be over a network. if (type == QQmlAbstractUrlInterceptor::DataType::UrlString && url.scheme() == "qsintercept") { diff --git a/src/core/qsintercept.hpp b/src/core/qsintercept.hpp index 57923568..d51b78e6 100644 --- a/src/core/qsintercept.hpp +++ b/src/core/qsintercept.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -14,12 +13,7 @@ Q_DECLARE_LOGGING_CATEGORY(logQsIntercept); class QsUrlInterceptor: public QQmlAbstractUrlInterceptor { public: - explicit QsUrlInterceptor(const QDir& configRoot): configRoot(configRoot) {} - - QUrl intercept(const QUrl& originalUrl, QQmlAbstractUrlInterceptor::DataType type) override; - -private: - QDir configRoot; + QUrl intercept(const QUrl& url, QQmlAbstractUrlInterceptor::DataType type) override; }; class QsInterceptDataReply: public QNetworkReply { diff --git a/src/core/rootwrapper.cpp b/src/core/rootwrapper.cpp index 1afb30cf..ed2ef4b7 100644 --- a/src/core/rootwrapper.cpp +++ b/src/core/rootwrapper.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include "generation.hpp" @@ -43,11 +42,10 @@ RootWrapper::~RootWrapper() { } void RootWrapper::reloadGraph(bool hard) { - auto rootPath = QFileInfo(this->rootPath).dir(); - auto scanner = QmlScanner(rootPath); + auto scanner = QmlScanner(); scanner.scanQmlFile(this->rootPath); - auto* generation = new EngineGeneration(rootPath, std::move(scanner)); + auto* generation = new EngineGeneration(std::move(scanner)); generation->wrapper = this; // todo: move into EngineGeneration @@ -65,28 +63,17 @@ void RootWrapper::reloadGraph(bool hard) { auto* obj = component.beginCreate(generation->engine->rootContext()); if (obj == nullptr) { - const QString error = "failed to create root component\n" + component.errorString(); - qWarning().noquote() << error; - generation->destroy(); - - if (this->generation != nullptr && this->generation->qsgInstance != nullptr) { - emit this->generation->qsgInstance->reloadFailed(error); - } - + qWarning() << component.errorString().toStdString().c_str(); + qWarning() << "failed to create root component"; + delete generation; return; } auto* newRoot = qobject_cast(obj); if (newRoot == nullptr) { - const QString error = "root component was not a Quickshell.ShellRoot"; - qWarning().noquote() << error; + qWarning() << "root component was not a Quickshell.ShellRoot"; delete obj; - generation->destroy(); - - if (this->generation != nullptr && this->generation->qsgInstance != nullptr) { - emit this->generation->qsgInstance->reloadFailed(error); - } - + delete generation; return; } @@ -94,13 +81,8 @@ void RootWrapper::reloadGraph(bool hard) { component.completeCreate(); - auto isReload = this->generation != nullptr; generation->onReload(hard ? nullptr : this->generation); - - if (hard && this->generation != nullptr) { - this->generation->destroy(); - } - + if (hard) delete this->generation; this->generation = generation; qInfo() << "Configuration Loaded"; @@ -113,10 +95,6 @@ void RootWrapper::reloadGraph(bool hard) { ); this->onWatchFilesChanged(); - - if (isReload && this->generation->qsgInstance != nullptr) { - emit this->generation->qsgInstance->reloadCompleted(); - } } void RootWrapper::onWatchFilesChanged() { diff --git a/src/core/scan.cpp b/src/core/scan.cpp index 59ec05b6..f5f078aa 100644 --- a/src/core/scan.cpp +++ b/src/core/scan.cpp @@ -103,15 +103,7 @@ bool QmlScanner::scanQmlFile(const QString& path) { this->scanDir(currentdir.path()); for (auto& import: imports) { - QString ipath; - if (import.startsWith("root:")) { - auto path = import.sliced(5); - if (path.startsWith('/')) path = path.sliced(1); - ipath = this->rootPath.filePath(path); - } else { - ipath = currentdir.filePath(import); - } - + auto ipath = currentdir.filePath(import); auto cpath = QFileInfo(ipath).canonicalFilePath(); if (cpath.isEmpty()) { diff --git a/src/core/scan.hpp b/src/core/scan.hpp index e3071a88..32a6166d 100644 --- a/src/core/scan.hpp +++ b/src/core/scan.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include #include @@ -11,8 +10,6 @@ Q_DECLARE_LOGGING_CATEGORY(logQmlScanner); // expects canonical paths class QmlScanner { public: - QmlScanner(const QDir& rootPath): rootPath(rootPath) {} - void scanDir(const QString& path); // returns if the file has a singleton bool scanQmlFile(const QString& path); @@ -20,7 +17,4 @@ public: QVector scannedDirs; QVector scannedFiles; QHash qmldirIntercepts; - -private: - QDir rootPath; }; diff --git a/src/core/transformwatcher.cpp b/src/core/transformwatcher.cpp index 2a33bad0..697dfc56 100644 --- a/src/core/transformwatcher.cpp +++ b/src/core/transformwatcher.cpp @@ -82,10 +82,7 @@ void TransformWatcher::linkItem(QQuickItem* item) const { QObject::connect(item, &QQuickItem::parentChanged, this, &TransformWatcher::recalcChains); QObject::connect(item, &QQuickItem::windowChanged, this, &TransformWatcher::recalcChains); - - if (item != this->mA && item != this->mB) { - QObject::connect(item, &QObject::destroyed, this, &TransformWatcher::itemDestroyed); - } + QObject::connect(item, &QObject::destroyed, this, &TransformWatcher::recalcChains); } void TransformWatcher::linkChains() { @@ -106,18 +103,6 @@ void TransformWatcher::unlinkChains() { for (auto* item: this->childChain) { QObject::disconnect(item, nullptr, this, nullptr); } - - // relink a and b destruction notifications - if (this->mA != nullptr) { - QObject::connect(this->mA, &QObject::destroyed, this, &TransformWatcher::aDestroyed); - } - - if (this->mB != nullptr) { - QObject::connect(this->mB, &QObject::destroyed, this, &TransformWatcher::bDestroyed); - } - - this->parentChain.clear(); - this->childChain.clear(); } void TransformWatcher::recalcChains() { @@ -126,57 +111,26 @@ void TransformWatcher::recalcChains() { this->linkChains(); } -void TransformWatcher::itemDestroyed() { - auto destroyed = - this->parentChain.removeOne(this->sender()) || this->childChain.removeOne(this->sender()); - - if (destroyed) this->recalcChains(); -} - QQuickItem* TransformWatcher::a() const { return this->mA; } void TransformWatcher::setA(QQuickItem* a) { if (this->mA == a) return; - if (this->mA != nullptr) QObject::disconnect(this->mA, nullptr, this, nullptr); this->mA = a; - - if (this->mA != nullptr) { - QObject::connect(this->mA, &QObject::destroyed, this, &TransformWatcher::aDestroyed); - } - this->recalcChains(); } -void TransformWatcher::aDestroyed() { - this->mA = nullptr; - this->unlinkChains(); - emit this->aChanged(); -} - QQuickItem* TransformWatcher::b() const { return this->mB; } void TransformWatcher::setB(QQuickItem* b) { if (this->mB == b) return; - if (this->mB != nullptr) QObject::disconnect(this->mB, nullptr, this, nullptr); this->mB = b; - - if (this->mB != nullptr) { - QObject::connect(this->mB, &QObject::destroyed, this, &TransformWatcher::bDestroyed); - } - this->recalcChains(); } -void TransformWatcher::bDestroyed() { - this->mB = nullptr; - this->unlinkChains(); - emit this->bChanged(); -} - QQuickItem* TransformWatcher::commonParent() const { return this->mCommonParent; } void TransformWatcher::setCommonParent(QQuickItem* commonParent) { if (this->mCommonParent == commonParent) return; this->mCommonParent = commonParent; - this->recalcChains(); + this->resolveChains(); } diff --git a/src/core/transformwatcher.hpp b/src/core/transformwatcher.hpp index 64bac4a1..d7174e4c 100644 --- a/src/core/transformwatcher.hpp +++ b/src/core/transformwatcher.hpp @@ -60,9 +60,6 @@ signals: private slots: void recalcChains(); - void itemDestroyed(); - void aDestroyed(); - void bDestroyed(); private: void resolveChains(QQuickItem* a, QQuickItem* b, QQuickItem* commonParent); diff --git a/src/services/mpris/player.cpp b/src/services/mpris/player.cpp index b659badf..3b0c7463 100644 --- a/src/services/mpris/player.cpp +++ b/src/services/mpris/player.cpp @@ -60,7 +60,6 @@ MprisPlayer::MprisPlayer(const QString& address, QObject* parent): QObject(paren QObject::connect(&this->pCanRaise, &AbstractDBusProperty::changed, this, &MprisPlayer::canRaiseChanged); QObject::connect(&this->pCanSetFullscreen, &AbstractDBusProperty::changed, this, &MprisPlayer::canSetFullscreenChanged); QObject::connect(&this->pIdentity, &AbstractDBusProperty::changed, this, &MprisPlayer::identityChanged); - QObject::connect(&this->pDesktopEntry, &AbstractDBusProperty::changed, this, &MprisPlayer::desktopEntryChanged); QObject::connect(&this->pFullscreen, &AbstractDBusProperty::changed, this, &MprisPlayer::fullscreenChanged); QObject::connect(&this->pSupportedUriSchemes, &AbstractDBusProperty::changed, this, &MprisPlayer::supportedUriSchemesChanged); QObject::connect(&this->pSupportedMimeTypes, &AbstractDBusProperty::changed, this, &MprisPlayer::supportedMimeTypesChanged); @@ -156,7 +155,6 @@ bool MprisPlayer::canRaise() const { return this->pCanRaise.get(); } bool MprisPlayer::canSetFullscreen() const { return this->pCanSetFullscreen.get(); } QString MprisPlayer::identity() const { return this->pIdentity.get(); } -QString MprisPlayer::desktopEntry() const { return this->pDesktopEntry.get(); } qlonglong MprisPlayer::positionMs() const { if (!this->positionSupported()) return 0; // unsupported @@ -192,15 +190,15 @@ void MprisPlayer::setPosition(qreal position) { } auto target = static_cast(position * 1000) * 1000; + this->pPosition.set(target); if (!this->mTrackId.isEmpty()) { this->player->SetPosition(QDBusObjectPath(this->mTrackId), target); + return; } else { auto pos = this->positionMs() * 1000; this->player->Seek(target - pos); } - - this->pPosition.set(target); } void MprisPlayer::onPositionChanged() { @@ -247,8 +245,6 @@ void MprisPlayer::setVolume(qreal volume) { QVariantMap MprisPlayer::metadata() const { return this->pMetadata.get(); } void MprisPlayer::onMetadataChanged() { - emit this->metadataChanged(); - auto lengthVariant = this->pMetadata.get().value("mpris:length"); qlonglong length = -1; if (lengthVariant.isValid() && lengthVariant.canConvert()) { @@ -260,34 +256,13 @@ void MprisPlayer::onMetadataChanged() { emit this->lengthChanged(); } - auto trackChanged = false; - auto trackidVariant = this->pMetadata.get().value("mpris:trackid"); if (trackidVariant.isValid() && trackidVariant.canConvert()) { - auto trackId = trackidVariant.value(); - - if (trackId != this->mTrackId) { - this->mTrackId = trackId; - trackChanged = true; - } + this->mTrackId = trackidVariant.value(); + this->onSeek(0); } - // Helps to catch players without trackid. - auto urlVariant = this->pMetadata.get().value("xesam:url"); - if (urlVariant.isValid() && urlVariant.canConvert()) { - auto url = urlVariant.value(); - - if (url != this->mUrl) { - this->mUrl = url; - trackChanged = true; - } - } - - if (trackChanged) { - // Some players don't seem to send position updates or seeks on track change. - this->pPosition.update(); - emit this->trackChanged(); - } + emit this->metadataChanged(); } MprisPlaybackState::Enum MprisPlayer::playbackState() const { return this->mPlaybackState; } @@ -330,25 +305,18 @@ void MprisPlayer::setPlaybackState(MprisPlaybackState::Enum playbackState) { void MprisPlayer::onPlaybackStatusChanged() { const auto& status = this->pPlaybackStatus.get(); - auto state = MprisPlaybackState::Stopped; if (status == "Playing") { - state = MprisPlaybackState::Playing; + this->mPlaybackState = MprisPlaybackState::Playing; } else if (status == "Paused") { - this->pausedTime = QDateTime::currentDateTimeUtc(); - state = MprisPlaybackState::Paused; + this->mPlaybackState = MprisPlaybackState::Paused; } else if (status == "Stopped") { - state = MprisPlaybackState::Stopped; + this->mPlaybackState = MprisPlaybackState::Stopped; } else { - state = MprisPlaybackState::Stopped; + this->mPlaybackState = MprisPlaybackState::Stopped; qWarning() << "Received unexpected PlaybackStatus for" << this << status; } - if (state != this->mPlaybackState) { - // make sure we're in sync at least on play/pause. Some players don't automatically send this. - this->pPosition.update(); - this->mPlaybackState = state; - emit this->playbackStateChanged(); - } + emit this->playbackStateChanged(); } MprisLoopState::Enum MprisPlayer::loopState() const { return this->mLoopState; } diff --git a/src/services/mpris/player.hpp b/src/services/mpris/player.hpp index 1172505a..0b18d78c 100644 --- a/src/services/mpris/player.hpp +++ b/src/services/mpris/player.hpp @@ -68,8 +68,6 @@ class MprisPlayer: public QObject { Q_PROPERTY(bool canSetFullscreen READ canSetFullscreen NOTIFY canSetFullscreenChanged); /// The human readable name of the media player. Q_PROPERTY(QString identity READ identity NOTIFY identityChanged); - /// The name of the desktop entry for the media player, or an empty string if not provided. - Q_PROPERTY(QString desktopEntry READ desktopEntry NOTIFY desktopEntryChanged); /// The current position in the playing track, as seconds, with millisecond precision, /// or `0` if `positionSupported` is false. /// @@ -206,7 +204,6 @@ public: [[nodiscard]] bool canSetFullscreen() const; [[nodiscard]] QString identity() const; - [[nodiscard]] QString desktopEntry() const; [[nodiscard]] qlonglong positionMs() const; [[nodiscard]] qreal position() const; @@ -245,8 +242,6 @@ public: [[nodiscard]] QList supportedMimeTypes() const; signals: - void trackChanged(); - QSDOC_HIDE void ready(); void canControlChanged(); void canPlayChanged(); @@ -258,7 +253,6 @@ signals: void canRaiseChanged(); void canSetFullscreenChanged(); void identityChanged(); - void desktopEntryChanged(); void positionChanged(); void positionSupportedChanged(); void lengthChanged(); @@ -291,7 +285,6 @@ private: // clang-format off dbus::DBusPropertyGroup appProperties; dbus::DBusProperty pIdentity {this->appProperties, "Identity"}; - dbus::DBusProperty pDesktopEntry {this->appProperties, "DesktopEntry", "", false}; dbus::DBusProperty pCanQuit {this->appProperties, "CanQuit"}; dbus::DBusProperty pCanRaise {this->appProperties, "CanRaise"}; dbus::DBusProperty pFullscreen {this->appProperties, "Fullscreen", false, false}; @@ -326,7 +319,6 @@ private: DBusMprisPlayerApp* app = nullptr; DBusMprisPlayer* player = nullptr; QString mTrackId; - QString mUrl; }; } // namespace qs::service::mpris diff --git a/src/services/mpris/watcher.cpp b/src/services/mpris/watcher.cpp index 8a788933..1e107660 100644 --- a/src/services/mpris/watcher.cpp +++ b/src/services/mpris/watcher.cpp @@ -4,19 +4,21 @@ #include #include #include +#include #include #include #include #include +#include +#include -#include "../../core/model.hpp" #include "player.hpp" namespace qs::service::mpris { Q_LOGGING_CATEGORY(logMprisWatcher, "quickshell.service.mpris.watcher", QtWarningMsg); -MprisWatcher::MprisWatcher() { +MprisWatcher::MprisWatcher(QObject* parent): QObject(parent) { qCDebug(logMprisWatcher) << "Starting MprisWatcher"; auto bus = QDBusConnection::sessionBus(); @@ -72,15 +74,34 @@ void MprisWatcher::onServiceUnregistered(const QString& service) { void MprisWatcher::onPlayerReady() { auto* player = qobject_cast(this->sender()); - this->readyPlayers.insertObject(player); + this->readyPlayers.push_back(player); + emit this->playersChanged(); } void MprisWatcher::onPlayerDestroyed(QObject* object) { auto* player = static_cast(object); // NOLINT - this->readyPlayers.removeObject(player); + + if (this->readyPlayers.removeOne(player)) { + emit this->playersChanged(); + } } -ObjectModel* MprisWatcher::players() { return &this->readyPlayers; } +QQmlListProperty MprisWatcher::players() { + return QQmlListProperty( + this, + nullptr, + &MprisWatcher::playersCount, + &MprisWatcher::playerAt + ); +} + +qsizetype MprisWatcher::playersCount(QQmlListProperty* property) { + return static_cast(property->object)->readyPlayers.count(); // NOLINT +} + +MprisPlayer* MprisWatcher::playerAt(QQmlListProperty* property, qsizetype index) { + return static_cast(property->object)->readyPlayers.at(index); // NOLINT +} void MprisWatcher::registerPlayer(const QString& address) { if (this->mPlayers.contains(address)) { @@ -102,13 +123,4 @@ void MprisWatcher::registerPlayer(const QString& address) { qCDebug(logMprisWatcher) << "Registered MprisPlayer" << address; } -MprisWatcher* MprisWatcher::instance() { - static MprisWatcher* instance = new MprisWatcher(); // NOLINT - return instance; -} - -ObjectModel* MprisQml::players() { // NOLINT - return MprisWatcher::instance()->players(); -} - } // namespace qs::service::mpris diff --git a/src/services/mpris/watcher.hpp b/src/services/mpris/watcher.hpp index d60471cc..a1e4df7c 100644 --- a/src/services/mpris/watcher.hpp +++ b/src/services/mpris/watcher.hpp @@ -4,13 +4,14 @@ #include #include #include +#include #include #include #include #include #include +#include -#include "../../core/model.hpp" #include "player.hpp" namespace qs::service::mpris { @@ -18,11 +19,18 @@ namespace qs::service::mpris { ///! Provides access to MprisPlayers. class MprisWatcher: public QObject { Q_OBJECT; + QML_NAMED_ELEMENT(Mpris); + QML_SINGLETON; + /// All connected MPRIS players. + Q_PROPERTY(QQmlListProperty players READ players NOTIFY playersChanged); public: - [[nodiscard]] ObjectModel* players(); + explicit MprisWatcher(QObject* parent = nullptr); - static MprisWatcher* instance(); + [[nodiscard]] QQmlListProperty players(); + +signals: + void playersChanged(); private slots: void onServiceRegistered(const QString& service); @@ -31,27 +39,15 @@ private slots: void onPlayerDestroyed(QObject* object); private: - explicit MprisWatcher(); + static qsizetype playersCount(QQmlListProperty* property); + static MprisPlayer* playerAt(QQmlListProperty* property, qsizetype index); void registerExisting(); void registerPlayer(const QString& address); QDBusServiceWatcher serviceWatcher; QHash mPlayers; - ObjectModel readyPlayers {this}; -}; - -class MprisQml: public QObject { - Q_OBJECT; - QML_NAMED_ELEMENT(Mpris); - QML_SINGLETON; - /// All connected MPRIS players. - Q_PROPERTY(ObjectModel* players READ players CONSTANT); - -public: - explicit MprisQml(QObject* parent = nullptr): QObject(parent) {}; - - [[nodiscard]] ObjectModel* players(); + QList readyPlayers; }; } // namespace qs::service::mpris diff --git a/src/services/pipewire/qml.cpp b/src/services/pipewire/qml.cpp index b40de687..a6617d29 100644 --- a/src/services/pipewire/qml.cpp +++ b/src/services/pipewire/qml.cpp @@ -8,7 +8,6 @@ #include #include -#include "../../core/model.hpp" #include "connection.hpp" #include "link.hpp" #include "metadata.hpp" @@ -66,43 +65,88 @@ Pipewire::Pipewire(QObject* parent): QObject(parent) { // clang-format on } -ObjectModel* Pipewire::nodes() { return &this->mNodes; } +QQmlListProperty Pipewire::nodes() { + return QQmlListProperty(this, nullptr, &Pipewire::nodesCount, &Pipewire::nodeAt); +} + +qsizetype Pipewire::nodesCount(QQmlListProperty* property) { + return static_cast(property->object)->mNodes.count(); // NOLINT +} + +PwNodeIface* Pipewire::nodeAt(QQmlListProperty* property, qsizetype index) { + return static_cast(property->object)->mNodes.at(index); // NOLINT +} void Pipewire::onNodeAdded(PwNode* node) { auto* iface = PwNodeIface::instance(node); QObject::connect(iface, &QObject::destroyed, this, &Pipewire::onNodeRemoved); - this->mNodes.insertObject(iface); + + this->mNodes.push_back(iface); + emit this->nodesChanged(); } void Pipewire::onNodeRemoved(QObject* object) { auto* iface = static_cast(object); // NOLINT - this->mNodes.removeObject(iface); + this->mNodes.removeOne(iface); + emit this->nodesChanged(); } -ObjectModel* Pipewire::links() { return &this->mLinks; } +QQmlListProperty Pipewire::links() { + return QQmlListProperty(this, nullptr, &Pipewire::linksCount, &Pipewire::linkAt); +} + +qsizetype Pipewire::linksCount(QQmlListProperty* property) { + return static_cast(property->object)->mLinks.count(); // NOLINT +} + +PwLinkIface* Pipewire::linkAt(QQmlListProperty* property, qsizetype index) { + return static_cast(property->object)->mLinks.at(index); // NOLINT +} void Pipewire::onLinkAdded(PwLink* link) { auto* iface = PwLinkIface::instance(link); QObject::connect(iface, &QObject::destroyed, this, &Pipewire::onLinkRemoved); - this->mLinks.insertObject(iface); + + this->mLinks.push_back(iface); + emit this->linksChanged(); } void Pipewire::onLinkRemoved(QObject* object) { auto* iface = static_cast(object); // NOLINT - this->mLinks.removeObject(iface); + this->mLinks.removeOne(iface); + emit this->linksChanged(); } -ObjectModel* Pipewire::linkGroups() { return &this->mLinkGroups; } +QQmlListProperty Pipewire::linkGroups() { + return QQmlListProperty( + this, + nullptr, + &Pipewire::linkGroupsCount, + &Pipewire::linkGroupAt + ); +} + +qsizetype Pipewire::linkGroupsCount(QQmlListProperty* property) { + return static_cast(property->object)->mLinkGroups.count(); // NOLINT +} + +PwLinkGroupIface* +Pipewire::linkGroupAt(QQmlListProperty* property, qsizetype index) { + return static_cast(property->object)->mLinkGroups.at(index); // NOLINT +} void Pipewire::onLinkGroupAdded(PwLinkGroup* linkGroup) { auto* iface = PwLinkGroupIface::instance(linkGroup); QObject::connect(iface, &QObject::destroyed, this, &Pipewire::onLinkGroupRemoved); - this->mLinkGroups.insertObject(iface); + + this->mLinkGroups.push_back(iface); + emit this->linkGroupsChanged(); } void Pipewire::onLinkGroupRemoved(QObject* object) { auto* iface = static_cast(object); // NOLINT - this->mLinkGroups.removeObject(iface); + this->mLinkGroups.removeOne(iface); + emit this->linkGroupsChanged(); } PwNodeIface* Pipewire::defaultAudioSink() const { // NOLINT diff --git a/src/services/pipewire/qml.hpp b/src/services/pipewire/qml.hpp index 8d456419..9b452727 100644 --- a/src/services/pipewire/qml.hpp +++ b/src/services/pipewire/qml.hpp @@ -8,7 +8,6 @@ #include #include -#include "../../core/model.hpp" #include "link.hpp" #include "node.hpp" #include "registry.hpp" @@ -53,11 +52,11 @@ class Pipewire: public QObject { Q_OBJECT; // clang-format off /// All pipewire nodes. - Q_PROPERTY(ObjectModel* nodes READ nodes CONSTANT); + Q_PROPERTY(QQmlListProperty nodes READ nodes NOTIFY nodesChanged); /// All pipewire links. - Q_PROPERTY(ObjectModel* links READ links CONSTANT); + Q_PROPERTY(QQmlListProperty links READ links NOTIFY linksChanged); /// All pipewire link groups. - Q_PROPERTY(ObjectModel* linkGroups READ linkGroups CONSTANT); + Q_PROPERTY(QQmlListProperty linkGroups READ linkGroups NOTIFY linkGroupsChanged); /// The default audio sink or `null`. Q_PROPERTY(PwNodeIface* defaultAudioSink READ defaultAudioSink NOTIFY defaultAudioSinkChanged); /// The default audio source or `null`. @@ -69,13 +68,16 @@ class Pipewire: public QObject { public: explicit Pipewire(QObject* parent = nullptr); - [[nodiscard]] ObjectModel* nodes(); - [[nodiscard]] ObjectModel* links(); - [[nodiscard]] ObjectModel* linkGroups(); + [[nodiscard]] QQmlListProperty nodes(); + [[nodiscard]] QQmlListProperty links(); + [[nodiscard]] QQmlListProperty linkGroups(); [[nodiscard]] PwNodeIface* defaultAudioSink() const; [[nodiscard]] PwNodeIface* defaultAudioSource() const; signals: + void nodesChanged(); + void linksChanged(); + void linkGroupsChanged(); void defaultAudioSinkChanged(); void defaultAudioSourceChanged(); @@ -88,9 +90,17 @@ private slots: void onLinkGroupRemoved(QObject* object); private: - ObjectModel mNodes {this}; - ObjectModel mLinks {this}; - ObjectModel mLinkGroups {this}; + static qsizetype nodesCount(QQmlListProperty* property); + static PwNodeIface* nodeAt(QQmlListProperty* property, qsizetype index); + static qsizetype linksCount(QQmlListProperty* property); + static PwLinkIface* linkAt(QQmlListProperty* property, qsizetype index); + static qsizetype linkGroupsCount(QQmlListProperty* property); + static PwLinkGroupIface* + linkGroupAt(QQmlListProperty* property, qsizetype index); + + QVector mNodes; + QVector mLinks; + QVector mLinkGroups; }; ///! Tracks all link connections to a given node. diff --git a/src/services/status_notifier/qml.cpp b/src/services/status_notifier/qml.cpp index f81a6381..cea5646e 100644 --- a/src/services/status_notifier/qml.cpp +++ b/src/services/status_notifier/qml.cpp @@ -9,7 +9,6 @@ #include #include -#include "../../core/model.hpp" #include "../../dbus/dbusmenu/dbusmenu.hpp" #include "../../dbus/properties.hpp" #include "host.hpp" @@ -107,25 +106,46 @@ SystemTray::SystemTray(QObject* parent): QObject(parent) { // clang-format on for (auto* item: host->items()) { - this->mItems.insertObject(new SystemTrayItem(item, this)); + this->mItems.push_back(new SystemTrayItem(item, this)); } } void SystemTray::onItemRegistered(StatusNotifierItem* item) { - this->mItems.insertObject(new SystemTrayItem(item, this)); + this->mItems.push_back(new SystemTrayItem(item, this)); + emit this->itemsChanged(); } void SystemTray::onItemUnregistered(StatusNotifierItem* item) { - for (const auto* storedItem: this->mItems.valueList()) { - if (storedItem->item == item) { - this->mItems.removeObject(storedItem); - delete storedItem; - break; - } - } + SystemTrayItem* trayItem = nullptr; + + this->mItems.removeIf([item, &trayItem](SystemTrayItem* testItem) { + if (testItem->item == item) { + trayItem = testItem; + return true; + } else return false; + }); + + emit this->itemsChanged(); + + delete trayItem; } -ObjectModel* SystemTray::items() { return &this->mItems; } +QQmlListProperty SystemTray::items() { + return QQmlListProperty( + this, + nullptr, + &SystemTray::itemsCount, + &SystemTray::itemAt + ); +} + +qsizetype SystemTray::itemsCount(QQmlListProperty* property) { + return reinterpret_cast(property->object)->mItems.count(); // NOLINT +} + +SystemTrayItem* SystemTray::itemAt(QQmlListProperty* property, qsizetype index) { + return reinterpret_cast(property->object)->mItems.at(index); // NOLINT +} SystemTrayItem* SystemTrayMenuWatcher::trayItem() const { return this->item; } diff --git a/src/services/status_notifier/qml.hpp b/src/services/status_notifier/qml.hpp index e55509df..01f6bb05 100644 --- a/src/services/status_notifier/qml.hpp +++ b/src/services/status_notifier/qml.hpp @@ -2,9 +2,10 @@ #include #include +#include #include +#include -#include "../../core/model.hpp" #include "item.hpp" namespace SystemTrayStatus { // NOLINT @@ -107,21 +108,27 @@ signals: class SystemTray: public QObject { Q_OBJECT; /// List of all system tray icons. - Q_PROPERTY(ObjectModel* items READ items CONSTANT); + Q_PROPERTY(QQmlListProperty items READ items NOTIFY itemsChanged); QML_ELEMENT; QML_SINGLETON; public: explicit SystemTray(QObject* parent = nullptr); - [[nodiscard]] ObjectModel* items(); + [[nodiscard]] QQmlListProperty items(); + +signals: + void itemsChanged(); private slots: void onItemRegistered(qs::service::sni::StatusNotifierItem* item); void onItemUnregistered(qs::service::sni::StatusNotifierItem* item); private: - ObjectModel mItems {this}; + static qsizetype itemsCount(QQmlListProperty* property); + static SystemTrayItem* itemAt(QQmlListProperty* property, qsizetype index); + + QList mItems; }; ///! Accessor for SystemTrayItem menus. diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt index ac8f42bb..48140a91 100644 --- a/src/wayland/CMakeLists.txt +++ b/src/wayland/CMakeLists.txt @@ -51,19 +51,16 @@ endfunction() # ----- qt_add_library(quickshell-wayland STATIC) +qt_add_qml_module(quickshell-wayland URI Quickshell.Wayland VERSION 0.1) # required to make sure the constructor is linked add_library(quickshell-wayland-init OBJECT init.cpp) -set(WAYLAND_MODULES) - if (WAYLAND_WLR_LAYERSHELL) target_sources(quickshell-wayland PRIVATE wlr_layershell.cpp) add_subdirectory(wlr_layershell) target_compile_definitions(quickshell-wayland PRIVATE QS_WAYLAND_WLR_LAYERSHELL) target_compile_definitions(quickshell-wayland-init PRIVATE QS_WAYLAND_WLR_LAYERSHELL) - - list(APPEND WAYLAND_MODULES Quickshell.Wayland._WlrLayerShell) endif() if (WAYLAND_SESSION_LOCK) @@ -71,11 +68,6 @@ if (WAYLAND_SESSION_LOCK) add_subdirectory(session_lock) endif() -if (WAYLAND_TOPLEVEL_MANAGEMENT) - add_subdirectory(toplevel_management) - list(APPEND WAYLAND_MODULES Quickshell.Wayland._ToplevelManagement) -endif() - if (HYPRLAND) add_subdirectory(hyprland) endif() @@ -83,12 +75,6 @@ endif() target_link_libraries(quickshell-wayland PRIVATE ${QT_DEPS}) target_link_libraries(quickshell-wayland-init PRIVATE ${QT_DEPS}) -qt_add_qml_module(quickshell-wayland - URI Quickshell.Wayland - VERSION 0.1 - IMPORTS ${WAYLAND_MODULES} -) - qs_pch(quickshell-wayland) qs_pch(quickshell-waylandplugin) qs_pch(quickshell-wayland-init) diff --git a/src/wayland/hyprland/CMakeLists.txt b/src/wayland/hyprland/CMakeLists.txt index be2f0c59..06121a7e 100644 --- a/src/wayland/hyprland/CMakeLists.txt +++ b/src/wayland/hyprland/CMakeLists.txt @@ -1,29 +1,15 @@ qt_add_library(quickshell-hyprland STATIC) - -target_link_libraries(quickshell-hyprland PRIVATE ${QT_DEPS}) - -set(HYPRLAND_MODULES) - -if (HYPRLAND_IPC) - add_subdirectory(ipc) - list(APPEND HYPRLAND_MODULES Quickshell.Hyprland._Ipc) -endif() +qt_add_qml_module(quickshell-hyprland URI Quickshell.Hyprland VERSION 0.1) if (HYPRLAND_FOCUS_GRAB) add_subdirectory(focus_grab) - list(APPEND HYPRLAND_MODULES Quickshell.Hyprland._FocusGrab) endif() if (HYPRLAND_GLOBAL_SHORTCUTS) add_subdirectory(global_shortcuts) - list(APPEND HYPRLAND_MODULES Quickshell.Hyprland._GlobalShortcuts) endif() -qt_add_qml_module(quickshell-hyprland - URI Quickshell.Hyprland - VERSION 0.1 - IMPORTS ${HYPRLAND_MODULES} -) +target_link_libraries(quickshell-hyprland PRIVATE ${QT_DEPS}) qs_pch(quickshell-hyprland) qs_pch(quickshell-hyprlandplugin) diff --git a/src/wayland/hyprland/focus_grab/CMakeLists.txt b/src/wayland/hyprland/focus_grab/CMakeLists.txt index 1e37c9fe..587ae939 100644 --- a/src/wayland/hyprland/focus_grab/CMakeLists.txt +++ b/src/wayland/hyprland/focus_grab/CMakeLists.txt @@ -9,14 +9,21 @@ qt_add_qml_module(quickshell-hyprland-focus-grab VERSION 0.1 ) +add_library(quickshell-hyprland-focus-grab-init OBJECT init.cpp) + wl_proto(quickshell-hyprland-focus-grab hyprland-focus-grab-v1 "${CMAKE_CURRENT_SOURCE_DIR}/hyprland-focus-grab-v1.xml" ) target_link_libraries(quickshell-hyprland-focus-grab PRIVATE ${QT_DEPS} wayland-client) +target_link_libraries(quickshell-hyprland-focus-grab-init PRIVATE ${QT_DEPS}) qs_pch(quickshell-hyprland-focus-grab) qs_pch(quickshell-hyprland-focus-grabplugin) +qs_pch(quickshell-hyprland-focus-grab-init) -target_link_libraries(quickshell PRIVATE quickshell-hyprland-focus-grabplugin) +target_link_libraries(quickshell PRIVATE + quickshell-hyprland-focus-grabplugin + quickshell-hyprland-focus-grab-init +) diff --git a/src/wayland/hyprland/focus_grab/init.cpp b/src/wayland/hyprland/focus_grab/init.cpp new file mode 100644 index 00000000..784c7f26 --- /dev/null +++ b/src/wayland/hyprland/focus_grab/init.cpp @@ -0,0 +1,20 @@ +#include + +#include "../../../core/plugin.hpp" + +namespace { + +class HyprlandFocusGrabPlugin: public QuickshellPlugin { + void registerTypes() override { + qmlRegisterModuleImport( + "Quickshell.Hyprland", + QQmlModuleImportModuleAny, + "Quickshell.Hyprland._FocusGrab", + QQmlModuleImportLatest + ); + } +}; + +QS_REGISTER_PLUGIN(HyprlandFocusGrabPlugin); + +} // namespace diff --git a/src/wayland/hyprland/global_shortcuts/CMakeLists.txt b/src/wayland/hyprland/global_shortcuts/CMakeLists.txt index 2ccfb74d..804c0a3c 100644 --- a/src/wayland/hyprland/global_shortcuts/CMakeLists.txt +++ b/src/wayland/hyprland/global_shortcuts/CMakeLists.txt @@ -9,14 +9,21 @@ qt_add_qml_module(quickshell-hyprland-global-shortcuts VERSION 0.1 ) +add_library(quickshell-hyprland-global-shortcuts-init OBJECT init.cpp) + wl_proto(quickshell-hyprland-global-shortcuts hyprland-global-shortcuts-v1 "${CMAKE_CURRENT_SOURCE_DIR}/hyprland-global-shortcuts-v1.xml" ) target_link_libraries(quickshell-hyprland-global-shortcuts PRIVATE ${QT_DEPS} wayland-client) +target_link_libraries(quickshell-hyprland-global-shortcuts-init PRIVATE ${QT_DEPS}) qs_pch(quickshell-hyprland-global-shortcuts) qs_pch(quickshell-hyprland-global-shortcutsplugin) +qs_pch(quickshell-hyprland-global-shortcuts-init) -target_link_libraries(quickshell PRIVATE quickshell-hyprland-global-shortcutsplugin) +target_link_libraries(quickshell PRIVATE + quickshell-hyprland-global-shortcutsplugin + quickshell-hyprland-global-shortcuts-init +) diff --git a/src/wayland/hyprland/global_shortcuts/init.cpp b/src/wayland/hyprland/global_shortcuts/init.cpp new file mode 100644 index 00000000..12fed07f --- /dev/null +++ b/src/wayland/hyprland/global_shortcuts/init.cpp @@ -0,0 +1,20 @@ +#include + +#include "../../../core/plugin.hpp" + +namespace { + +class HyprlandFocusGrabPlugin: public QuickshellPlugin { + void registerTypes() override { + qmlRegisterModuleImport( + "Quickshell.Hyprland", + QQmlModuleImportModuleAny, + "Quickshell.Hyprland._GlobalShortcuts", + QQmlModuleImportLatest + ); + } +}; + +QS_REGISTER_PLUGIN(HyprlandFocusGrabPlugin); + +} // namespace diff --git a/src/wayland/hyprland/ipc/CMakeLists.txt b/src/wayland/hyprland/ipc/CMakeLists.txt deleted file mode 100644 index 59200462..00000000 --- a/src/wayland/hyprland/ipc/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -qt_add_library(quickshell-hyprland-ipc STATIC - connection.cpp - monitor.cpp - workspace.cpp - qml.cpp -) - -qt_add_qml_module(quickshell-hyprland-ipc - URI Quickshell.Hyprland._Ipc - VERSION 0.1 -) - -target_link_libraries(quickshell-hyprland-ipc PRIVATE ${QT_DEPS}) - -qs_pch(quickshell-hyprland-ipc) -qs_pch(quickshell-hyprland-ipcplugin) - -target_link_libraries(quickshell PRIVATE quickshell-hyprland-ipcplugin) diff --git a/src/wayland/hyprland/ipc/connection.cpp b/src/wayland/hyprland/ipc/connection.cpp deleted file mode 100644 index 5ee8fffe..00000000 --- a/src/wayland/hyprland/ipc/connection.cpp +++ /dev/null @@ -1,561 +0,0 @@ -#include "connection.hpp" -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../../core/model.hpp" -#include "../../../core/qmlscreen.hpp" -#include "monitor.hpp" -#include "workspace.hpp" - -namespace qs::hyprland::ipc { - -Q_LOGGING_CATEGORY(logHyprlandIpc, "quickshell.hyprland.ipc", QtWarningMsg); -Q_LOGGING_CATEGORY(logHyprlandIpcEvents, "quickshell.hyprland.ipc.events", QtWarningMsg); - -HyprlandIpc::HyprlandIpc() { - auto his = qEnvironmentVariable("HYPRLAND_INSTANCE_SIGNATURE"); - if (his.isEmpty()) { - qWarning() << "$HYPRLAND_INSTANCE_SIGNATURE is unset. Cannot connect to hyprland."; - return; - } - - auto runtimeDir = qEnvironmentVariable("XDG_RUNTIME_DIR"); - auto hyprlandDir = runtimeDir + "/hypr/" + his; - - if (!QFileInfo(hyprlandDir).isDir()) { - hyprlandDir = "/tmp/hypr/" + his; - } - - if (!QFileInfo(hyprlandDir).isDir()) { - qWarning() << "Unable to find hyprland socket. Cannot connect to hyprland."; - return; - } - - this->mRequestSocketPath = hyprlandDir + "/.socket.sock"; - this->mEventSocketPath = hyprlandDir + "/.socket2.sock"; - - // clang-format off - QObject::connect(&this->eventSocket, &QLocalSocket::errorOccurred, this, &HyprlandIpc::eventSocketError); - QObject::connect(&this->eventSocket, &QLocalSocket::stateChanged, this, &HyprlandIpc::eventSocketStateChanged); - QObject::connect(&this->eventSocket, &QLocalSocket::readyRead, this, &HyprlandIpc::eventSocketReady); - // clang-format on - - // Sockets don't appear to be able to send data in the first event loop - // cycle of the program, so delay it by one. No idea why this is the case. - QTimer::singleShot(0, [this]() { - this->eventSocket.connectToServer(this->mEventSocketPath, QLocalSocket::ReadOnly); - this->refreshMonitors(true); - this->refreshWorkspaces(true); - }); -} - -QString HyprlandIpc::requestSocketPath() const { return this->mRequestSocketPath; } -QString HyprlandIpc::eventSocketPath() const { return this->mEventSocketPath; } - -void HyprlandIpc::eventSocketError(QLocalSocket::LocalSocketError error) const { - if (!this->valid) { - qWarning() << "Unable to connect to hyprland event socket:" << error; - } else { - qWarning() << "Hyprland event socket error:" << error; - } -} - -void HyprlandIpc::eventSocketStateChanged(QLocalSocket::LocalSocketState state) { - if (state == QLocalSocket::ConnectedState) { - qCInfo(logHyprlandIpc) << "Hyprland event socket connected."; - emit this->connected(); - } else if (state == QLocalSocket::UnconnectedState && this->valid) { - qCWarning(logHyprlandIpc) << "Hyprland event socket disconnected."; - } - - this->valid = state == QLocalSocket::ConnectedState; -} - -void HyprlandIpc::eventSocketReady() { - while (true) { - auto rawEvent = this->eventSocket.readLine(); - if (rawEvent.isEmpty()) break; - - // remove trailing \n - rawEvent.truncate(rawEvent.length() - 1); - auto splitIdx = rawEvent.indexOf(">>"); - auto event = QByteArrayView(rawEvent.data(), splitIdx); - auto data = QByteArrayView( - rawEvent.data() + splitIdx + 2, // NOLINT - rawEvent.data() + rawEvent.length() // NOLINT - ); - qCDebug(logHyprlandIpcEvents) << "Received event:" << rawEvent << "parsed as" << event << data; - - this->event.name = event; - this->event.data = data; - this->onEvent(&this->event); - emit this->rawEvent(&this->event); - } -} - -void HyprlandIpc::makeRequest( - const QByteArray& request, - const std::function& callback -) { - auto* requestSocket = new QLocalSocket(this); - qCDebug(logHyprlandIpc) << "Making request:" << request; - - auto connectedCallback = [this, request, requestSocket, callback]() { - auto responseCallback = [requestSocket, callback]() { - auto response = requestSocket->readAll(); - callback(true, std::move(response)); - delete requestSocket; - }; - - QObject::connect(requestSocket, &QLocalSocket::readyRead, this, responseCallback); - - requestSocket->write(request); - }; - - auto errorCallback = [=](QLocalSocket::LocalSocketError error) { - qCWarning(logHyprlandIpc) << "Error making request:" << error << "request:" << request; - requestSocket->deleteLater(); - callback(false, {}); - }; - - QObject::connect(requestSocket, &QLocalSocket::connected, this, connectedCallback); - QObject::connect(requestSocket, &QLocalSocket::errorOccurred, this, errorCallback); - - requestSocket->connectToServer(this->mRequestSocketPath); -} - -void HyprlandIpc::dispatch(const QString& request) { - this->makeRequest( - ("dispatch " + request).toUtf8(), - [request](bool success, const QByteArray& response) { - if (!success) { - qCWarning(logHyprlandIpc) << "Failed to request dispatch of" << request; - return; - } - - if (response != "ok") { - qCWarning(logHyprlandIpc) - << "Dispatch request" << request << "failed with error" << response; - } - } - ); -} - -ObjectModel* HyprlandIpc::monitors() { return &this->mMonitors; } - -ObjectModel* HyprlandIpc::workspaces() { return &this->mWorkspaces; } - -QVector HyprlandIpc::parseEventArgs(QByteArrayView event, quint16 count) { - auto args = QVector(); - - for (auto i = 0; i < count - 1; i++) { - auto splitIdx = event.indexOf(','); - if (splitIdx == -1) break; - args.push_back(event.sliced(0, splitIdx)); - event = event.sliced(splitIdx + 1); - } - - if (!event.isEmpty()) { - args.push_back(event); - } - - return args; -} - -QVector HyprlandIpcEvent::parse(qint32 argumentCount) const { - auto args = QVector(); - - for (auto arg: this->parseView(argumentCount)) { - args.push_back(QString::fromUtf8(arg)); - } - - return args; -} - -QVector HyprlandIpcEvent::parseView(qint32 argumentCount) const { - return HyprlandIpc::parseEventArgs(this->data, argumentCount); -} - -QString HyprlandIpcEvent::nameStr() const { return QString::fromUtf8(this->name); } -QString HyprlandIpcEvent::dataStr() const { return QString::fromUtf8(this->data); } - -HyprlandIpc* HyprlandIpc::instance() { - static HyprlandIpc* instance = nullptr; // NOLINT - - if (instance == nullptr) { - instance = new HyprlandIpc(); - } - - return instance; -} - -void HyprlandIpc::onEvent(HyprlandIpcEvent* event) { - if (event->name == "configreloaded") { - this->refreshMonitors(true); - this->refreshWorkspaces(true); - } else if (event->name == "monitoraddedv2") { - auto args = event->parseView(3); - - auto id = args.at(0).toInt(); - auto name = QString::fromUtf8(args.at(1)); - - // hyprland will often reference the monitor before creation, in which case - // it will already exist. - auto* monitor = this->findMonitorByName(name, false); - auto existed = monitor != nullptr; - - if (monitor == nullptr) { - monitor = new HyprlandMonitor(this); - } - - qCDebug(logHyprlandIpc) << "Monitor added with id" << id << "name" << name - << "preemptively created:" << existed; - - monitor->updateInitial(id, name, QString::fromUtf8(args.at(2))); - - if (!existed) { - this->mMonitors.insertObject(monitor); - } - - // refresh even if it already existed because workspace focus might have changed. - this->refreshMonitors(false); - } else if (event->name == "monitorremoved") { - const auto& mList = this->mMonitors.valueList(); - auto name = QString::fromUtf8(event->data); - - auto monitorIter = std::find_if(mList.begin(), mList.end(), [name](const HyprlandMonitor* m) { - return m->name() == name; - }); - - if (monitorIter == mList.end()) { - qCWarning(logHyprlandIpc) << "Got removal for monitor" << name - << "which was not previously tracked."; - return; - } - - auto index = monitorIter - mList.begin(); - auto* monitor = *monitorIter; - - qCDebug(logHyprlandIpc) << "Monitor removed with id" << monitor->id() << "name" - << monitor->name(); - this->mMonitors.removeAt(index); - - // delete the monitor object in the next event loop cycle so it's likely to - // still exist when future events reference it after destruction. - // If we get to the next cycle and things still reference it (unlikely), nulls - // can make it to the frontend. - monitor->deleteLater(); - } else if (event->name == "createworkspacev2") { - auto args = event->parseView(2); - - auto id = args.at(0).toInt(); - auto name = QString::fromUtf8(args.at(1)); - - qCDebug(logHyprlandIpc) << "Workspace created with id" << id << "name" << name; - - auto* workspace = this->findWorkspaceByName(name, false); - auto existed = workspace != nullptr; - - if (workspace == nullptr) { - workspace = new HyprlandWorkspace(this); - } - - workspace->updateInitial(id, name); - - if (!existed) { - this->refreshWorkspaces(false); - this->mWorkspaces.insertObject(workspace); - } - } else if (event->name == "destroyworkspacev2") { - auto args = event->parseView(2); - - auto id = args.at(0).toInt(); - auto name = QString::fromUtf8(args.at(1)); - - const auto& mList = this->mWorkspaces.valueList(); - - auto workspaceIter = std::find_if(mList.begin(), mList.end(), [id](const HyprlandWorkspace* m) { - return m->id() == id; - }); - - if (workspaceIter == mList.end()) { - qCWarning(logHyprlandIpc) << "Got removal for workspace id" << id << "name" << name - << "which was not previously tracked."; - return; - } - - auto index = workspaceIter - mList.begin(); - auto* workspace = *workspaceIter; - - qCDebug(logHyprlandIpc) << "Workspace removed with id" << id << "name" << name; - this->mWorkspaces.removeAt(index); - - // workspaces have not been observed to be referenced after deletion - delete workspace; - - for (auto* monitor: this->mMonitors.valueList()) { - if (monitor->activeWorkspace() == nullptr) { - // removing a monitor will cause a new workspace to be created and destroyed after removal, - // but it won't go back to a real workspace afterwards and just leaves a null, so we - // re-query monitors if this appears to be the case. - this->refreshMonitors(false); - break; - } - } - } else if (event->name == "focusedmon") { - auto args = event->parseView(2); - auto name = QString::fromUtf8(args.at(0)); - auto workspaceName = QString::fromUtf8(args.at(1)); - - HyprlandWorkspace* workspace = nullptr; - if (workspaceName != "?") { // what the fuck - workspace = this->findWorkspaceByName(workspaceName, false); - } - - auto* monitor = this->findMonitorByName(name, true); - this->setFocusedMonitor(monitor); - monitor->setActiveWorkspace(workspace); - } else if (event->name == "workspacev2") { - auto args = event->parseView(2); - auto id = args.at(0).toInt(); - auto name = QString::fromUtf8(args.at(1)); - - if (this->mFocusedMonitor != nullptr) { - auto* workspace = this->findWorkspaceByName(name, true, id); - this->mFocusedMonitor->setActiveWorkspace(workspace); - } - } else if (event->name == "moveworkspacev2") { - auto args = event->parseView(3); - auto id = args.at(0).toInt(); - auto name = QString::fromUtf8(args.at(1)); - auto monitorName = QString::fromUtf8(args.at(2)); - - auto* workspace = this->findWorkspaceByName(name, true, id); - auto* monitor = this->findMonitorByName(monitorName, true); - - workspace->setMonitor(monitor); - } -} - -HyprlandWorkspace* -HyprlandIpc::findWorkspaceByName(const QString& name, bool createIfMissing, qint32 id) { - const auto& mList = this->mWorkspaces.valueList(); - - auto workspaceIter = std::find_if(mList.begin(), mList.end(), [name](const HyprlandWorkspace* m) { - return m->name() == name; - }); - - if (workspaceIter != mList.end()) { - return *workspaceIter; - } else if (createIfMissing) { - qCDebug(logHyprlandIpc) << "Workspace" << name - << "requested before creation, performing early init"; - auto* workspace = new HyprlandWorkspace(this); - workspace->updateInitial(id, name); - this->mWorkspaces.insertObject(workspace); - return workspace; - } else { - return nullptr; - } -} - -void HyprlandIpc::refreshWorkspaces(bool canCreate, bool tryAgain) { - if (this->requestingWorkspaces) return; - this->requestingWorkspaces = true; - - this->makeRequest( - "j/workspaces", - [this, canCreate, tryAgain](bool success, const QByteArray& resp) { - this->requestingWorkspaces = false; - if (!success) { - // sometimes fails randomly, so we give it another shot. - if (tryAgain) this->refreshWorkspaces(canCreate, false); - return; - } - - qCDebug(logHyprlandIpc) << "parsing workspaces response"; - auto json = QJsonDocument::fromJson(resp).array(); - - const auto& mList = this->mWorkspaces.valueList(); - auto names = QVector(); - - for (auto entry: json) { - auto object = entry.toObject().toVariantMap(); - auto name = object.value("name").toString(); - - auto workspaceIter = - std::find_if(mList.begin(), mList.end(), [name](const HyprlandWorkspace* m) { - return m->name() == name; - }); - - auto* workspace = workspaceIter == mList.end() ? nullptr : *workspaceIter; - auto existed = workspace != nullptr; - - if (workspace == nullptr) { - if (!canCreate) continue; - workspace = new HyprlandWorkspace(this); - } - - workspace->updateFromObject(object); - - if (!existed) { - this->mWorkspaces.insertObject(workspace); - } - - names.push_back(name); - } - - auto removedWorkspaces = QVector(); - - for (auto* workspace: mList) { - if (!names.contains(workspace->name())) { - removedWorkspaces.push_back(workspace); - } - } - - for (auto* workspace: removedWorkspaces) { - this->mWorkspaces.removeObject(workspace); - delete workspace; - } - } - ); -} - -HyprlandMonitor* -HyprlandIpc::findMonitorByName(const QString& name, bool createIfMissing, qint32 id) { - const auto& mList = this->mMonitors.valueList(); - - auto monitorIter = std::find_if(mList.begin(), mList.end(), [name](const HyprlandMonitor* m) { - return m->name() == name; - }); - - if (monitorIter != mList.end()) { - return *monitorIter; - } else if (createIfMissing) { - qCDebug(logHyprlandIpc) << "Monitor" << name - << "requested before creation, performing early init"; - auto* monitor = new HyprlandMonitor(this); - monitor->updateInitial(id, name, ""); - this->mMonitors.insertObject(monitor); - return monitor; - } else { - return nullptr; - } -} - -HyprlandMonitor* HyprlandIpc::focusedMonitor() const { return this->mFocusedMonitor; } - -HyprlandMonitor* HyprlandIpc::monitorFor(QuickshellScreenInfo* screen) { - // Wayland monitors appear after hyprland ones are created and disappear after destruction - // so simply not doing any preemptive creation is enough, however if this call creates - // the HyprlandIpc singleton then monitors won't be initialized, in which case we - // preemptively create one. - - if (screen == nullptr) return nullptr; - return this->findMonitorByName(screen->name(), !this->monitorsRequested); -} - -void HyprlandIpc::setFocusedMonitor(HyprlandMonitor* monitor) { - if (monitor == this->mFocusedMonitor) return; - - if (this->mFocusedMonitor != nullptr) { - QObject::disconnect(this->mFocusedMonitor, nullptr, this, nullptr); - } - - this->mFocusedMonitor = monitor; - - if (monitor != nullptr) { - QObject::connect(monitor, &QObject::destroyed, this, &HyprlandIpc::onFocusedMonitorDestroyed); - } - emit this->focusedMonitorChanged(); -} - -void HyprlandIpc::onFocusedMonitorDestroyed() { - this->mFocusedMonitor = nullptr; - emit this->focusedMonitorChanged(); -} - -void HyprlandIpc::refreshMonitors(bool canCreate, bool tryAgain) { - if (this->requestingMonitors) return; - this->requestingMonitors = true; - - this->makeRequest( - "j/monitors", - [this, canCreate, tryAgain](bool success, const QByteArray& resp) { - this->requestingMonitors = false; - if (!success) { - // sometimes fails randomly, so we give it another shot. - if (tryAgain) this->refreshMonitors(canCreate, false); - return; - } - - this->monitorsRequested = true; - - qCDebug(logHyprlandIpc) << "parsing monitors response"; - auto json = QJsonDocument::fromJson(resp).array(); - - const auto& mList = this->mMonitors.valueList(); - auto names = QVector(); - - for (auto entry: json) { - auto object = entry.toObject().toVariantMap(); - auto name = object.value("name").toString(); - - auto monitorIter = - std::find_if(mList.begin(), mList.end(), [name](const HyprlandMonitor* m) { - return m->name() == name; - }); - - auto* monitor = monitorIter == mList.end() ? nullptr : *monitorIter; - auto existed = monitor != nullptr; - - if (monitor == nullptr) { - if (!canCreate) continue; - monitor = new HyprlandMonitor(this); - } - - monitor->updateFromObject(object); - - if (!existed) { - this->mMonitors.insertObject(monitor); - } - - names.push_back(name); - } - - auto removedMonitors = QVector(); - - for (auto* monitor: mList) { - if (!names.contains(monitor->name())) { - removedMonitors.push_back(monitor); - } - } - - for (auto* monitor: removedMonitors) { - this->mMonitors.removeObject(monitor); - // see comment in onEvent - monitor->deleteLater(); - } - } - ); -} - -} // namespace qs::hyprland::ipc diff --git a/src/wayland/hyprland/ipc/connection.hpp b/src/wayland/hyprland/ipc/connection.hpp deleted file mode 100644 index 1778460a..00000000 --- a/src/wayland/hyprland/ipc/connection.hpp +++ /dev/null @@ -1,124 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "../../../core/model.hpp" -#include "../../../core/qmlscreen.hpp" - -namespace qs::hyprland::ipc { - -class HyprlandMonitor; -class HyprlandWorkspace; - -} // namespace qs::hyprland::ipc - -Q_DECLARE_OPAQUE_POINTER(qs::hyprland::ipc::HyprlandWorkspace*); -Q_DECLARE_OPAQUE_POINTER(qs::hyprland::ipc::HyprlandMonitor*); - -namespace qs::hyprland::ipc { - -///! Live Hyprland IPC event. -/// Live Hyprland IPC event. Holding this object after the -/// signal handler exits is undefined as the event instance -/// is reused. -class HyprlandIpcEvent: public QObject { - Q_OBJECT; - /// The name of the event. - Q_PROPERTY(QString name READ nameStr CONSTANT); - /// The unparsed data of the event. - Q_PROPERTY(QString data READ dataStr CONSTANT); - QML_NAMED_ELEMENT(HyprlandEvent); - QML_UNCREATABLE("HyprlandIpcEvents cannot be created."); - -public: - HyprlandIpcEvent(QObject* parent): QObject(parent) {} - - /// Parse this event with a known number of arguments. - /// - /// Argument count is required as some events can contain commas - /// in the last argument, which can be ignored as long as the count is known. - Q_INVOKABLE [[nodiscard]] QVector parse(qint32 argumentCount) const; - [[nodiscard]] QVector parseView(qint32 argumentCount) const; - - [[nodiscard]] QString nameStr() const; - [[nodiscard]] QString dataStr() const; - - void reset(); - QByteArrayView name; - QByteArrayView data; -}; - -class HyprlandIpc: public QObject { - Q_OBJECT; - -public: - static HyprlandIpc* instance(); - - [[nodiscard]] QString requestSocketPath() const; - [[nodiscard]] QString eventSocketPath() const; - - void - makeRequest(const QByteArray& request, const std::function& callback); - void dispatch(const QString& request); - - [[nodiscard]] HyprlandMonitor* monitorFor(QuickshellScreenInfo* screen); - [[nodiscard]] HyprlandMonitor* focusedMonitor() const; - void setFocusedMonitor(HyprlandMonitor* monitor); - - [[nodiscard]] ObjectModel* monitors(); - [[nodiscard]] ObjectModel* workspaces(); - - // No byId because these preemptively create objects. The given id is set if created. - HyprlandWorkspace* findWorkspaceByName(const QString& name, bool createIfMissing, qint32 id = 0); - HyprlandMonitor* findMonitorByName(const QString& name, bool createIfMissing, qint32 id = -1); - - // canCreate avoids making ghost workspaces when the connection races - void refreshWorkspaces(bool canCreate, bool tryAgain = true); - void refreshMonitors(bool canCreate, bool tryAgain = true); - - // The last argument may contain commas, so the count is required. - [[nodiscard]] static QVector parseEventArgs(QByteArrayView event, quint16 count); - -signals: - void connected(); - void rawEvent(HyprlandIpcEvent* event); - - void focusedMonitorChanged(); - -private slots: - void eventSocketError(QLocalSocket::LocalSocketError error) const; - void eventSocketStateChanged(QLocalSocket::LocalSocketState state); - void eventSocketReady(); - - void onFocusedMonitorDestroyed(); - -private: - explicit HyprlandIpc(); - - void onEvent(HyprlandIpcEvent* event); - - QLocalSocket eventSocket; - QString mRequestSocketPath; - QString mEventSocketPath; - bool valid = false; - bool requestingMonitors = false; - bool requestingWorkspaces = false; - bool monitorsRequested = false; - - ObjectModel mMonitors {this}; - ObjectModel mWorkspaces {this}; - HyprlandMonitor* mFocusedMonitor = nullptr; - //HyprlandWorkspace* activeWorkspace = nullptr; - - HyprlandIpcEvent event {this}; -}; - -} // namespace qs::hyprland::ipc diff --git a/src/wayland/hyprland/ipc/monitor.cpp b/src/wayland/hyprland/ipc/monitor.cpp deleted file mode 100644 index 8ee5e207..00000000 --- a/src/wayland/hyprland/ipc/monitor.cpp +++ /dev/null @@ -1,136 +0,0 @@ -#include "monitor.hpp" -#include - -#include -#include -#include -#include - -#include "workspace.hpp" - -namespace qs::hyprland::ipc { - -qint32 HyprlandMonitor::id() const { return this->mId; } -QString HyprlandMonitor::name() const { return this->mName; } -QString HyprlandMonitor::description() const { return this->mDescription; } -qint32 HyprlandMonitor::x() const { return this->mX; } -qint32 HyprlandMonitor::y() const { return this->mY; } -qint32 HyprlandMonitor::width() const { return this->mWidth; } -qint32 HyprlandMonitor::height() const { return this->mHeight; } -qreal HyprlandMonitor::scale() const { return this->mScale; } -QVariantMap HyprlandMonitor::lastIpcObject() const { return this->mLastIpcObject; } - -void HyprlandMonitor::updateInitial(qint32 id, QString name, QString description) { - if (id != this->mId) { - this->mId = id; - emit this->idChanged(); - } - - if (name != this->mName) { - this->mName = std::move(name); - emit this->nameChanged(); - } - - if (description != this->mDescription) { - this->mDescription = std::move(description); - emit this->descriptionChanged(); - } -} - -void HyprlandMonitor::updateFromObject(QVariantMap object) { - auto id = object.value("id").value(); - auto name = object.value("name").value(); - auto description = object.value("description").value(); - auto x = object.value("x").value(); - auto y = object.value("y").value(); - auto width = object.value("width").value(); - auto height = object.value("height").value(); - auto scale = object.value("height").value(); - auto activeWorkspaceObj = object.value("activeWorkspace").value(); - auto activeWorkspaceId = activeWorkspaceObj.value("id").value(); - auto activeWorkspaceName = activeWorkspaceObj.value("name").value(); - auto focused = object.value("focused").value(); - - if (id != this->mId) { - this->mId = id; - emit this->idChanged(); - } - - if (name != this->mName) { - this->mName = std::move(name); - emit this->nameChanged(); - } - - if (description != this->mDescription) { - this->mDescription = std::move(description); - emit this->descriptionChanged(); - } - - if (x != this->mX) { - this->mX = x; - emit this->xChanged(); - } - - if (y != this->mY) { - this->mY = y; - emit this->yChanged(); - } - - if (width != this->mWidth) { - this->mWidth = width; - emit this->widthChanged(); - } - - if (height != this->mHeight) { - this->mHeight = height; - emit this->heightChanged(); - } - - if (scale != this->mScale) { - this->mScale = scale; - emit this->scaleChanged(); - } - - if (this->mActiveWorkspace == nullptr || this->mActiveWorkspace->name() != activeWorkspaceName) { - auto* workspace = this->ipc->findWorkspaceByName(activeWorkspaceName, true, activeWorkspaceId); - workspace->setMonitor(this); - this->setActiveWorkspace(workspace); - } - - this->mLastIpcObject = std::move(object); - emit this->lastIpcObjectChanged(); - - if (focused) { - this->ipc->setFocusedMonitor(this); - } -} - -HyprlandWorkspace* HyprlandMonitor::activeWorkspace() const { return this->mActiveWorkspace; } - -void HyprlandMonitor::setActiveWorkspace(HyprlandWorkspace* workspace) { - if (workspace == this->mActiveWorkspace) return; - - if (this->mActiveWorkspace != nullptr) { - QObject::disconnect(this->mActiveWorkspace, nullptr, this, nullptr); - } - - this->mActiveWorkspace = workspace; - - if (workspace != nullptr) { - QObject::connect( - workspace, - &QObject::destroyed, - this, - &HyprlandMonitor::onActiveWorkspaceDestroyed - ); - } - - emit this->activeWorkspaceChanged(); -} - -void HyprlandMonitor::onActiveWorkspaceDestroyed() { - this->mActiveWorkspace = nullptr; - emit this->activeWorkspaceChanged(); -} - -} // namespace qs::hyprland::ipc diff --git a/src/wayland/hyprland/ipc/monitor.hpp b/src/wayland/hyprland/ipc/monitor.hpp deleted file mode 100644 index 6b5d2ecc..00000000 --- a/src/wayland/hyprland/ipc/monitor.hpp +++ /dev/null @@ -1,85 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "connection.hpp" - -namespace qs::hyprland::ipc { - -class HyprlandMonitor: public QObject { - Q_OBJECT; - Q_PROPERTY(qint32 id READ id NOTIFY idChanged); - Q_PROPERTY(QString name READ name NOTIFY nameChanged); - Q_PROPERTY(QString description READ description NOTIFY descriptionChanged); - Q_PROPERTY(qint32 x READ x NOTIFY xChanged); - Q_PROPERTY(qint32 y READ y NOTIFY yChanged); - Q_PROPERTY(qint32 width READ width NOTIFY widthChanged); - Q_PROPERTY(qint32 height READ height NOTIFY heightChanged); - Q_PROPERTY(qreal scale READ scale NOTIFY scaleChanged); - /// Last json returned for this monitor, as a javascript object. - /// - /// > [!WARNING] This is *not* updated unless the monitor object is fetched again from - /// > Hyprland. If you need a value that is subject to change and does not have a dedicated - /// > property, run `HyprlandIpc.refreshMonitors()` and wait for this property to update. - Q_PROPERTY(QVariantMap lastIpcObject READ lastIpcObject NOTIFY lastIpcObjectChanged); - /// The currently active workspace on this monitor. May be null. - Q_PROPERTY(HyprlandWorkspace* activeWorkspace READ activeWorkspace NOTIFY activeWorkspaceChanged); - QML_ELEMENT; - QML_UNCREATABLE("HyprlandMonitors must be retrieved from the HyprlandIpc object."); - -public: - explicit HyprlandMonitor(HyprlandIpc* ipc): QObject(ipc), ipc(ipc) {} - - void updateInitial(qint32 id, QString name, QString description); - void updateFromObject(QVariantMap object); - - [[nodiscard]] qint32 id() const; - [[nodiscard]] QString name() const; - [[nodiscard]] QString description() const; - [[nodiscard]] qint32 x() const; - [[nodiscard]] qint32 y() const; - [[nodiscard]] qint32 width() const; - [[nodiscard]] qint32 height() const; - [[nodiscard]] qreal scale() const; - [[nodiscard]] QVariantMap lastIpcObject() const; - - void setActiveWorkspace(HyprlandWorkspace* workspace); - [[nodiscard]] HyprlandWorkspace* activeWorkspace() const; - -signals: - void idChanged(); - void nameChanged(); - void descriptionChanged(); - void xChanged(); - void yChanged(); - void widthChanged(); - void heightChanged(); - void scaleChanged(); - void lastIpcObjectChanged(); - void activeWorkspaceChanged(); - -private slots: - void onActiveWorkspaceDestroyed(); - -private: - HyprlandIpc* ipc; - - qint32 mId = -1; - QString mName; - QString mDescription; - qint32 mX = 0; - qint32 mY = 0; - qint32 mWidth = 0; - qint32 mHeight = 0; - qreal mScale = 0; - QVariantMap mLastIpcObject; - - HyprlandWorkspace* mActiveWorkspace = nullptr; -}; - -} // namespace qs::hyprland::ipc diff --git a/src/wayland/hyprland/ipc/qml.cpp b/src/wayland/hyprland/ipc/qml.cpp deleted file mode 100644 index 1e75ee9c..00000000 --- a/src/wayland/hyprland/ipc/qml.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "qml.hpp" - -#include - -#include "../../../core/model.hpp" -#include "../../../core/qmlscreen.hpp" -#include "connection.hpp" -#include "monitor.hpp" - -namespace qs::hyprland::ipc { - -HyprlandIpcQml::HyprlandIpcQml() { - auto* instance = HyprlandIpc::instance(); - - QObject::connect(instance, &HyprlandIpc::rawEvent, this, &HyprlandIpcQml::rawEvent); - QObject::connect( - instance, - &HyprlandIpc::focusedMonitorChanged, - this, - &HyprlandIpcQml::focusedMonitorChanged - ); -} - -void HyprlandIpcQml::dispatch(const QString& request) { - HyprlandIpc::instance()->dispatch(request); -} - -HyprlandMonitor* HyprlandIpcQml::monitorFor(QuickshellScreenInfo* screen) { - return HyprlandIpc::instance()->monitorFor(screen); -} - -void HyprlandIpcQml::refreshMonitors() { HyprlandIpc::instance()->refreshMonitors(false); } - -void HyprlandIpcQml::refreshWorkspaces() { HyprlandIpc::instance()->refreshWorkspaces(false); } - -QString HyprlandIpcQml::requestSocketPath() { return HyprlandIpc::instance()->requestSocketPath(); } - -QString HyprlandIpcQml::eventSocketPath() { return HyprlandIpc::instance()->eventSocketPath(); } - -HyprlandMonitor* HyprlandIpcQml::focusedMonitor() { - return HyprlandIpc::instance()->focusedMonitor(); -} - -ObjectModel* HyprlandIpcQml::monitors() { - return HyprlandIpc::instance()->monitors(); -} - -ObjectModel* HyprlandIpcQml::workspaces() { - return HyprlandIpc::instance()->workspaces(); -} - -} // namespace qs::hyprland::ipc diff --git a/src/wayland/hyprland/ipc/qml.hpp b/src/wayland/hyprland/ipc/qml.hpp deleted file mode 100644 index 2d39623f..00000000 --- a/src/wayland/hyprland/ipc/qml.hpp +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "../../../core/model.hpp" -#include "../../../core/qmlscreen.hpp" -#include "connection.hpp" -#include "monitor.hpp" - -namespace qs::hyprland::ipc { - -class HyprlandIpcQml: public QObject { - Q_OBJECT; - /// Path to the request socket (.socket.sock) - Q_PROPERTY(QString requestSocketPath READ requestSocketPath CONSTANT); - /// Path to the event socket (.socket2.sock) - Q_PROPERTY(QString eventSocketPath READ eventSocketPath CONSTANT); - /// The currently focused hyprland monitor. May be null. - Q_PROPERTY(HyprlandMonitor* focusedMonitor READ focusedMonitor NOTIFY focusedMonitorChanged); - /// All hyprland monitors. - Q_PROPERTY(ObjectModel* monitors READ monitors CONSTANT); - /// All hyprland workspaces. - Q_PROPERTY(ObjectModel* workspaces READ workspaces CONSTANT); - QML_NAMED_ELEMENT(Hyprland); - QML_SINGLETON; - -public: - explicit HyprlandIpcQml(); - - /// Execute a hyprland [dispatcher](https://wiki.hyprland.org/Configuring/Dispatchers). - Q_INVOKABLE static void dispatch(const QString& request); - - /// Get the HyprlandMonitor object that corrosponds to a quickshell screen. - Q_INVOKABLE static HyprlandMonitor* monitorFor(QuickshellScreenInfo* screen); - - /// Refresh monitor information. - /// - /// Many actions that will invalidate monitor state don't send events, - /// so this function is available if required. - Q_INVOKABLE static void refreshMonitors(); - - /// Refresh workspace information. - /// - /// Many actions that will invalidate workspace state don't send events, - /// so this function is available if required. - Q_INVOKABLE static void refreshWorkspaces(); - - [[nodiscard]] static QString requestSocketPath(); - [[nodiscard]] static QString eventSocketPath(); - [[nodiscard]] static HyprlandMonitor* focusedMonitor(); - [[nodiscard]] static ObjectModel* monitors(); - [[nodiscard]] static ObjectModel* workspaces(); - -signals: - /// Emitted for every event that comes in through the hyprland event socket (socket2). - /// - /// See [Hyprland Wiki: IPC](https://wiki.hyprland.org/IPC/) for a list of events. - void rawEvent(HyprlandIpcEvent* event); - - void focusedMonitorChanged(); -}; - -} // namespace qs::hyprland::ipc diff --git a/src/wayland/hyprland/ipc/workspace.cpp b/src/wayland/hyprland/ipc/workspace.cpp deleted file mode 100644 index fbf8477f..00000000 --- a/src/wayland/hyprland/ipc/workspace.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include "workspace.hpp" -#include - -#include -#include -#include -#include - -#include "monitor.hpp" - -namespace qs::hyprland::ipc { - -qint32 HyprlandWorkspace::id() const { return this->mId; } -QString HyprlandWorkspace::name() const { return this->mName; } -QVariantMap HyprlandWorkspace::lastIpcObject() const { return this->mLastIpcObject; } - -void HyprlandWorkspace::updateInitial(qint32 id, QString name) { - if (id != this->mId) { - this->mId = id; - emit this->idChanged(); - } - - if (name != this->mName) { - this->mName = std::move(name); - emit this->nameChanged(); - } -} - -void HyprlandWorkspace::updateFromObject(QVariantMap object) { - auto id = object.value("id").value(); - auto name = object.value("name").value(); - auto monitorId = object.value("monitorID").value(); - auto monitorName = object.value("monitor").value(); - - if (id != this->mId) { - this->mId = id; - emit this->idChanged(); - } - - if (name != this->mName) { - this->mName = std::move(name); - emit this->nameChanged(); - } - - if (!monitorName.isEmpty() - && (this->mMonitor == nullptr || this->mMonitor->name() != monitorName)) - { - auto* monitor = this->ipc->findMonitorByName(monitorName, true, monitorId); - this->setMonitor(monitor); - } - - this->mLastIpcObject = std::move(object); - emit this->lastIpcObjectChanged(); -} - -HyprlandMonitor* HyprlandWorkspace::monitor() const { return this->mMonitor; } - -void HyprlandWorkspace::setMonitor(HyprlandMonitor* monitor) { - if (monitor == this->mMonitor) return; - - if (this->mMonitor != nullptr) { - QObject::disconnect(this->mMonitor, nullptr, this, nullptr); - } - - this->mMonitor = monitor; - - if (monitor != nullptr) { - QObject::connect(monitor, &QObject::destroyed, this, &HyprlandWorkspace::onMonitorDestroyed); - } - - emit this->monitorChanged(); -} - -void HyprlandWorkspace::onMonitorDestroyed() { - this->mMonitor = nullptr; - emit this->monitorChanged(); -} - -} // namespace qs::hyprland::ipc diff --git a/src/wayland/hyprland/ipc/workspace.hpp b/src/wayland/hyprland/ipc/workspace.hpp deleted file mode 100644 index a63901e6..00000000 --- a/src/wayland/hyprland/ipc/workspace.hpp +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "connection.hpp" - -namespace qs::hyprland::ipc { - -class HyprlandWorkspace: public QObject { - Q_OBJECT; - Q_PROPERTY(qint32 id READ id NOTIFY idChanged); - Q_PROPERTY(QString name READ name NOTIFY nameChanged); - /// Last json returned for this workspace, as a javascript object. - /// - /// > [!WARNING] This is *not* updated unless the workspace object is fetched again from - /// > Hyprland. If you need a value that is subject to change and does not have a dedicated - /// > property, run `HyprlandIpc.refreshWorkspaces()` and wait for this property to update. - Q_PROPERTY(QVariantMap lastIpcObject READ lastIpcObject NOTIFY lastIpcObjectChanged); - Q_PROPERTY(HyprlandMonitor* monitor READ monitor NOTIFY monitorChanged); - QML_ELEMENT; - QML_UNCREATABLE("HyprlandWorkspaces must be retrieved from the HyprlandIpc object."); - -public: - explicit HyprlandWorkspace(HyprlandIpc* ipc): QObject(ipc), ipc(ipc) {} - - void updateInitial(qint32 id, QString name); - void updateFromObject(QVariantMap object); - - [[nodiscard]] qint32 id() const; - [[nodiscard]] QString name() const; - [[nodiscard]] QVariantMap lastIpcObject() const; - - void setMonitor(HyprlandMonitor* monitor); - [[nodiscard]] HyprlandMonitor* monitor() const; - -signals: - void idChanged(); - void nameChanged(); - void lastIpcObjectChanged(); - void monitorChanged(); - -private slots: - void onMonitorDestroyed(); - -private: - HyprlandIpc* ipc; - - qint32 mId = -1; - QString mName; - QVariantMap mLastIpcObject; - HyprlandMonitor* mMonitor = nullptr; -}; - -} // namespace qs::hyprland::ipc diff --git a/src/wayland/hyprland/module.md b/src/wayland/hyprland/module.md index 6c2de249..1b3e2fbf 100644 --- a/src/wayland/hyprland/module.md +++ b/src/wayland/hyprland/module.md @@ -1,10 +1,6 @@ name = "Quickshell.Hyprland" description = "Hyprland specific Quickshell types" headers = [ - "ipc/connection.hpp", - "ipc/monitor.hpp", - "ipc/workspace.hpp", - "ipc/qml.hpp", "focus_grab/qml.hpp", "global_shortcuts/qml.hpp", ] diff --git a/src/wayland/init.cpp b/src/wayland/init.cpp index 95adb248..194bad4c 100644 --- a/src/wayland/init.cpp +++ b/src/wayland/init.cpp @@ -34,6 +34,13 @@ class WaylandPlugin: public QuickshellPlugin { // will not be registered. This can be worked around with a module import which makes // the QML_ELMENT module import the old register-type style module. + qmlRegisterModuleImport( + "Quickshell.Wayland", + QQmlModuleImportModuleAny, + "Quickshell.Wayland._WlrLayerShell", + QQmlModuleImportLatest + ); + qmlRegisterModuleImport( "Quickshell", QQmlModuleImportModuleAny, diff --git a/src/wayland/module.md b/src/wayland/module.md index d6376e39..7a427df9 100644 --- a/src/wayland/module.md +++ b/src/wayland/module.md @@ -4,6 +4,5 @@ headers = [ "wlr_layershell/window.hpp", "wlr_layershell.hpp", "session_lock.hpp", - "toplevel_management/qml.hpp", ] ----- diff --git a/src/wayland/toplevel_management/CMakeLists.txt b/src/wayland/toplevel_management/CMakeLists.txt deleted file mode 100644 index 4537c201..00000000 --- a/src/wayland/toplevel_management/CMakeLists.txt +++ /dev/null @@ -1,22 +0,0 @@ -qt_add_library(quickshell-wayland-toplevel-management STATIC - manager.cpp - handle.cpp - qml.cpp -) - -qt_add_qml_module(quickshell-wayland-toplevel-management - URI Quickshell.Wayland._ToplevelManagement - VERSION 0.1 -) - -wl_proto(quickshell-wayland-toplevel-management - wlr-foreign-toplevel-management-unstable-v1 - "${CMAKE_CURRENT_SOURCE_DIR}/wlr-foreign-toplevel-management-unstable-v1.xml" -) - -target_link_libraries(quickshell-wayland-toplevel-management PRIVATE ${QT_DEPS} wayland-client) - -qs_pch(quickshell-wayland-toplevel-management) -qs_pch(quickshell-wayland-toplevel-managementplugin) - -target_link_libraries(quickshell PRIVATE quickshell-wayland-toplevel-managementplugin) diff --git a/src/wayland/toplevel_management/handle.cpp b/src/wayland/toplevel_management/handle.cpp deleted file mode 100644 index 8c2886b4..00000000 --- a/src/wayland/toplevel_management/handle.cpp +++ /dev/null @@ -1,228 +0,0 @@ -#include "handle.hpp" -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "manager.hpp" -#include "qwayland-wlr-foreign-toplevel-management-unstable-v1.h" -#include "wayland-wlr-foreign-toplevel-management-unstable-v1-client-protocol.h" - -namespace qs::wayland::toplevel_management::impl { - -QString ToplevelHandle::appId() const { return this->mAppId; } -QString ToplevelHandle::title() const { return this->mTitle; } -QVector ToplevelHandle::visibleScreens() const { return this->mVisibleScreens; } -ToplevelHandle* ToplevelHandle::parent() const { return this->mParent; } -bool ToplevelHandle::activated() const { return this->mActivated; } -bool ToplevelHandle::maximized() const { return this->mMaximized; } -bool ToplevelHandle::minimized() const { return this->mMinimized; } -bool ToplevelHandle::fullscreen() const { return this->mFullscreen; } - -void ToplevelHandle::activate() { - auto* display = QtWaylandClient::QWaylandIntegration::instance()->display(); - auto* inputDevice = display->lastInputDevice(); - if (inputDevice == nullptr) return; - this->QtWayland::zwlr_foreign_toplevel_handle_v1::activate(inputDevice->object()); -} - -void ToplevelHandle::setMaximized(bool maximized) { - if (maximized) this->set_maximized(); - else this->unset_maximized(); -} - -void ToplevelHandle::setMinimized(bool minimized) { - if (minimized) this->set_minimized(); - else this->unset_minimized(); -} - -void ToplevelHandle::setFullscreen(bool fullscreen) { - if (fullscreen) this->set_fullscreen(nullptr); - else this->unset_fullscreen(); -} - -void ToplevelHandle::fullscreenOn(QScreen* screen) { - auto* waylandScreen = dynamic_cast(screen->handle()); - this->set_fullscreen(waylandScreen != nullptr ? waylandScreen->output() : nullptr); -} - -void ToplevelHandle::setRectangle(QWindow* window, QRect rect) { - if (window == nullptr) { - // will be cleared by the compositor if the surface is destroyed - if (this->rectWindow != nullptr) { - auto* waylandWindow = - dynamic_cast(this->rectWindow->handle()); - - if (waylandWindow != nullptr) { - this->set_rectangle(waylandWindow->surface(), 0, 0, 0, 0); - } - } - - QObject::disconnect(this->rectWindow, nullptr, this, nullptr); - this->rectWindow = nullptr; - return; - } - - if (this->rectWindow != window) { - if (this->rectWindow != nullptr) { - QObject::disconnect(this->rectWindow, nullptr, this, nullptr); - } - - this->rectWindow = window; - QObject::connect(window, &QObject::destroyed, this, &ToplevelHandle::onRectWindowDestroyed); - } - - if (auto* waylandWindow = dynamic_cast(window->handle())) { - this->set_rectangle(waylandWindow->surface(), rect.x(), rect.y(), rect.width(), rect.height()); - } else { - QObject::connect(window, &QWindow::visibleChanged, this, [this, window, rect]() { - if (window->isVisible()) { - if (window->handle() == nullptr) { - window->create(); - } - - auto* waylandWindow = dynamic_cast(window->handle()); - this->set_rectangle( - waylandWindow->surface(), - rect.x(), - rect.y(), - rect.width(), - rect.height() - ); - } - }); - } -} - -void ToplevelHandle::onRectWindowDestroyed() { this->rectWindow = nullptr; } - -void ToplevelHandle::zwlr_foreign_toplevel_handle_v1_done() { - qCDebug(logToplevelManagement) << this << "got done"; - auto wasReady = this->isReady; - this->isReady = true; - - if (!wasReady) { - emit this->ready(); - } -} - -void ToplevelHandle::zwlr_foreign_toplevel_handle_v1_closed() { - qCDebug(logToplevelManagement) << this << "closed"; - this->destroy(); - emit this->closed(); - delete this; -} - -void ToplevelHandle::zwlr_foreign_toplevel_handle_v1_app_id(const QString& appId) { - qCDebug(logToplevelManagement) << this << "got appid" << appId; - this->mAppId = appId; - emit this->appIdChanged(); -} - -void ToplevelHandle::zwlr_foreign_toplevel_handle_v1_title(const QString& title) { - qCDebug(logToplevelManagement) << this << "got toplevel" << title; - this->mTitle = title; - emit this->titleChanged(); -} - -void ToplevelHandle::zwlr_foreign_toplevel_handle_v1_state(wl_array* stateArray) { - auto activated = false; - auto maximized = false; - auto minimized = false; - auto fullscreen = false; - - // wl_array_for_each is illegal in C++ so it is manually expanded. - auto* state = static_cast<::zwlr_foreign_toplevel_handle_v1_state*>(stateArray->data); - auto size = stateArray->size / sizeof(::zwlr_foreign_toplevel_handle_v1_state); - for (size_t i = 0; i < size; i++) { - auto flag = state[i]; // NOLINT - switch (flag) { - case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED: activated = true; break; - case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED: maximized = true; break; - case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED: minimized = true; break; - case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN: fullscreen = true; break; - } - } - - qCDebug(logToplevelManagement) << this << "got state update - activated:" << activated - << "maximized:" << maximized << "minimized:" << minimized - << "fullscreen:" << fullscreen; - - if (activated != this->mActivated) { - this->mActivated = activated; - emit this->activatedChanged(); - } - - if (maximized != this->mMaximized) { - this->mMaximized = maximized; - emit this->maximizedChanged(); - } - - if (minimized != this->mMinimized) { - this->mMinimized = minimized; - emit this->minimizedChanged(); - } - - if (fullscreen != this->mFullscreen) { - this->mFullscreen = fullscreen; - emit this->fullscreenChanged(); - } -} - -void ToplevelHandle::zwlr_foreign_toplevel_handle_v1_output_enter(wl_output* output) { - auto* display = QtWaylandClient::QWaylandIntegration::instance()->display(); - auto* screen = display->screenForOutput(output)->screen(); - - qCDebug(logToplevelManagement) << this << "got output enter" << screen; - - this->mVisibleScreens.push_back(screen); - emit this->visibleScreenAdded(screen); -} - -void ToplevelHandle::zwlr_foreign_toplevel_handle_v1_output_leave(wl_output* output) { - auto* display = QtWaylandClient::QWaylandIntegration::instance()->display(); - auto* screen = display->screenForOutput(output)->screen(); - - qCDebug(logToplevelManagement) << this << "got output leave" << screen; - - emit this->visibleScreenRemoved(screen); - this->mVisibleScreens.removeOne(screen); -} - -void ToplevelHandle::zwlr_foreign_toplevel_handle_v1_parent( - ::zwlr_foreign_toplevel_handle_v1* parent -) { - auto* handle = ToplevelManager::instance()->handleFor(parent); - qCDebug(logToplevelManagement) << this << "got parent" << handle; - - if (handle != this->mParent) { - if (this->mParent != nullptr) { - QObject::disconnect(this->mParent, nullptr, this, nullptr); - } - - this->mParent = handle; - - if (handle != nullptr) { - QObject::connect(handle, &ToplevelHandle::closed, this, &ToplevelHandle::onParentClosed); - } - - emit this->parentChanged(); - } -} - -void ToplevelHandle::onParentClosed() { - this->mParent = nullptr; - emit this->parentChanged(); -} - -} // namespace qs::wayland::toplevel_management::impl diff --git a/src/wayland/toplevel_management/handle.hpp b/src/wayland/toplevel_management/handle.hpp deleted file mode 100644 index a49afe82..00000000 --- a/src/wayland/toplevel_management/handle.hpp +++ /dev/null @@ -1,77 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "wayland-wlr-foreign-toplevel-management-unstable-v1-client-protocol.h" - -namespace qs::wayland::toplevel_management::impl { - -class ToplevelHandle - : public QObject - , public QtWayland::zwlr_foreign_toplevel_handle_v1 { - Q_OBJECT; - -public: - [[nodiscard]] QString appId() const; - [[nodiscard]] QString title() const; - [[nodiscard]] QVector visibleScreens() const; - [[nodiscard]] ToplevelHandle* parent() const; - [[nodiscard]] bool activated() const; - [[nodiscard]] bool maximized() const; - [[nodiscard]] bool minimized() const; - [[nodiscard]] bool fullscreen() const; - - void activate(); - void setMaximized(bool maximized); - void setMinimized(bool minimized); - void setFullscreen(bool fullscreen); - void fullscreenOn(QScreen* screen); - void setRectangle(QWindow* window, QRect rect); - -signals: - // sent after the first done event. - void ready(); - // sent right before delete this. - void closed(); - - void appIdChanged(); - void titleChanged(); - void visibleScreenAdded(QScreen* screen); - void visibleScreenRemoved(QScreen* screen); - void parentChanged(); - void activatedChanged(); - void maximizedChanged(); - void minimizedChanged(); - void fullscreenChanged(); - -private slots: - void onParentClosed(); - void onRectWindowDestroyed(); - -private: - void zwlr_foreign_toplevel_handle_v1_done() override; - void zwlr_foreign_toplevel_handle_v1_closed() override; - void zwlr_foreign_toplevel_handle_v1_app_id(const QString& appId) override; - void zwlr_foreign_toplevel_handle_v1_title(const QString& title) override; - void zwlr_foreign_toplevel_handle_v1_state(wl_array* stateArray) override; - void zwlr_foreign_toplevel_handle_v1_output_enter(wl_output* output) override; - void zwlr_foreign_toplevel_handle_v1_output_leave(wl_output* output) override; - void zwlr_foreign_toplevel_handle_v1_parent(::zwlr_foreign_toplevel_handle_v1* parent) override; - - bool isReady = false; - QString mAppId; - QString mTitle; - QVector mVisibleScreens; - ToplevelHandle* mParent = nullptr; - bool mActivated = false; - bool mMaximized = false; - bool mMinimized = false; - bool mFullscreen = false; - QWindow* rectWindow = nullptr; -}; - -} // namespace qs::wayland::toplevel_management::impl diff --git a/src/wayland/toplevel_management/manager.cpp b/src/wayland/toplevel_management/manager.cpp deleted file mode 100644 index bd477b49..00000000 --- a/src/wayland/toplevel_management/manager.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include "manager.hpp" - -#include -#include -#include -#include -#include -#include - -#include "handle.hpp" -#include "wayland-wlr-foreign-toplevel-management-unstable-v1-client-protocol.h" - -namespace qs::wayland::toplevel_management::impl { - -Q_LOGGING_CATEGORY(logToplevelManagement, "quickshell.wayland.toplevelManagement", QtWarningMsg); - -ToplevelManager::ToplevelManager(): QWaylandClientExtensionTemplate(3) { this->initialize(); } - -bool ToplevelManager::available() const { return this->isActive(); } - -const QVector& ToplevelManager::readyToplevels() const { - return this->mReadyToplevels; -} - -ToplevelHandle* ToplevelManager::handleFor(::zwlr_foreign_toplevel_handle_v1* toplevel) { - if (toplevel == nullptr) return nullptr; - - for (auto* other: this->mToplevels) { - if (other->object() == toplevel) return other; - } - - return nullptr; -} - -ToplevelManager* ToplevelManager::instance() { - static auto* instance = new ToplevelManager(); // NOLINT - return instance; -} - -void ToplevelManager::zwlr_foreign_toplevel_manager_v1_toplevel( - ::zwlr_foreign_toplevel_handle_v1* toplevel -) { - auto* handle = new ToplevelHandle(); - QObject::connect(handle, &ToplevelHandle::closed, this, &ToplevelManager::onToplevelClosed); - QObject::connect(handle, &ToplevelHandle::ready, this, &ToplevelManager::onToplevelReady); - - qCDebug(logToplevelManagement) << "Toplevel handle created" << handle; - this->mToplevels.push_back(handle); - - // Not done in constructor as a close could technically be picked up immediately on init, - // making touching the handle a UAF. - handle->init(toplevel); -} - -void ToplevelManager::onToplevelReady() { - auto* handle = qobject_cast(this->sender()); - this->mReadyToplevels.push_back(handle); - emit this->toplevelReady(handle); -} - -void ToplevelManager::onToplevelClosed() { - auto* handle = qobject_cast(this->sender()); - this->mReadyToplevels.removeOne(handle); - this->mToplevels.removeOne(handle); -} - -} // namespace qs::wayland::toplevel_management::impl diff --git a/src/wayland/toplevel_management/manager.hpp b/src/wayland/toplevel_management/manager.hpp deleted file mode 100644 index 41848de1..00000000 --- a/src/wayland/toplevel_management/manager.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "wayland-wlr-foreign-toplevel-management-unstable-v1-client-protocol.h" - -namespace qs::wayland::toplevel_management::impl { - -class ToplevelHandle; - -Q_DECLARE_LOGGING_CATEGORY(logToplevelManagement); - -class ToplevelManager - : public QWaylandClientExtensionTemplate - , public QtWayland::zwlr_foreign_toplevel_manager_v1 { - Q_OBJECT; - -public: - [[nodiscard]] bool available() const; - [[nodiscard]] const QVector& readyToplevels() const; - [[nodiscard]] ToplevelHandle* handleFor(::zwlr_foreign_toplevel_handle_v1* toplevel); - - static ToplevelManager* instance(); - -signals: - void toplevelReady(ToplevelHandle* toplevel); - -protected: - explicit ToplevelManager(); - - void zwlr_foreign_toplevel_manager_v1_toplevel(::zwlr_foreign_toplevel_handle_v1* toplevel - ) override; - -private slots: - void onToplevelReady(); - void onToplevelClosed(); - -private: - QVector mToplevels; - QVector mReadyToplevels; -}; - -} // namespace qs::wayland::toplevel_management::impl diff --git a/src/wayland/toplevel_management/qml.cpp b/src/wayland/toplevel_management/qml.cpp deleted file mode 100644 index 2042262b..00000000 --- a/src/wayland/toplevel_management/qml.cpp +++ /dev/null @@ -1,153 +0,0 @@ -#include "qml.hpp" - -#include -#include - -#include "../../core/model.hpp" -#include "../../core/proxywindow.hpp" -#include "../../core/qmlscreen.hpp" -#include "../../core/windowinterface.hpp" -#include "handle.hpp" -#include "manager.hpp" - -namespace qs::wayland::toplevel_management { - -Toplevel::Toplevel(impl::ToplevelHandle* handle, QObject* parent): QObject(parent), handle(handle) { - // clang-format off - QObject::connect(handle, &impl::ToplevelHandle::closed, this, &Toplevel::onClosed); - QObject::connect(handle, &impl::ToplevelHandle::appIdChanged, this, &Toplevel::appIdChanged); - QObject::connect(handle, &impl::ToplevelHandle::titleChanged, this, &Toplevel::titleChanged); - QObject::connect(handle, &impl::ToplevelHandle::parentChanged, this, &Toplevel::parentChanged); - QObject::connect(handle, &impl::ToplevelHandle::activatedChanged, this, &Toplevel::activatedChanged); - QObject::connect(handle, &impl::ToplevelHandle::maximizedChanged, this, &Toplevel::maximizedChanged); - QObject::connect(handle, &impl::ToplevelHandle::minimizedChanged, this, &Toplevel::minimizedChanged); - QObject::connect(handle, &impl::ToplevelHandle::fullscreenChanged, this, &Toplevel::fullscreenChanged); - // clang-format on -} - -void Toplevel::onClosed() { - emit this->closed(); - delete this; -} - -void Toplevel::activate() { this->handle->activate(); } - -QString Toplevel::appId() const { return this->handle->appId(); } -QString Toplevel::title() const { return this->handle->title(); } - -Toplevel* Toplevel::parent() const { - return ToplevelManager::instance()->forImpl(this->handle->parent()); -} - -bool Toplevel::activated() const { return this->handle->activated(); } - -bool Toplevel::maximized() const { return this->handle->maximized(); } -void Toplevel::setMaximized(bool maximized) { this->handle->setMaximized(maximized); } - -bool Toplevel::minimized() const { return this->handle->minimized(); } -void Toplevel::setMinimized(bool minimized) { this->handle->setMinimized(minimized); } - -bool Toplevel::fullscreen() const { return this->handle->fullscreen(); } -void Toplevel::setFullscreen(bool fullscreen) { this->handle->setFullscreen(fullscreen); } - -void Toplevel::fullscreenOn(QuickshellScreenInfo* screen) { - auto* qscreen = screen != nullptr ? screen->screen : nullptr; - this->handle->fullscreenOn(qscreen); -} - -void Toplevel::setRectangle(QObject* window, QRect rect) { - 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) { - QObject::disconnect(this->rectWindow, nullptr, this, nullptr); - } - - this->rectWindow = proxyWindow; - - if (proxyWindow != nullptr) { - QObject::connect( - proxyWindow, - &QObject::destroyed, - this, - &Toplevel::onRectangleProxyDestroyed - ); - - QObject::connect( - proxyWindow, - &ProxyWindowBase::windowConnected, - this, - &Toplevel::onRectangleProxyConnected - ); - } - } - - this->rectangle = rect; - this->handle->setRectangle(proxyWindow->backingWindow(), rect); -} - -void Toplevel::unsetRectangle() { this->setRectangle(nullptr, QRect()); } - -void Toplevel::onRectangleProxyConnected() { - this->handle->setRectangle(this->rectWindow->backingWindow(), this->rectangle); -} - -void Toplevel::onRectangleProxyDestroyed() { - this->rectWindow = nullptr; - this->rectangle = QRect(); -} - -ToplevelManager::ToplevelManager() { - auto* manager = impl::ToplevelManager::instance(); - - QObject::connect( - manager, - &impl::ToplevelManager::toplevelReady, - this, - &ToplevelManager::onToplevelReady - ); - - for (auto* handle: manager->readyToplevels()) { - this->onToplevelReady(handle); - } -} - -Toplevel* ToplevelManager::forImpl(impl::ToplevelHandle* impl) const { - if (impl == nullptr) return nullptr; - - for (auto* toplevel: this->mToplevels.valueList()) { - if (toplevel->handle == impl) return toplevel; - } - - return nullptr; -} - -ObjectModel* ToplevelManager::toplevels() { return &this->mToplevels; } - -void ToplevelManager::onToplevelReady(impl::ToplevelHandle* handle) { - auto* toplevel = new Toplevel(handle, this); - QObject::connect(toplevel, &Toplevel::closed, this, &ToplevelManager::onToplevelClosed); - this->mToplevels.insertObject(toplevel); -} - -void ToplevelManager::onToplevelClosed() { - auto* toplevel = qobject_cast(this->sender()); - this->mToplevels.removeObject(toplevel); -} - -ToplevelManager* ToplevelManager::instance() { - static auto* instance = new ToplevelManager(); // NOLINT - return instance; -} - -ObjectModel* ToplevelManagerQml::toplevels() { - return ToplevelManager::instance()->toplevels(); -} - -} // namespace qs::wayland::toplevel_management diff --git a/src/wayland/toplevel_management/qml.hpp b/src/wayland/toplevel_management/qml.hpp deleted file mode 100644 index 8bb1d551..00000000 --- a/src/wayland/toplevel_management/qml.hpp +++ /dev/null @@ -1,140 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "../../core/model.hpp" -#include "../../core/proxywindow.hpp" -#include "../../core/qmlscreen.hpp" - -namespace qs::wayland::toplevel_management { - -namespace impl { -class ToplevelManager; -class ToplevelHandle; -} // namespace impl - -///! Window from another application. -/// A window/toplevel from another application, retrievable from -/// the [ToplevelManager](../toplevelmanager). -class Toplevel: public QObject { - Q_OBJECT; - Q_PROPERTY(QString appId READ appId NOTIFY appIdChanged); - Q_PROPERTY(QString title READ title NOTIFY titleChanged); - /// Parent toplevel if this toplevel is a modal/dialog, otherwise null. - Q_PROPERTY(Toplevel* parent READ parent NOTIFY parentChanged); - /// If the window is currently activated or focused. - /// - /// Activation can be requested with the `activate()` function. - Q_PROPERTY(bool activated READ activated NOTIFY activatedChanged); - /// If the window is currently maximized. - /// - /// Maximization can be requested by setting this property, though it may - /// be ignored by the compositor. - Q_PROPERTY(bool maximized READ maximized WRITE setMaximized NOTIFY maximizedChanged); - /// If the window is currently minimized. - /// - /// Minimization can be requested by setting this property, though it may - /// be ignored by the compositor. - Q_PROPERTY(bool minimized READ minimized WRITE setMinimized NOTIFY minimizedChanged); - /// If the window is currently fullscreen. - /// - /// Fullscreen can be requested by setting this property, though it may - /// be ignored by the compositor. - /// Fullscreen can be requested on a specific screen with the `fullscreenOn()` function. - Q_PROPERTY(bool fullscreen READ fullscreen WRITE setFullscreen NOTIFY fullscreenChanged); - QML_ELEMENT; - QML_UNCREATABLE("Toplevels must be acquired from the ToplevelManager."); - -public: - explicit Toplevel(impl::ToplevelHandle* handle, QObject* parent); - - /// Request that this toplevel is activated. - /// The request may be ignored by the compositor. - Q_INVOKABLE void activate(); - - /// Request that this toplevel is fullscreened on a specific screen. - /// The request may be ignored by the compositor. - Q_INVOKABLE void fullscreenOn(QuickshellScreenInfo* screen); - - /// Provide a hint to the compositor where the visual representation - /// of this toplevel is relative to a quickshell window. - /// This hint can be used visually in operations like minimization. - Q_INVOKABLE void setRectangle(QObject* window, QRect rect); - Q_INVOKABLE void unsetRectangle(); - - [[nodiscard]] QString appId() const; - [[nodiscard]] QString title() const; - [[nodiscard]] Toplevel* parent() const; - [[nodiscard]] bool activated() const; - - [[nodiscard]] bool maximized() const; - void setMaximized(bool maximized); - - [[nodiscard]] bool minimized() const; - void setMinimized(bool minimized); - - [[nodiscard]] bool fullscreen() const; - void setFullscreen(bool fullscreen); - -signals: - void closed(); - void appIdChanged(); - void titleChanged(); - void parentChanged(); - void activatedChanged(); - void maximizedChanged(); - void minimizedChanged(); - void fullscreenChanged(); - -private slots: - void onClosed(); - void onRectangleProxyConnected(); - void onRectangleProxyDestroyed(); - -private: - impl::ToplevelHandle* handle; - ProxyWindowBase* rectWindow = nullptr; - QRect rectangle; - - friend class ToplevelManager; -}; - -class ToplevelManager: public QObject { - Q_OBJECT; - -public: - Toplevel* forImpl(impl::ToplevelHandle* impl) const; - - [[nodiscard]] ObjectModel* toplevels(); - - static ToplevelManager* instance(); - -private slots: - void onToplevelReady(impl::ToplevelHandle* handle); - void onToplevelClosed(); - -private: - explicit ToplevelManager(); - - ObjectModel mToplevels {this}; -}; - -///! Exposes a list of Toplevels. -/// Exposes a list of windows from other applications as [Toplevel](../toplevel)s via the -/// [zwlr-foreign-toplevel-management-v1](https://wayland.app/protocols/wlr-foreign-toplevel-management-unstable-v1) -/// wayland protocol. -class ToplevelManagerQml: public QObject { - Q_OBJECT; - Q_PROPERTY(ObjectModel* toplevels READ toplevels CONSTANT); - QML_NAMED_ELEMENT(ToplevelManager); - QML_SINGLETON; - -public: - explicit ToplevelManagerQml(QObject* parent = nullptr): QObject(parent) {} - - [[nodiscard]] static ObjectModel* toplevels(); -}; - -} // namespace qs::wayland::toplevel_management diff --git a/src/wayland/toplevel_management/wlr-foreign-toplevel-management-unstable-v1.xml b/src/wayland/toplevel_management/wlr-foreign-toplevel-management-unstable-v1.xml deleted file mode 100644 index 44505bbb..00000000 --- a/src/wayland/toplevel_management/wlr-foreign-toplevel-management-unstable-v1.xml +++ /dev/null @@ -1,270 +0,0 @@ - - - - Copyright © 2018 Ilia Bozhinov - - Permission to use, copy, modify, distribute, and sell this - software and its documentation for any purpose is hereby granted - without fee, provided that the above copyright notice appear in - all copies and that both that copyright notice and this permission - notice appear in supporting documentation, and that the name of - the copyright holders not be used in advertising or publicity - pertaining to distribution of the software without specific, - written prior permission. The copyright holders make no - representations about the suitability of this software for any - purpose. It is provided "as is" without express or implied - warranty. - - THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS - SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND - FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY - SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN - AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, - ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF - THIS SOFTWARE. - - - - - The purpose of this protocol is to enable the creation of taskbars - and docks by providing them with a list of opened applications and - letting them request certain actions on them, like maximizing, etc. - - After a client binds the zwlr_foreign_toplevel_manager_v1, each opened - toplevel window will be sent via the toplevel event - - - - - This event is emitted whenever a new toplevel window is created. It - is emitted for all toplevels, regardless of the app that has created - them. - - All initial details of the toplevel(title, app_id, states, etc.) will - be sent immediately after this event via the corresponding events in - zwlr_foreign_toplevel_handle_v1. - - - - - - - Indicates the client no longer wishes to receive events for new toplevels. - However the compositor may emit further toplevel_created events, until - the finished event is emitted. - - The client must not send any more requests after this one. - - - - - - This event indicates that the compositor is done sending events to the - zwlr_foreign_toplevel_manager_v1. The server will destroy the object - immediately after sending this request, so it will become invalid and - the client should free any resources associated with it. - - - - - - - A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel - window. Each app may have multiple opened toplevels. - - Each toplevel has a list of outputs it is visible on, conveyed to the - client with the output_enter and output_leave events. - - - - - This event is emitted whenever the title of the toplevel changes. - - - - - - - This event is emitted whenever the app-id of the toplevel changes. - - - - - - - This event is emitted whenever the toplevel becomes visible on - the given output. A toplevel may be visible on multiple outputs. - - - - - - - This event is emitted whenever the toplevel stops being visible on - the given output. It is guaranteed that an entered-output event - with the same output has been emitted before this event. - - - - - - - Requests that the toplevel be maximized. If the maximized state actually - changes, this will be indicated by the state event. - - - - - - Requests that the toplevel be unmaximized. If the maximized state actually - changes, this will be indicated by the state event. - - - - - - Requests that the toplevel be minimized. If the minimized state actually - changes, this will be indicated by the state event. - - - - - - Requests that the toplevel be unminimized. If the minimized state actually - changes, this will be indicated by the state event. - - - - - - Request that this toplevel be activated on the given seat. - There is no guarantee the toplevel will be actually activated. - - - - - - - The different states that a toplevel can have. These have the same meaning - as the states with the same names defined in xdg-toplevel - - - - - - - - - - - This event is emitted immediately after the zlw_foreign_toplevel_handle_v1 - is created and each time the toplevel state changes, either because of a - compositor action or because of a request in this protocol. - - - - - - - - This event is sent after all changes in the toplevel state have been - sent. - - This allows changes to the zwlr_foreign_toplevel_handle_v1 properties - to be seen as atomic, even if they happen via multiple events. - - - - - - Send a request to the toplevel to close itself. The compositor would - typically use a shell-specific method to carry out this request, for - example by sending the xdg_toplevel.close event. However, this gives - no guarantees the toplevel will actually be destroyed. If and when - this happens, the zwlr_foreign_toplevel_handle_v1.closed event will - be emitted. - - - - - - The rectangle of the surface specified in this request corresponds to - the place where the app using this protocol represents the given toplevel. - It can be used by the compositor as a hint for some operations, e.g - minimizing. The client is however not required to set this, in which - case the compositor is free to decide some default value. - - If the client specifies more than one rectangle, only the last one is - considered. - - The dimensions are given in surface-local coordinates. - Setting width=height=0 removes the already-set rectangle. - - - - - - - - - - - - - - - - This event means the toplevel has been destroyed. It is guaranteed there - won't be any more events for this zwlr_foreign_toplevel_handle_v1. The - toplevel itself becomes inert so any requests will be ignored except the - destroy request. - - - - - - Destroys the zwlr_foreign_toplevel_handle_v1 object. - - This request should be called either when the client does not want to - use the toplevel anymore or after the closed event to finalize the - destruction of the object. - - - - - - - - Requests that the toplevel be fullscreened on the given output. If the - fullscreen state and/or the outputs the toplevel is visible on actually - change, this will be indicated by the state and output_enter/leave - events. - - The output parameter is only a hint to the compositor. Also, if output - is NULL, the compositor should decide which output the toplevel will be - fullscreened on, if at all. - - - - - - - Requests that the toplevel be unfullscreened. If the fullscreen state - actually changes, this will be indicated by the state event. - - - - - - - - This event is emitted whenever the parent of the toplevel changes. - - No event is emitted when the parent handle is destroyed by the client. - - - - - diff --git a/src/wayland/wlr_layershell/surface.cpp b/src/wayland/wlr_layershell/surface.cpp index 5c369f2b..ac80ebd0 100644 --- a/src/wayland/wlr_layershell/surface.cpp +++ b/src/wayland/wlr_layershell/surface.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -17,10 +18,6 @@ #include "shell_integration.hpp" #include "window.hpp" -#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0) -#include -#endif - // clang-format off [[nodiscard]] QtWayland::zwlr_layer_shell_v1::layer toWaylandLayer(const WlrLayer::Enum& layer) noexcept; [[nodiscard]] QtWayland::zwlr_layer_surface_v1::anchor toWaylandAnchors(const Anchors& anchors) noexcept; @@ -75,10 +72,7 @@ QSWaylandLayerSurface::QSWaylandLayerSurface( } QSWaylandLayerSurface::~QSWaylandLayerSurface() { - if (this->ext != nullptr) { - this->ext->surface = nullptr; - } - + this->ext->surface = nullptr; this->destroy(); } @@ -112,7 +106,6 @@ void QSWaylandLayerSurface::applyConfigure() { } void QSWaylandLayerSurface::setWindowGeometry(const QRect& geometry) { - if (this->ext == nullptr) return; auto size = constrainedSize(this->ext->mAnchors, geometry.size()); this->set_size(size.width(), size.height()); } diff --git a/src/wayland/wlr_layershell/window.cpp b/src/wayland/wlr_layershell/window.cpp index a671d59e..035bae1d 100644 --- a/src/wayland/wlr_layershell/window.cpp +++ b/src/wayland/wlr_layershell/window.cpp @@ -13,12 +13,6 @@ #include "shell_integration.hpp" #include "surface.hpp" -LayershellWindowExtension::~LayershellWindowExtension() { - if (this->surface != nullptr) { - this->surface->ext = nullptr; - } -} - LayershellWindowExtension* LayershellWindowExtension::get(QWindow* window) { auto v = window->property("layershell_ext"); diff --git a/src/wayland/wlr_layershell/window.hpp b/src/wayland/wlr_layershell/window.hpp index 37092a6a..163f3aa7 100644 --- a/src/wayland/wlr_layershell/window.hpp +++ b/src/wayland/wlr_layershell/window.hpp @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -57,8 +56,6 @@ class LayershellWindowExtension: public QObject { public: LayershellWindowExtension(QObject* parent = nullptr): QObject(parent) {} - ~LayershellWindowExtension() override; - Q_DISABLE_COPY_MOVE(LayershellWindowExtension); // returns the layershell extension if attached, otherwise nullptr static LayershellWindowExtension* get(QWindow* window);