diff --git a/.clang-format b/.clang-format index 8ec602a..610ee65 100644 --- a/.clang-format +++ b/.clang-format @@ -1,6 +1,6 @@ AlignArrayOfStructures: None AlignAfterOpenBracket: BlockIndent -AllowShortBlocksOnASingleLine: Empty +AllowShortBlocksOnASingleLine: Always AllowShortCaseLabelsOnASingleLine: true AllowShortEnumsOnASingleLine: true AllowShortFunctionsOnASingleLine: All diff --git a/.clang-tidy b/.clang-tidy index c83ed8f..002c444 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -20,7 +20,6 @@ Checks: > -cppcoreguidelines-avoid-do-while, -cppcoreguidelines-pro-type-reinterpret-cast, -cppcoreguidelines-pro-type-vararg, - -cppcoreguidelines-use-enum-class, google-global-names-in-headers, google-readability-casting, google-runtime-int, @@ -64,8 +63,6 @@ CheckOptions: readability-identifier-naming.ParameterCase: camelBack readability-identifier-naming.VariableCase: camelBack - misc-const-correctness.WarnPointersAsPointers: false - # does not appear to work readability-operators-representation.BinaryOperators: '&&;&=;&;|;~;!;!=;||;|=;^;^=' readability-operators-representation.OverloadedOperators: '&&;&=;&;|;~;!;!=;||;|=;^;^=' diff --git a/.github/ISSUE_TEMPLATE/crash.yml b/.github/ISSUE_TEMPLATE/crash.yml index 80fa827..c8b4804 100644 --- a/.github/ISSUE_TEMPLATE/crash.yml +++ b/.github/ISSUE_TEMPLATE/crash.yml @@ -1,4 +1,4 @@ -name: Crash Report (v1) +name: Crash Report description: Quickshell has crashed labels: ["bug", "crash"] body: diff --git a/.github/ISSUE_TEMPLATE/crash2.yml b/.github/ISSUE_TEMPLATE/crash2.yml deleted file mode 100644 index 84beef8..0000000 --- a/.github/ISSUE_TEMPLATE/crash2.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Crash Report (v2) -description: Quickshell has crashed -labels: ["bug", "crash"] -body: - - type: textarea - id: userinfo - attributes: - label: What caused the crash - description: | - Any information likely to help debug the crash. What were you doing when the crash occurred, - what changes did you make, can you get it to happen again? - - type: textarea - id: report - attributes: - label: Report file - description: Attach `report.txt` here. - validations: - required: true - - type: textarea - id: logs - attributes: - label: Log file - description: | - Attach `log.qslog.log` here. If it is too big to upload, compress it. - - You can preview the log if you'd like using `quickshell read-log `. - validations: - required: true - - type: textarea - id: config - attributes: - label: Configuration - description: | - Attach your configuration here, preferrably in full (not just one file). - Compress it into a zip, tar, etc. - - This will help us reproduce the crash ourselves. - - type: textarea - id: bt - attributes: - label: Backtrace - description: | - GDB usually produces better stacktraces than quickshell can. Consider attaching a gdb backtrace - following the instructions below. - - 1. Run `coredumpctl debug ` where `pid` is the number shown after "Crashed process ID" - in the crash reporter. - 2. Once it loads, type `bt -full` (then enter) - 3. Copy the output and attach it as a file or in a spoiler. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7b8cbce..93b8458 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,23 +6,17 @@ jobs: name: Nix strategy: matrix: - qtver: [qt6.10.1, qt6.10.0, qt6.9.2, qt6.9.1, qt6.9.0, qt6.8.3, qt6.8.2, qt6.8.1, qt6.8.0, qt6.7.3, qt6.7.2, qt6.7.1, qt6.7.0, qt6.6.3, qt6.6.2, qt6.6.1, qt6.6.0] + qtver: [qt6.9.0, qt6.8.3, qt6.8.2, qt6.8.1, qt6.8.0, qt6.7.3, qt6.7.2, qt6.7.1, qt6.7.0, qt6.6.3, qt6.6.2, qt6.6.1, qt6.6.0] compiler: [clang, gcc] runs-on: ubuntu-latest - permissions: - contents: read - id-token: write steps: - uses: actions/checkout@v4 # Use cachix action over detsys for testing with act. # - uses: cachix/install-nix-action@v27 - uses: DeterminateSystems/nix-installer-action@main - - uses: DeterminateSystems/magic-nix-cache-action@main - with: - use-flakehub: false - name: Download Dependencies - run: nix-build --no-out-link --expr '((import ./ci/matrix.nix) { qtver = "${{ matrix.qtver }}"; compiler = "${{ matrix.compiler }}"; }).unwrapped.inputDerivation' + run: nix-build --no-out-link --expr '((import ./ci/matrix.nix) { qtver = "${{ matrix.qtver }}"; compiler = "${{ matrix.compiler }}"; }).inputDerivation' - name: Build run: nix-build --no-out-link --expr '(import ./ci/matrix.nix) { qtver = "${{ matrix.qtver }}"; compiler = "${{ matrix.compiler }}"; }' @@ -50,16 +44,13 @@ jobs: wayland-protocols \ wayland \ libdrm \ - vulkan-headers \ libxcb \ libpipewire \ cli11 \ - polkit \ - jemalloc \ - libunwind \ - git # for cpptrace clone + jemalloc - name: Build + # breakpad is annoying to build in ci due to makepkg not running as root run: | - cmake -GNinja -B build -DVENDOR_CPPTRACE=ON + cmake -GNinja -B build -DCRASH_REPORTER=OFF cmake --build build diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index de0c304..da329cc 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -5,17 +5,11 @@ jobs: lint: name: Lint runs-on: ubuntu-latest - permissions: - contents: read - id-token: write steps: - uses: actions/checkout@v4 # Use cachix action over detsys for testing with act. # - uses: cachix/install-nix-action@v27 - uses: DeterminateSystems/nix-installer-action@main - - uses: DeterminateSystems/magic-nix-cache-action@main - with: - use-flakehub: false - uses: nicknovitski/nix-develop@v1 - name: Check formatting diff --git a/BUILD.md b/BUILD.md index 29aecac..aa7c98a 100644 --- a/BUILD.md +++ b/BUILD.md @@ -15,7 +15,15 @@ Please make this descriptive enough to identify your specific package, for examp - `Nixpkgs` - `Fedora COPR (errornointernet/quickshell)` -Please leave at least symbol names attached to the binary for debugging purposes. +`-DDISTRIBUTOR_DEBUGINFO_AVAILABLE=YES/NO` + +If we can retrieve binaries and debug information for the package without actually running your +distribution (e.g. from an website), and you would like to strip the binary, please set this to `YES`. + +If we cannot retrieve debug information, please set this to `NO` and +**ensure you aren't distributing stripped (non debuggable) binaries**. + +In both cases you should build with `-DCMAKE_BUILD_TYPE=RelWithDebInfo` (then split or keep the debuginfo). ### QML Module dir Currently all QML modules are statically linked to quickshell, but this is where @@ -47,7 +55,7 @@ On some distros, private Qt headers are in separate packages which you may have We currently require private headers for the following libraries: - `qt6declarative` -- `qt6wayland` (for Qt versions prior to 6.10) +- `qt6wayland` We recommend an implicit dependency on `qt6svg`. If it is not installed, svg images and svg icons will not work, including system ones. @@ -56,18 +64,14 @@ At least Qt 6.6 is required. All features are enabled by default and some have their own dependencies. -### Crash Handler -The crash reporter catches crashes, restarts Quickshell when it crashes, +### Crash Reporter +The crash reporter catches crashes, restarts quickshell when it crashes, and collects useful crash information in one place. Leaving this enabled will enable us to fix bugs far more easily. -To disable: `-DCRASH_HANDLER=OFF` +To disable: `-DCRASH_REPORTER=OFF` -Dependencies: `cpptrace` - -Note: `-DVENDOR_CPPTRACE=ON` can be set to vendor cpptrace using FetchContent. - -When using FetchContent, `libunwind` is required, and `libdwarf` can be provided by the package manager or fetched with FetchContent. +Dependencies: `google-breakpad` (static library) ### Jemalloc We recommend leaving Jemalloc enabled as it will mask memory fragmentation caused @@ -100,7 +104,7 @@ Currently supported Qt versions: `6.6`, `6.7`. To disable: `-DWAYLAND=OFF` Dependencies: - - `qt6wayland` (for Qt versions prior to 6.10) + - `qt6wayland` - `wayland` (libwayland-client) - `wayland-scanner` (build time) - `wayland-protocols` (static library) @@ -142,7 +146,6 @@ To disable: `-DSCREENCOPY=OFF` Dependencies: - `libdrm` - `libgbm` -- `vulkan-headers` (build-time) Specific protocols can also be disabled: - `DSCREENCOPY_ICC=OFF` - Disable screencopy via [ext-image-copy-capture-v1] @@ -189,13 +192,6 @@ To disable: `-DSERVICE_PAM=OFF` Dependencies: `pam` -### Polkit -This feature enables creating Polkit agents that can prompt user for authentication. - -To disable: `-DSERVICE_POLKIT=OFF` - -Dependencies: `polkit`, `glib` - ### Hyprland This feature enables hyprland specific integrations. It requires wayland support but has no extra dependencies. diff --git a/CMakeLists.txt b/CMakeLists.txt index d57e322..9ef5b98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,5 @@ cmake_minimum_required(VERSION 3.20) -project(quickshell VERSION "0.2.1" LANGUAGES CXX C) - -set(UNRELEASED_FEATURES) +project(quickshell VERSION "0.1.0" LANGUAGES CXX C) set(QT_MIN_VERSION "6.6.0") set(CMAKE_CXX_STANDARD 20) @@ -40,17 +38,14 @@ string(APPEND QS_BUILD_OPTIONS " Distributor: ${DISTRIBUTOR}") message(STATUS "Quickshell configuration") message(STATUS " Distributor: ${DISTRIBUTOR}") +boption(DISTRIBUTOR_DEBUGINFO_AVAILABLE "Distributor provided debuginfo" NO) boption(NO_PCH "Disable precompild headers (dev)" OFF) boption(BUILD_TESTING "Build tests (dev)" OFF) boption(ASAN "ASAN (dev)" OFF) # note: better output with gcc than clang boption(FRAME_POINTERS "Keep Frame Pointers (dev)" ${ASAN}) -if (CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") - boption(USE_JEMALLOC "Use jemalloc" OFF) -else() - boption(USE_JEMALLOC "Use jemalloc" ON) -endif() -boption(CRASH_HANDLER "Crash Handling" ON) +boption(CRASH_REPORTER "Crash Handling" ON) +boption(USE_JEMALLOC "Use jemalloc" ON) boption(SOCKETS "Unix Sockets" ON) boption(WAYLAND "Wayland" ON) boption(WAYLAND_WLR_LAYERSHELL " Wlroots Layer-Shell" ON REQUIRES WAYLAND) @@ -72,12 +67,10 @@ boption(SERVICE_STATUS_NOTIFIER "System Tray" ON) boption(SERVICE_PIPEWIRE "PipeWire" ON) boption(SERVICE_MPRIS "Mpris" ON) boption(SERVICE_PAM "Pam" ON) -boption(SERVICE_POLKIT "Polkit" ON) boption(SERVICE_GREETD "Greetd" ON) boption(SERVICE_UPOWER "UPower" ON) boption(SERVICE_NOTIFICATIONS "Notifications" ON) boption(BLUETOOTH "Bluetooth" ON) -boption(NETWORK "Network" ON) include(cmake/install-qml-module.cmake) include(cmake/util.cmake) @@ -107,7 +100,6 @@ if (NOT CMAKE_BUILD_TYPE) endif() set(QT_FPDEPS Gui Qml Quick QuickControls2 Widgets ShaderTools) -set(QT_PRIVDEPS QuickPrivate) include(cmake/pch.cmake) @@ -123,10 +115,9 @@ endif() if (WAYLAND) list(APPEND QT_FPDEPS WaylandClient) - list(APPEND QT_PRIVDEPS WaylandClientPrivate) endif() -if (SERVICE_STATUS_NOTIFIER OR SERVICE_MPRIS OR SERVICE_UPOWER OR SERVICE_NOTIFICATIONS OR BLUETOOTH OR NETWORK) +if (SERVICE_STATUS_NOTIFIER OR SERVICE_MPRIS OR SERVICE_UPOWER OR SERVICE_NOTIFICATIONS OR BLUETOOTH) set(DBUS ON) endif() @@ -136,13 +127,6 @@ endif() find_package(Qt6 REQUIRED COMPONENTS ${QT_FPDEPS}) -# In Qt 6.10, private dependencies are required to be explicit, -# but they could not be explicitly depended on prior to 6.9. -if (Qt6_VERSION VERSION_GREATER_EQUAL "6.9.0") - set(QT_NO_PRIVATE_MODULE_WARNING ON) - find_package(Qt6 REQUIRED COMPONENTS ${QT_PRIVDEPS}) -endif() - set(CMAKE_AUTOUIC OFF) qt_standard_project_setup(REQUIRES 6.6) set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/qml_modules) diff --git a/Justfile b/Justfile index 801eb2a..f60771a 100644 --- a/Justfile +++ b/Justfile @@ -12,9 +12,6 @@ lint-ci: lint-changed: git diff --name-only HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }} -lint-staged: - git diff --staged --name-only --diff-filter=d HEAD | grep "^.*\.cpp\$" | parallel -j$(nproc) --no-notice --will-cite --tty --bar clang-tidy --load={{ env_var("TIDYFOX") }} - configure target='debug' *FLAGS='': cmake -GNinja -B {{builddir}} \ -DCMAKE_BUILD_TYPE={{ if target == "debug" { "Debug" } else { "RelWithDebInfo" } }} \ diff --git a/changelog/next.md b/changelog/next.md deleted file mode 100644 index ef63323..0000000 --- a/changelog/next.md +++ /dev/null @@ -1,60 +0,0 @@ -## Breaking Changes - -### Config paths are no longer canonicalized - -This fixes nix configs changing shell-ids on rebuild as the shell id is now derived from -the symlink path. Configs with a symlink in their path will have a different shell id. - -Shell ids are used to derive the default config / state / cache folders, so those files -will need to be manually moved if using a config behind a symlinked path without an explicitly -set shell id. - -## New Features - -- Added support for creating Polkit agents. -- Added support for creating wayland idle inhibitors. -- Added support for wayland idle timeouts. -- Added support for inhibiting wayland compositor shortcuts for focused windows. -- Added the ability to override Quickshell.cacheDir with a custom path. -- Added minimized, maximized, and fullscreen properties to FloatingWindow. -- Added the ability to handle move and resize events to FloatingWindow. -- Pipewire service now reconnects if pipewire dies or a protocol error occurs. -- Added pipewire audio peak detection. -- Added initial support for network management. -- Added support for grabbing focus from popup windows. -- Added support for IPC signal listeners. -- Added Quickshell version checking and version gated preprocessing. -- Added a way to detect if an icon is from the system icon theme or not. -- Added vulkan support to screencopy. - -## Other Changes - -- FreeBSD is now partially supported. -- IPC operations filter available instances to the current display connection by default. -- PwNodeLinkTracker ignores sound level monitoring programs. -- Replaced breakpad with cpptrace. - -## Bug Fixes - -- Fixed volume control breaking with pipewire pro audio mode. -- Fixed volume control breaking with bluez streams and potentially others. -- Fixed volume control breaking for devices without route definitions. -- Fixed escape sequence handling in desktop entries. -- Fixed volumes not initializing if a pipewire device was already loaded before its node. -- Fixed hyprland active toplevel not resetting after window closes. -- Fixed hyprland ipc window names and titles being reversed. -- Fixed missing signals for system tray item title and description updates. -- Fixed asynchronous loaders not working after reload. -- Fixed asynchronous loaders not working before window creation. -- Fixed memory leak in IPC handlers. -- Fixed ClippingRectangle related crashes. -- Fixed crashes when monitors are unplugged. -- Fixed crashes when default pipewire devices are lost. -- Desktop action order is now preserved. - -## Packaging Changes - -- `glib` and `polkit` have been added as dependencies when compiling with polkit agent support. -- `vulkan-headers` has been added as a build-time dependency for screencopy (Vulkan backend support). -- `breakpad` has been replaced by `cpptrace`, which is far easier to package, and the `CRASH_REPORTER` cmake variable has been replaced with `CRASH_HANDLER` to stop this from being easy to ignore. -- `DISTRIBUTOR_DEBUGINFO_AVAILABLE` was removed as it is no longer important without breakpad. diff --git a/changelog/v0.1.0.md b/changelog/v0.1.0.md deleted file mode 100644 index f8a032f..0000000 --- a/changelog/v0.1.0.md +++ /dev/null @@ -1 +0,0 @@ -Initial release diff --git a/changelog/v0.2.0.md b/changelog/v0.2.0.md deleted file mode 100644 index 2fbf74d..0000000 --- a/changelog/v0.2.0.md +++ /dev/null @@ -1,84 +0,0 @@ -## Breaking Changes - -- Files outside of the shell directory can no longer be referenced with relative paths, e.g. '../../foo.png'. -- PanelWindow's Automatic exclusion mode now adds an exclusion zone for panels with a single anchor. -- `QT_QUICK_CONTROLS_STYLE` and `QT_STYLE_OVERRIDE` are ignored unless `//@ pragma RespectSystemStyle` is set. - -## New Features - -### Root-Relative Imports - -Quickshell 0.2 comes with a new method to import QML modules which is supported by QMLLS. -This replaces "root:/" imports for QML modules. - -The new syntax is `import qs.path.to.module`, where `path/to/module` is the path to -a module/subdirectory relative to the config root (`qs`). - -### Better LSP support - -LSP support for Singletons and Root-Relative imports can be enabled by creating a file named -`.qmlls.ini` in the shell root directory. Quickshell will detect this file and automatically -populate it with an LSP configuration. This file should be gitignored in your configuration, -as it is system dependent. - -The generated configuration also includes QML import paths available to Quickshell, meaning -QMLLS no longer requires the `-E` flag. - -### Bluetooth Module - -Quickshell can now manage your bluetooth devices through BlueZ. While authenticated pairing -has not landed in 0.2, support for connecting and disconnecting devices, basic device information, -and non-authenticated pairing are now supported. - -### Other Features - -- Added `HyprlandToplevel` and related toplevel/window management APIs in the Hyprland module. -- Added `Quickshell.execDetached()`, which spawns a detached process without a `Process` object. -- Added `Process.exec()` for easier reconfiguration of process commands when starting them. -- Added `FloatingWindow.title`, which allows changing the title of a floating window. -- Added `signal QsWindow.closed()`, fired when a window is closed externally. -- Added support for inline replies in notifications, when supported by applications. -- Added `DesktopEntry.startupWmClass` and `DesktopEntry.heuristicLookup()` to better identify toplevels. -- Added `DesktopEntry.command` which can be run as an alternative to `DesktopEntry.execute()`. -- Added `//@ pragma Internal`, which makes a QML component impossible to import outside of its module. -- Added dead instance selection for some subcommands, such as `qs log` and `qs list`. - -## Other Changes - -- `Quickshell.shellRoot` has been renamed to `Quickshell.shellDir`. -- PanelWindow margins opposite the window's anchorpoint are now added to exclusion zone. -- stdout/stderr or detached processes and executed desktop entries are now hidden by default. -- Various warnings caused by other applications Quickshell communicates with over D-BUS have been hidden in logs. -- Quickshell's new logo is now shown in any floating windows. - -## Bug Fixes - -- Fixed pipewire device volume and mute states not updating before the device has been used. -- Fixed a crash when changing the volume of any pipewire device on a sound card another removed device was using. -- Fixed a crash when accessing a removed previous default pipewire node from the default sink/source changed signals. -- Fixed session locks crashing if all monitors are disconnected. -- Fixed session locks crashing if unsupported by the compositor. -- Fixed a crash when creating a session lock and destroying it before acknowledged by the compositor. -- Fixed window input masks not updating after a reload. -- Fixed PanelWindows being unconfigurable unless `screen` was set under X11. -- Fixed a crash when anchoring a popup to a zero sized `Item`. -- Fixed `FileView` crashing if `watchChanges` was used. -- Fixed `SocketServer` sockets disappearing after a reload. -- Fixed `ScreencopyView` having incorrect rotation when displaying a rotated monitor. -- Fixed `MarginWrapperManager` breaking pixel alignment of child items when centering. -- Fixed `IpcHandler`, `NotificationServer` and `GlobalShortcut` not activating with certain QML structures. -- Fixed tracking of QML incubator destruction and deregistration, which occasionally caused crashes. -- Fixed FloatingWindows being constrained to the smallest window manager supported size unless max size was set. -- Fixed `MprisPlayer.lengthSupported` not updating reactively. -- Fixed normal tray icon being ignored when status is `NeedsAttention` and no attention icon is provided. -- Fixed `HyprlandWorkspace.activate()` sending invalid commands to Hyprland for named or special workspaces. -- Fixed file watcher occasionally breaking when using VSCode to edit QML files. -- Fixed crashes when screencopy buffer creation fails. -- Fixed a crash when wayland layer surfaces are recreated for the same window. -- Fixed the `QsWindow` attached object not working when using `WlrLayershell` directly. -- Fixed a crash when attempting to create a window without available VRAM. -- Fixed OOM crash when failing to write to detailed log file. -- Prevented distro logging configurations for Qt from interfering with Quickshell commands. -- Removed the "QProcess destroyed for running process" warning when destroying `Process` objects. -- Fixed `ColorQuantizer` printing a pointer to an error message instead of an error message. -- Fixed notification pixmap rowstride warning showing for correct rowstrides. diff --git a/changelog/v0.2.1.md b/changelog/v0.2.1.md deleted file mode 100644 index 596b82f..0000000 --- a/changelog/v0.2.1.md +++ /dev/null @@ -1,17 +0,0 @@ -## New Features - -- Changes to desktop entries are now tracked in real time. - -## Other Changes - -- Added support for Qt 6.10 - -## Bug Fixes - -- Fixed volumes getting stuck on change for pipewire devices with few volume steps. -- Fixed a crash when running out of disk space to write log files. -- Fixed a rare crash when disconnecting a monitor. -- Fixed build issues preventing cross compilation from working. -- Fixed dekstop entries with lower priority than a hidden entry not being hidden. -- Fixed desktop entry keys with mismatched modifier or country not being discarded. -- Fixed greetd hanging when authenticating with a fingerprint. diff --git a/ci/matrix.nix b/ci/matrix.nix index dd20fa5..be2da61 100644 --- a/ci/matrix.nix +++ b/ci/matrix.nix @@ -2,10 +2,7 @@ qtver, compiler, }: let - checkouts = import ./nix-checkouts.nix; - nixpkgs = checkouts.${builtins.replaceStrings ["."] ["_"] qtver}; + nixpkgs = (import ./nix-checkouts.nix).${builtins.replaceStrings ["."] ["_"] qtver}; compilerOverride = (nixpkgs.callPackage ./variations.nix {}).${compiler}; - pkg = (nixpkgs.callPackage ../default.nix {}).override (compilerOverride // { - wayland-protocols = checkouts.latest.wayland-protocols; - }); + pkg = (nixpkgs.callPackage ../default.nix {}).override compilerOverride; in pkg diff --git a/ci/nix-checkouts.nix b/ci/nix-checkouts.nix index 945973c..73c2415 100644 --- a/ci/nix-checkouts.nix +++ b/ci/nix-checkouts.nix @@ -7,28 +7,9 @@ let url = "https://github.com/nixos/nixpkgs/archive/${commit}.tar.gz"; inherit sha256; }) {}; -in rec { - latest = qt6_10_0; - - qt6_10_1 = byCommit { - commit = "ffbc9f8cbaacfb331b6017d5a5abb21a492c9a38"; - sha256 = "0fvbizl7j5rv2rf8j76yw0xb3d9l06hahkjys2a7k1yraznvnafm"; - }; - - qt6_10_0 = byCommit { - commit = "c5ae371f1a6a7fd27823bc500d9390b38c05fa55"; - sha256 = "18g0f8cb9m8mxnz9cf48sks0hib79b282iajl2nysyszph993yp0"; - }; - - qt6_9_2 = byCommit { - commit = "e9f00bd893984bc8ce46c895c3bf7cac95331127"; - sha256 = "0s2mhbrgzxlgkg2yxb0q0hpk8lby1a7w67dxvfmaz4gsmc0bnvfj"; - }; - - qt6_9_1 = byCommit { - commit = "4c202d26483c5ccf3cb95e0053163facde9f047e"; - sha256 = "06l2w4bcgfw7dfanpzpjcf25ydf84in240yplqsss82qx405y9di"; - }; +in { + # For old qt versions, grab the commit before the version bump that has all the patches + # instead of the bumped version. qt6_9_0 = byCommit { commit = "546c545bd0594809a28ab7e869b5f80dd7243ef6"; diff --git a/ci/variations.nix b/ci/variations.nix index b1d2947..b0889be 100644 --- a/ci/variations.nix +++ b/ci/variations.nix @@ -2,6 +2,6 @@ clangStdenv, gccStdenv, }: { - clang = { stdenv = clangStdenv; }; - gcc = { stdenv = gccStdenv; }; + clang = { buildStdenv = clangStdenv; }; + gcc = { buildStdenv = gccStdenv; }; } diff --git a/default.nix b/default.nix index 02b8659..73cd8d1 100644 --- a/default.nix +++ b/default.nix @@ -2,31 +2,25 @@ lib, nix-gitignore, pkgs, - stdenv, keepDebugInfo, + buildStdenv ? pkgs.clangStdenv, pkg-config, cmake, ninja, spirv-tools, qt6, - cpptrace ? null, - libunwind, - libdwarf, + breakpad, jemalloc, cli11, wayland, wayland-protocols, wayland-scanner, xorg, - libxcb ? xorg.libxcb, libdrm, libgbm ? null, - vulkan-headers, pipewire, pam, - polkit, - glib, gitRev ? (let headExists = builtins.pathExists ./.git/HEAD; @@ -49,106 +43,64 @@ withPam ? true, withHyprland ? true, withI3 ? true, - withPolkit ? true, - withNetworkManager ? true, -}: let - withCrashHandler = withCrashReporter && cpptrace != null && lib.strings.compareVersions cpptrace.version "0.7.2" >= 0; +}: buildStdenv.mkDerivation { + pname = "quickshell${lib.optionalString debug "-debug"}"; + version = "0.1.0"; + src = nix-gitignore.gitignoreSource [] ./.; - unwrapped = stdenv.mkDerivation { - pname = "quickshell${lib.optionalString debug "-debug"}"; - version = "0.2.1"; - src = nix-gitignore.gitignoreSource "/default.nix\n" ./.; + nativeBuildInputs = [ + cmake + ninja + qt6.qtshadertools + spirv-tools + qt6.wrapQtAppsHook + pkg-config + ] + ++ lib.optional withWayland wayland-scanner; - dontWrapQtApps = true; # see wrappers + buildInputs = [ + qt6.qtbase + qt6.qtdeclarative + cli11 + ] + ++ lib.optional withQtSvg qt6.qtsvg + ++ lib.optional withCrashReporter breakpad + ++ lib.optional withJemalloc jemalloc + ++ lib.optionals withWayland [ qt6.qtwayland wayland wayland-protocols ] + ++ lib.optionals (withWayland && libgbm != null) [ libdrm libgbm ] + ++ lib.optional withX11 xorg.libxcb + ++ lib.optional withPam pam + ++ lib.optional withPipewire pipewire; - nativeBuildInputs = [ - cmake - ninja - spirv-tools - pkg-config - ] - ++ lib.optional (withWayland && lib.strings.compareVersions qt6.qtbase.version "6.10.0" == -1) qt6.qtwayland - ++ lib.optionals withWayland [ - qt6.qtwayland # qtwaylandscanner required at build time - wayland-scanner - ]; + cmakeBuildType = if debug then "Debug" else "RelWithDebInfo"; - buildInputs = [ - qt6.qtbase - qt6.qtdeclarative - cli11 - ] - ++ lib.optional withQtSvg qt6.qtsvg - ++ lib.optional withCrashHandler (cpptrace.overrideAttrs (prev: { - cmakeFlags = prev.cmakeFlags ++ [ - "-DCPPTRACE_UNWIND_WITH_LIBUNWIND=TRUE" - ]; - buildInputs = prev.buildInputs ++ [ libunwind ]; - })) - ++ lib.optional withJemalloc jemalloc - ++ lib.optional (withWayland && lib.strings.compareVersions qt6.qtbase.version "6.10.0" == -1) qt6.qtwayland - ++ lib.optionals withWayland [ wayland wayland-protocols ] - ++ lib.optionals (withWayland && libgbm != null) [ libdrm libgbm vulkan-headers ] - ++ lib.optional withX11 libxcb - ++ lib.optional withPam pam - ++ lib.optional withPipewire pipewire - ++ lib.optionals withPolkit [ polkit glib ]; + cmakeFlags = [ + (lib.cmakeFeature "DISTRIBUTOR" "Official-Nix-Flake") + (lib.cmakeFeature "INSTALL_QML_PREFIX" qt6.qtbase.qtQmlPrefix) + (lib.cmakeBool "DISTRIBUTOR_DEBUGINFO_AVAILABLE" true) + (lib.cmakeFeature "GIT_REVISION" gitRev) + (lib.cmakeBool "CRASH_REPORTER" withCrashReporter) + (lib.cmakeBool "USE_JEMALLOC" withJemalloc) + (lib.cmakeBool "WAYLAND" withWayland) + (lib.cmakeBool "SCREENCOPY" (libgbm != null)) + (lib.cmakeBool "SERVICE_PIPEWIRE" withPipewire) + (lib.cmakeBool "SERVICE_PAM" withPam) + (lib.cmakeBool "HYPRLAND" withHyprland) + (lib.cmakeBool "I3" withI3) + ]; - cmakeBuildType = if debug then "Debug" else "RelWithDebInfo"; + # How to get debuginfo in gdb from a release build: + # 1. build `quickshell.debug` + # 2. set NIX_DEBUG_INFO_DIRS="/lib/debug" + # 3. launch gdb / coredumpctl and debuginfo will work + separateDebugInfo = !debug; + dontStrip = debug; - cmakeFlags = [ - (lib.cmakeFeature "DISTRIBUTOR" "Official-Nix-Flake") - (lib.cmakeFeature "INSTALL_QML_PREFIX" qt6.qtbase.qtQmlPrefix) - (lib.cmakeBool "DISTRIBUTOR_DEBUGINFO_AVAILABLE" true) - (lib.cmakeFeature "GIT_REVISION" gitRev) - (lib.cmakeBool "CRASH_HANDLER" withCrashHandler) - (lib.cmakeBool "USE_JEMALLOC" withJemalloc) - (lib.cmakeBool "WAYLAND" withWayland) - (lib.cmakeBool "SCREENCOPY" (libgbm != null)) - (lib.cmakeBool "SERVICE_PIPEWIRE" withPipewire) - (lib.cmakeBool "SERVICE_PAM" withPam) - (lib.cmakeBool "SERVICE_NETWORKMANAGER" withNetworkManager) - (lib.cmakeBool "SERVICE_POLKIT" withPolkit) - (lib.cmakeBool "HYPRLAND" withHyprland) - (lib.cmakeBool "I3" withI3) - ]; - - # How to get debuginfo in gdb from a release build: - # 1. build `quickshell.debug` - # 2. set NIX_DEBUG_INFO_DIRS="/lib/debug" - # 3. launch gdb / coredumpctl and debuginfo will work - separateDebugInfo = !debug; - dontStrip = debug; - - meta = with lib; { - homepage = "https://quickshell.org"; - description = "Flexbile QtQuick based desktop shell toolkit"; - license = licenses.lgpl3Only; - platforms = platforms.linux; - mainProgram = "quickshell"; - }; + meta = with lib; { + homepage = "https://quickshell.outfoxxed.me"; + description = "Flexbile QtQuick based desktop shell toolkit"; + license = licenses.lgpl3Only; + platforms = platforms.linux; + mainProgram = "quickshell"; }; - - wrapper = unwrapped.stdenv.mkDerivation { - inherit (unwrapped) version meta buildInputs; - pname = "${unwrapped.pname}-wrapped"; - - nativeBuildInputs = unwrapped.nativeBuildInputs ++ [ qt6.wrapQtAppsHook ]; - - dontUnpack = true; - dontConfigure = true; - dontBuild = true; - - installPhase = '' - mkdir -p $out - cp -r ${unwrapped}/* $out - ''; - - passthru = { - unwrapped = unwrapped; - withModules = modules: wrapper.overrideAttrs (prev: { - buildInputs = prev.buildInputs ++ modules; - }); - }; - }; -in wrapper +} diff --git a/flake.lock b/flake.lock index 2f95a44..7c25aa2 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1768127708, - "narHash": "sha256-1Sm77VfZh3mU0F5OqKABNLWxOuDeHIlcFjsXeeiPazs=", + "lastModified": 1749285348, + "narHash": "sha256-frdhQvPbmDYaScPFiCnfdh3B/Vh81Uuoo0w5TkWmmjU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ffbc9f8cbaacfb331b6017d5a5abb21a492c9a38", + "rev": "3e3afe5174c561dee0df6f2c2b2236990146329f", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 8edda2c..5de9c96 100644 --- a/flake.nix +++ b/flake.nix @@ -4,28 +4,23 @@ }; outputs = { self, nixpkgs }: let - overlayPkgs = p: p.appendOverlays [ self.overlays.default ]; - forEachSystem = fn: nixpkgs.lib.genAttrs nixpkgs.lib.platforms.linux - (system: fn system (overlayPkgs nixpkgs.legacyPackages.${system})); + (system: fn system nixpkgs.legacyPackages.${system}); in { - overlays.default = import ./overlay.nix { - rev = self.rev or self.dirtyRev; - }; - packages = forEachSystem (system: pkgs: rec { - quickshell = pkgs.quickshell; + quickshell = pkgs.callPackage ./default.nix { + gitRev = self.rev or self.dirtyRev; + }; + default = quickshell; }); devShells = forEachSystem (system: pkgs: rec { default = import ./shell.nix { inherit pkgs; - quickshell = self.packages.${system}.quickshell.override { - stdenv = pkgs.clangStdenv; - }; + inherit (self.packages.${system}) quickshell; }; }); }; diff --git a/overlay.nix b/overlay.nix deleted file mode 100644 index d8ea137..0000000 --- a/overlay.nix +++ /dev/null @@ -1,5 +0,0 @@ -{ rev ? null }: (final: prev: { - quickshell = final.callPackage ./default.nix { - gitRev = rev; - }; -}) diff --git a/quickshell.scm b/quickshell.scm index 780bb96..26abdc0 100644 --- a/quickshell.scm +++ b/quickshell.scm @@ -42,7 +42,6 @@ libxcb libxkbcommon linux-pam - polkit mesa pipewire qtbase @@ -56,7 +55,8 @@ #~(list "-GNinja" "-DDISTRIBUTOR=\"In-tree Guix channel\"" "-DDISTRIBUTOR_DEBUGINFO_AVAILABLE=NO" - "-DCRASH_HANDLER=OFF") + ;; Breakpad is not currently packaged for Guix. + "-DCRASH_REPORTER=OFF") #:phases #~(modify-phases %standard-phases (replace 'build (lambda _ (invoke "cmake" "--build" "."))) diff --git a/shell.nix b/shell.nix index 03a446d..82382f9 100644 --- a/shell.nix +++ b/shell.nix @@ -1,15 +1,14 @@ { pkgs ? import {}, - stdenv ? pkgs.clangStdenv, # faster compiles than gcc - quickshell ? pkgs.callPackage ./default.nix { inherit stdenv; }, + quickshell ? pkgs.callPackage ./default.nix {}, ... }: let tidyfox = import (pkgs.fetchFromGitea { domain = "git.outfoxxed.me"; owner = "outfoxxed"; repo = "tidyfox"; - rev = "9d85d7e7dea2602aa74ec3168955fee69967e92f"; - hash = "sha256-77ERiweF6lumonp2c/124rAoVG6/o9J+Aajhttwtu0w="; + rev = "1f062cc198d1112d13e5128fa1f2ee3dbffe613b"; + sha256 = "kbt0Zc1qHE5fhqBkKz8iue+B+ZANjF1AR/RdgmX1r0I="; }) { inherit pkgs; }; in pkgs.mkShell.override { stdenv = quickshell.stdenv; } { inputsFrom = [ quickshell ]; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0c05419..95e5a40 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -13,7 +13,7 @@ add_subdirectory(widgets) add_subdirectory(ui) add_subdirectory(windowmanager) -if (CRASH_HANDLER) +if (CRASH_REPORTER) add_subdirectory(crash) endif() @@ -34,7 +34,3 @@ add_subdirectory(services) if (BLUETOOTH) add_subdirectory(bluetooth) endif() - -if (NETWORK) - add_subdirectory(network) -endif() diff --git a/src/bluetooth/adapter.cpp b/src/bluetooth/adapter.cpp index 7f70a27..92ab837 100644 --- a/src/bluetooth/adapter.cpp +++ b/src/bluetooth/adapter.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "../core/logcat.hpp" @@ -52,12 +53,6 @@ QString BluetoothAdapter::adapterId() const { void BluetoothAdapter::setEnabled(bool enabled) { if (enabled == this->bEnabled) return; - - if (enabled && this->bState == BluetoothAdapterState::Blocked) { - qCCritical(logAdapter) << "Cannot enable adapter because it is blocked by rfkill."; - return; - } - this->bEnabled = enabled; this->pEnabled.write(); } diff --git a/src/bluetooth/device.cpp b/src/bluetooth/device.cpp index b140aa0..7265b24 100644 --- a/src/bluetooth/device.cpp +++ b/src/bluetooth/device.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include diff --git a/src/build/CMakeLists.txt b/src/build/CMakeLists.txt index c1ffa59..bb35da9 100644 --- a/src/build/CMakeLists.txt +++ b/src/build/CMakeLists.txt @@ -9,10 +9,16 @@ if (NOT DEFINED GIT_REVISION) ) endif() -if (CRASH_HANDLER) - set(CRASH_HANDLER_DEF 1) +if (CRASH_REPORTER) + set(CRASH_REPORTER_DEF 1) else() - set(CRASH_HANDLER_DEF 0) + set(CRASH_REPORTER_DEF 0) +endif() + +if (DISTRIBUTOR_DEBUGINFO_AVAILABLE) + set(DEBUGINFO_AVAILABLE 1) +else() + set(DEBUGINFO_AVAILABLE 0) endif() configure_file(build.hpp.in build.hpp @ONLY ESCAPE_QUOTES) diff --git a/src/build/build.hpp.in b/src/build/build.hpp.in index 2ab2db2..075abd1 100644 --- a/src/build/build.hpp.in +++ b/src/build/build.hpp.in @@ -1,14 +1,10 @@ #pragma once // NOLINTBEGIN -#define QS_VERSION "@quickshell_VERSION@" -#define QS_VERSION_MAJOR @quickshell_VERSION_MAJOR@ -#define QS_VERSION_MINOR @quickshell_VERSION_MINOR@ -#define QS_VERSION_PATCH @quickshell_VERSION_PATCH@ -#define QS_UNRELEASED_FEATURES "@UNRELEASED_FEATURES@" #define GIT_REVISION "@GIT_REVISION@" #define DISTRIBUTOR "@DISTRIBUTOR@" -#define CRASH_HANDLER @CRASH_HANDLER_DEF@ +#define DISTRIBUTOR_DEBUGINFO_AVAILABLE @DEBUGINFO_AVAILABLE@ +#define CRASH_REPORTER @CRASH_REPORTER_DEF@ #define BUILD_TYPE "@CMAKE_BUILD_TYPE@" #define COMPILER "@CMAKE_CXX_COMPILER_ID@ (@CMAKE_CXX_COMPILER_VERSION@)" #define COMPILE_FLAGS "@CMAKE_CXX_FLAGS@" diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index fb63f40..eca7270 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -12,7 +12,6 @@ qt_add_library(quickshell-core STATIC singleton.cpp generation.cpp scan.cpp - scanenv.cpp qsintercept.cpp incubator.cpp lazyloader.cpp @@ -24,7 +23,7 @@ qt_add_library(quickshell-core STATIC model.cpp elapsedtimer.cpp desktopentry.cpp - desktopentrymonitor.cpp + objectrepeater.cpp platformmenu.cpp qsmenu.cpp retainable.cpp @@ -39,7 +38,6 @@ qt_add_library(quickshell-core STATIC iconprovider.cpp scriptmodel.cpp colorquantizer.cpp - toolsupport.cpp ) qt_add_qml_module(quickshell-core @@ -52,7 +50,7 @@ qt_add_qml_module(quickshell-core install_qml_module(quickshell-core) -target_link_libraries(quickshell-core PRIVATE Qt::Quick Qt::QuickPrivate Qt::Widgets quickshell-build) +target_link_libraries(quickshell-core PRIVATE Qt::Quick Qt::Widgets) qs_module_pch(quickshell-core SET large) diff --git a/src/core/colorquantizer.cpp b/src/core/colorquantizer.cpp index 4ac850b..6cfb05d 100644 --- a/src/core/colorquantizer.cpp +++ b/src/core/colorquantizer.cpp @@ -28,28 +28,26 @@ ColorQuantizerOperation::ColorQuantizerOperation(QUrl* source, qreal depth, qrea : source(source) , maxDepth(depth) , rescaleSize(rescaleSize) { - this->setAutoDelete(false); + setAutoDelete(false); } void ColorQuantizerOperation::quantizeImage(const QAtomicInteger& shouldCancel) { - if (shouldCancel.loadAcquire() || this->source->isEmpty()) return; + if (shouldCancel.loadAcquire() || source->isEmpty()) return; - this->colors.clear(); + colors.clear(); - auto image = QImage(this->source->toLocalFile()); - if ((image.width() > this->rescaleSize || image.height() > this->rescaleSize) - && this->rescaleSize > 0) - { + auto image = QImage(source->toLocalFile()); + if ((image.width() > rescaleSize || image.height() > rescaleSize) && rescaleSize > 0) { image = image.scaled( - static_cast(this->rescaleSize), - static_cast(this->rescaleSize), + static_cast(rescaleSize), + static_cast(rescaleSize), Qt::KeepAspectRatio, Qt::SmoothTransformation ); } if (image.isNull()) { - qCWarning(logColorQuantizer) << "Failed to load image from" << this->source->toString(); + qCWarning(logColorQuantizer) << "Failed to load image from" << source->toString(); return; } @@ -65,7 +63,7 @@ void ColorQuantizerOperation::quantizeImage(const QAtomicInteger& shouldCa auto startTime = QDateTime::currentDateTime(); - this->colors = this->quantization(pixels, 0); + colors = quantization(pixels, 0); auto endTime = QDateTime::currentDateTime(); auto milliseconds = startTime.msecsTo(endTime); @@ -79,7 +77,7 @@ QList ColorQuantizerOperation::quantization( ) { if (shouldCancel.loadAcquire()) return QList(); - if (depth >= this->maxDepth || rgbValues.isEmpty()) { + if (depth >= maxDepth || rgbValues.isEmpty()) { if (rgbValues.isEmpty()) return QList(); auto totalR = 0; @@ -116,8 +114,8 @@ QList ColorQuantizerOperation::quantization( auto rightHalf = rgbValues.mid(mid); QList result; - result.append(this->quantization(leftHalf, depth + 1)); - result.append(this->quantization(rightHalf, depth + 1)); + result.append(quantization(leftHalf, depth + 1)); + result.append(quantization(rightHalf, depth + 1)); return result; } @@ -161,7 +159,7 @@ void ColorQuantizerOperation::finishRun() { } void ColorQuantizerOperation::finished() { - emit this->done(this->colors); + emit this->done(colors); delete this; } @@ -180,39 +178,39 @@ void ColorQuantizerOperation::run() { void ColorQuantizerOperation::tryCancel() { this->shouldCancel.storeRelease(true); } void ColorQuantizer::componentComplete() { - this->componentCompleted = true; - if (!this->mSource.isEmpty()) this->quantizeAsync(); + componentCompleted = true; + if (!mSource.isEmpty()) quantizeAsync(); } void ColorQuantizer::setSource(const QUrl& source) { - if (this->mSource != source) { - this->mSource = source; + if (mSource != source) { + mSource = source; emit this->sourceChanged(); - if (this->componentCompleted && !this->mSource.isEmpty()) this->quantizeAsync(); + if (this->componentCompleted && !mSource.isEmpty()) quantizeAsync(); } } void ColorQuantizer::setDepth(qreal depth) { - if (this->mDepth != depth) { - this->mDepth = depth; + if (mDepth != depth) { + mDepth = depth; emit this->depthChanged(); - if (this->componentCompleted) this->quantizeAsync(); + if (this->componentCompleted) quantizeAsync(); } } void ColorQuantizer::setRescaleSize(int rescaleSize) { - if (this->mRescaleSize != rescaleSize) { - this->mRescaleSize = rescaleSize; + if (mRescaleSize != rescaleSize) { + mRescaleSize = rescaleSize; emit this->rescaleSizeChanged(); - if (this->componentCompleted) this->quantizeAsync(); + if (this->componentCompleted) quantizeAsync(); } } void ColorQuantizer::operationFinished(const QList& result) { - this->bColors = result; + bColors = result; this->liveOperation = nullptr; emit this->colorsChanged(); } @@ -221,8 +219,7 @@ void ColorQuantizer::quantizeAsync() { if (this->liveOperation) this->cancelAsync(); qCDebug(logColorQuantizer) << "Starting color quantization asynchronously"; - this->liveOperation = - new ColorQuantizerOperation(&this->mSource, this->mDepth, this->mRescaleSize); + this->liveOperation = new ColorQuantizerOperation(&mSource, mDepth, mRescaleSize); QObject::connect( this->liveOperation, diff --git a/src/core/colorquantizer.hpp b/src/core/colorquantizer.hpp index f6e158d..d35a15a 100644 --- a/src/core/colorquantizer.hpp +++ b/src/core/colorquantizer.hpp @@ -91,13 +91,13 @@ public: [[nodiscard]] QBindable> bindableColors() { return &this->bColors; } - [[nodiscard]] QUrl source() const { return this->mSource; } + [[nodiscard]] QUrl source() const { return mSource; } void setSource(const QUrl& source); - [[nodiscard]] qreal depth() const { return this->mDepth; } + [[nodiscard]] qreal depth() const { return mDepth; } void setDepth(qreal depth); - [[nodiscard]] qreal rescaleSize() const { return this->mRescaleSize; } + [[nodiscard]] qreal rescaleSize() const { return mRescaleSize; } void setRescaleSize(int rescaleSize); signals: diff --git a/src/core/desktopentry.cpp b/src/core/desktopentry.cpp index 637f758..4673881 100644 --- a/src/core/desktopentry.cpp +++ b/src/core/desktopentry.cpp @@ -1,27 +1,21 @@ #include "desktopentry.hpp" -#include -#include #include #include #include -#include #include +#include +#include #include #include #include #include -#include #include -#include -#include +#include #include -#include -#include #include #include "../io/processcore.hpp" -#include "desktopentrymonitor.hpp" #include "logcat.hpp" #include "model.hpp" #include "qmlglobal.hpp" @@ -61,14 +55,12 @@ struct Locale { [[nodiscard]] int matchScore(const Locale& other) const { if (this->language != other.language) return 0; - - if (!other.modifier.isEmpty() && this->modifier != other.modifier) return 0; - if (!other.territory.isEmpty() && this->territory != other.territory) return 0; + auto territoryMatches = !this->territory.isEmpty() && this->territory == other.territory; + auto modifierMatches = !this->modifier.isEmpty() && this->modifier == other.modifier; auto score = 1; - - if (!other.territory.isEmpty()) score += 2; - if (!other.modifier.isEmpty()) score += 1; + if (territoryMatches) score += 2; + if (modifierMatches) score += 1; return score; } @@ -94,64 +86,56 @@ struct Locale { QDebug operator<<(QDebug debug, const Locale& locale) { auto saver = QDebugStateSaver(debug); debug.nospace() << "Locale(language=" << locale.language << ", territory=" << locale.territory - << ", modifier=" << locale.modifier << ')'; + << ", modifier" << locale.modifier << ')'; return debug; } -ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString& text) { - ParsedDesktopEntryData data; - data.id = id; +void DesktopEntry::parseEntry(const QString& text) { const auto& system = Locale::system(); auto groupName = QString(); auto entries = QHash>(); - auto actionOrder = QStringList(); - auto pendingActions = QHash(); - - auto finishCategory = [&data, &groupName, &entries, &actionOrder, &pendingActions]() { + auto finishCategory = [this, &groupName, &entries]() { if (groupName == "Desktop Entry") { - if (entries.value("Type").second != "Application") return; + if (entries["Type"].second != "Application") return; + if (entries.contains("Hidden") && entries["Hidden"].second == "true") return; for (const auto& [key, pair]: entries.asKeyValueRange()) { auto& [_, value] = pair; - data.entries.insert(key, value); + this->mEntries.insert(key, value); - if (key == "Name") data.name = value; - else if (key == "GenericName") data.genericName = value; - else if (key == "StartupWMClass") data.startupClass = value; - else if (key == "NoDisplay") data.noDisplay = value == "true"; - else if (key == "Hidden") data.hidden = value == "true"; - else if (key == "Comment") data.comment = value; - else if (key == "Icon") data.icon = value; + if (key == "Name") this->mName = value; + else if (key == "GenericName") this->mGenericName = value; + else if (key == "NoDisplay") this->mNoDisplay = value == "true"; + else if (key == "Comment") this->mComment = value; + else if (key == "Icon") this->mIcon = value; else if (key == "Exec") { - data.execString = value; - data.command = DesktopEntry::parseExecString(value); - } else if (key == "Path") data.workingDirectory = value; - else if (key == "Terminal") data.terminal = value == "true"; - else if (key == "Categories") data.categories = value.split(u';', Qt::SkipEmptyParts); - else if (key == "Keywords") data.keywords = value.split(u';', Qt::SkipEmptyParts); - else if (key == "Actions") actionOrder = value.split(u';', Qt::SkipEmptyParts); + this->mExecString = value; + this->mCommand = DesktopEntry::parseExecString(value); + } else if (key == "Path") this->mWorkingDirectory = value; + else if (key == "Terminal") this->mTerminal = value == "true"; + else if (key == "Categories") this->mCategories = value.split(u';', Qt::SkipEmptyParts); + else if (key == "Keywords") this->mKeywords = value.split(u';', Qt::SkipEmptyParts); } } else if (groupName.startsWith("Desktop Action ")) { - auto actionName = groupName.sliced(15); - DesktopActionData action; - action.id = actionName; + auto actionName = groupName.sliced(16); + auto* action = new DesktopAction(actionName, this); for (const auto& [key, pair]: entries.asKeyValueRange()) { const auto& [_, value] = pair; - action.entries.insert(key, value); + action->mEntries.insert(key, value); - if (key == "Name") action.name = value; - else if (key == "Icon") action.icon = value; + if (key == "Name") action->mName = value; + else if (key == "Icon") action->mIcon = value; else if (key == "Exec") { - action.execString = value; - action.command = DesktopEntry::parseExecString(value); + action->mExecString = value; + action->mCommand = DesktopEntry::parseExecString(value); } } - pendingActions.insert(actionName, action); + this->mActions.insert(actionName, action); } entries.clear(); @@ -197,73 +181,16 @@ ParsedDesktopEntryData DesktopEntry::parseText(const QString& id, const QString& } finishCategory(); - - for (const auto& actionId: actionOrder) { - if (pendingActions.contains(actionId)) { - data.actions.append(pendingActions.value(actionId)); - } - } - - return data; -} - -void DesktopEntry::updateState(const ParsedDesktopEntryData& newState) { - Qt::beginPropertyUpdateGroup(); - this->bName = newState.name; - this->bGenericName = newState.genericName; - this->bStartupClass = newState.startupClass; - this->bNoDisplay = newState.noDisplay; - this->bComment = newState.comment; - this->bIcon = newState.icon; - this->bExecString = newState.execString; - this->bCommand = newState.command; - this->bWorkingDirectory = newState.workingDirectory; - this->bRunInTerminal = newState.terminal; - this->bCategories = newState.categories; - this->bKeywords = newState.keywords; - Qt::endPropertyUpdateGroup(); - - this->state = newState; - this->updateActions(newState.actions); -} - -void DesktopEntry::updateActions(const QVector& newActions) { - auto old = this->mActions; - this->mActions.clear(); - - for (const auto& d: newActions) { - DesktopAction* act = nullptr; - auto found = std::ranges::find(old, d.id, &DesktopAction::mId); - if (found != old.end()) { - act = *found; - old.erase(found); - } else { - act = new DesktopAction(d.id, this); - } - - Qt::beginPropertyUpdateGroup(); - act->bName = d.name; - act->bIcon = d.icon; - act->bExecString = d.execString; - act->bCommand = d.command; - Qt::endPropertyUpdateGroup(); - - act->mEntries = d.entries; - this->mActions.append(act); - } - - for (auto* leftover: old) { - leftover->deleteLater(); - } } void DesktopEntry::execute() const { - DesktopEntry::doExec(this->bCommand.value(), this->bWorkingDirectory.value()); + DesktopEntry::doExec(this->mCommand, this->mWorkingDirectory); } -bool DesktopEntry::isValid() const { return !this->bName.value().isEmpty(); } +bool DesktopEntry::isValid() const { return !this->mName.isEmpty(); } +bool DesktopEntry::noDisplay() const { return this->mNoDisplay; } -QVector DesktopEntry::actions() const { return this->mActions; } +QVector DesktopEntry::actions() const { return this->mActions.values(); } QVector DesktopEntry::parseExecString(const QString& execString) { QVector arguments; @@ -282,22 +209,16 @@ QVector DesktopEntry::parseExecString(const QString& execString) { currentArgument += '\\'; escape = 0; } - } else if (escape == 2) { - currentArgument += c; - escape = 0; } else if (escape != 0) { - switch (c.unicode()) { - case 's': currentArgument += u' '; break; - case 'n': currentArgument += u'\n'; break; - case 't': currentArgument += u'\t'; break; - case 'r': currentArgument += u'\r'; break; - case '\\': currentArgument += u'\\'; break; - default: + if (escape != 2) { + // Technically this is an illegal state, but the spec has a terrible double escape + // rule in strings for no discernable reason. Assuming someone might understandably + // misunderstand it, treat it as a normal escape and log it. qCWarning(logDesktopEntry).noquote() << "Illegal escape sequence in desktop entry exec string:" << execString; - currentArgument += c; - break; } + + currentArgument += c; escape = 0; } else if (c == u'"' || c == u'\'') { parsingString = false; @@ -343,44 +264,59 @@ void DesktopEntry::doExec(const QList& execString, const QString& worki } void DesktopAction::execute() const { - DesktopEntry::doExec(this->bCommand.value(), this->entry->bWorkingDirectory.value()); + DesktopEntry::doExec(this->mCommand, this->entry->mWorkingDirectory); } -DesktopEntryScanner::DesktopEntryScanner(DesktopEntryManager* manager): manager(manager) { - this->setAutoDelete(true); +DesktopEntryManager::DesktopEntryManager() { + this->scanDesktopEntries(); + this->populateApplications(); } -void DesktopEntryScanner::run() { - const auto& desktopPaths = DesktopEntryManager::desktopPaths(); - auto scanResults = QList(); +void DesktopEntryManager::scanDesktopEntries() { + QList dataPaths; - for (const auto& path: desktopPaths | std::views::reverse) { - auto file = QFileInfo(path); - if (!file.isDir()) continue; - - this->scanDirectory(QDir(path), QString(), scanResults); + if (qEnvironmentVariableIsSet("XDG_DATA_HOME")) { + dataPaths.push_back(qEnvironmentVariable("XDG_DATA_HOME")); + } else if (qEnvironmentVariableIsSet("HOME")) { + dataPaths.push_back(qEnvironmentVariable("HOME") + "/.local/share"); } - QMetaObject::invokeMethod( - this->manager, - "onScanCompleted", - Qt::QueuedConnection, - Q_ARG(QList, scanResults) - ); + if (qEnvironmentVariableIsSet("XDG_DATA_DIRS")) { + auto var = qEnvironmentVariable("XDG_DATA_DIRS"); + dataPaths += var.split(u':', Qt::SkipEmptyParts); + } else { + dataPaths.push_back("/usr/local/share"); + dataPaths.push_back("/usr/share"); + } + + qCDebug(logDesktopEntry) << "Creating desktop entry scanners"; + + for (auto& path: std::ranges::reverse_view(dataPaths)) { + auto p = QDir(path).filePath("applications"); + auto file = QFileInfo(p); + + if (!file.isDir()) { + qCDebug(logDesktopEntry) << "Not scanning path" << p << "as it is not a directory"; + continue; + } + + qCDebug(logDesktopEntry) << "Scanning path" << p; + this->scanPath(p); + } } -void DesktopEntryScanner::scanDirectory( - const QDir& dir, - const QString& idPrefix, - QList& entries -) { - auto dirEntries = dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot); +void DesktopEntryManager::populateApplications() { + for (auto& entry: this->desktopEntries.values()) { + if (!entry->noDisplay()) this->mApplications.insertObject(entry); + } +} - for (auto& entry: dirEntries) { - if (entry.isDir()) { - auto subdirPrefix = idPrefix.isEmpty() ? entry.fileName() : idPrefix + '-' + entry.fileName(); - this->scanDirectory(QDir(entry.absoluteFilePath()), subdirPrefix, entries); - } else if (entry.isFile()) { +void DesktopEntryManager::scanPath(const QDir& dir, const QString& prefix) { + auto entries = dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot); + + for (auto& entry: entries) { + if (entry.isDir()) this->scanPath(entry.absoluteFilePath(), prefix + dir.dirName() + "-"); + else if (entry.isFile()) { auto path = entry.filePath(); if (!path.endsWith(".desktop")) { qCDebug(logDesktopEntry) << "Skipping file" << path << "as it has no .desktop extension"; @@ -393,42 +329,46 @@ void DesktopEntryScanner::scanDirectory( continue; } - auto basename = QFileInfo(entry.fileName()).completeBaseName(); - auto id = idPrefix.isEmpty() ? basename : idPrefix + '-' + basename; - auto content = QString::fromUtf8(file.readAll()); + auto id = prefix + entry.fileName().sliced(0, entry.fileName().length() - 8); + auto lowerId = id.toLower(); - auto data = DesktopEntry::parseText(id, content); - entries.append(std::move(data)); + auto text = QString::fromUtf8(file.readAll()); + auto* dentry = new DesktopEntry(id, this); + dentry->parseEntry(text); + + if (!dentry->isValid()) { + qCDebug(logDesktopEntry) << "Skipping desktop entry" << path; + delete dentry; + continue; + } + + qCDebug(logDesktopEntry) << "Found desktop entry" << id << "at" << path; + + auto conflictingId = this->desktopEntries.contains(id); + + if (conflictingId) { + qCDebug(logDesktopEntry) << "Replacing old entry for" << id; + delete this->desktopEntries.value(id); + this->desktopEntries.remove(id); + this->lowercaseDesktopEntries.remove(lowerId); + } + + this->desktopEntries.insert(id, dentry); + + if (this->lowercaseDesktopEntries.contains(lowerId)) { + qCInfo(logDesktopEntry).nospace() + << "Multiple desktop entries have the same lowercased id " << lowerId + << ". This can cause ambiguity when byId requests are not made with the correct case " + "already."; + + this->lowercaseDesktopEntries.remove(lowerId); + } + + this->lowercaseDesktopEntries.insert(lowerId, dentry); } } } -DesktopEntryManager::DesktopEntryManager(): monitor(new DesktopEntryMonitor(this)) { - QObject::connect( - this->monitor, - &DesktopEntryMonitor::desktopEntriesChanged, - this, - &DesktopEntryManager::handleFileChanges - ); - - DesktopEntryScanner(this).run(); -} - -void DesktopEntryManager::scanDesktopEntries() { - qCDebug(logDesktopEntry) << "Starting desktop entry scan"; - - if (this->scanInProgress) { - qCDebug(logDesktopEntry) << "Scan already in progress, queuing another scan"; - this->scanQueued = true; - return; - } - - this->scanInProgress = true; - this->scanQueued = false; - auto* scanner = new DesktopEntryScanner(this); - QThreadPool::globalInstance()->start(scanner); -} - DesktopEntryManager* DesktopEntryManager::instance() { static auto* instance = new DesktopEntryManager(); // NOLINT return instance; @@ -444,167 +384,14 @@ DesktopEntry* DesktopEntryManager::byId(const QString& id) { } } -DesktopEntry* DesktopEntryManager::heuristicLookup(const QString& name) { - if (auto* entry = this->byId(name)) return entry; - - auto list = this->desktopEntries.values(); - - auto iter = std::ranges::find_if(list, [&](DesktopEntry* entry) { - return name == entry->bStartupClass.value(); - }); - - if (iter != list.end()) return *iter; - - iter = std::ranges::find_if(list, [&](DesktopEntry* entry) { - return name.toLower() == entry->bStartupClass.value().toLower(); - }); - - if (iter != list.end()) return *iter; - return nullptr; -} - ObjectModel* DesktopEntryManager::applications() { return &this->mApplications; } -void DesktopEntryManager::handleFileChanges() { - qCDebug(logDesktopEntry) << "Directory change detected, performing full rescan"; - - if (this->scanInProgress) { - qCDebug(logDesktopEntry) << "Scan already in progress, queuing another scan"; - this->scanQueued = true; - return; - } - - this->scanInProgress = true; - this->scanQueued = false; - auto* scanner = new DesktopEntryScanner(this); - QThreadPool::globalInstance()->start(scanner); -} - -const QStringList& DesktopEntryManager::desktopPaths() { - static const auto paths = []() { - auto dataPaths = QStringList(); - - auto dataHome = qEnvironmentVariable("XDG_DATA_HOME"); - if (dataHome.isEmpty() && qEnvironmentVariableIsSet("HOME")) - dataHome = qEnvironmentVariable("HOME") + "/.local/share"; - if (!dataHome.isEmpty()) dataPaths.append(dataHome + "/applications"); - - auto dataDirs = qEnvironmentVariable("XDG_DATA_DIRS"); - if (dataDirs.isEmpty()) dataDirs = "/usr/local/share:/usr/share"; - - for (const auto& dir: dataDirs.split(':', Qt::SkipEmptyParts)) { - dataPaths.append(dir + "/applications"); - } - - return dataPaths; - }(); - - return paths; -} - -void DesktopEntryManager::onScanCompleted(const QList& scanResults) { - auto guard = qScopeGuard([this] { - this->scanInProgress = false; - if (this->scanQueued) { - this->scanQueued = false; - this->scanDesktopEntries(); - } - }); - - auto oldEntries = this->desktopEntries; - auto newEntries = QHash(); - auto newLowercaseEntries = QHash(); - - for (const auto& data: scanResults) { - auto lowerId = data.id.toLower(); - - if (data.hidden) { - if (auto* victim = newEntries.take(data.id)) victim->deleteLater(); - newLowercaseEntries.remove(lowerId); - - if (auto it = oldEntries.find(data.id); it != oldEntries.end()) { - it.value()->deleteLater(); - oldEntries.erase(it); - } - - qCDebug(logDesktopEntry) << "Masking hidden desktop entry" << data.id; - continue; - } - - DesktopEntry* dentry = nullptr; - - if (auto it = oldEntries.find(data.id); it != oldEntries.end()) { - dentry = it.value(); - oldEntries.erase(it); - dentry->updateState(data); - } else { - dentry = new DesktopEntry(data.id, this); - dentry->updateState(data); - } - - if (!dentry->isValid()) { - qCDebug(logDesktopEntry) << "Skipping desktop entry" << data.id; - if (!oldEntries.contains(data.id)) { - dentry->deleteLater(); - } - continue; - } - - qCDebug(logDesktopEntry) << "Found desktop entry" << data.id; - - auto conflictingId = newEntries.contains(data.id); - - if (conflictingId) { - qCDebug(logDesktopEntry) << "Replacing old entry for" << data.id; - if (auto* victim = newEntries.take(data.id)) victim->deleteLater(); - newLowercaseEntries.remove(lowerId); - } - - newEntries.insert(data.id, dentry); - - if (newLowercaseEntries.contains(lowerId)) { - qCInfo(logDesktopEntry).nospace() - << "Multiple desktop entries have the same lowercased id " << lowerId - << ". This can cause ambiguity when byId requests are not made with the correct case " - "already."; - - newLowercaseEntries.remove(lowerId); - } - - newLowercaseEntries.insert(lowerId, dentry); - } - - this->desktopEntries = newEntries; - this->lowercaseDesktopEntries = newLowercaseEntries; - - auto newApplications = QVector(); - for (auto* entry: this->desktopEntries.values()) - if (!entry->bNoDisplay) newApplications.append(entry); - - this->mApplications.diffUpdate(newApplications); - - emit this->applicationsChanged(); - - for (auto* e: oldEntries) e->deleteLater(); -} - -DesktopEntries::DesktopEntries() { - QObject::connect( - DesktopEntryManager::instance(), - &DesktopEntryManager::applicationsChanged, - this, - &DesktopEntries::applicationsChanged - ); -} +DesktopEntries::DesktopEntries() { DesktopEntryManager::instance(); } DesktopEntry* DesktopEntries::byId(const QString& id) { return DesktopEntryManager::instance()->byId(id); } -DesktopEntry* DesktopEntries::heuristicLookup(const QString& name) { - return DesktopEntryManager::instance()->heuristicLookup(name); -} - ObjectModel* DesktopEntries::applications() { return DesktopEntryManager::instance()->applications(); } diff --git a/src/core/desktopentry.hpp b/src/core/desktopentry.hpp index 0d1eff2..ee8f511 100644 --- a/src/core/desktopentry.hpp +++ b/src/core/desktopentry.hpp @@ -6,68 +6,32 @@ #include #include #include -#include #include -#include #include -#include "desktopentrymonitor.hpp" #include "doc.hpp" #include "model.hpp" class DesktopAction; -class DesktopEntryMonitor; - -struct DesktopActionData { - QString id; - QString name; - QString icon; - QString execString; - QVector command; - QHash entries; -}; - -struct ParsedDesktopEntryData { - QString id; - QString name; - QString genericName; - QString startupClass; - bool noDisplay = false; - bool hidden = false; - QString comment; - QString icon; - QString execString; - QVector command; - QString workingDirectory; - bool terminal = false; - QVector categories; - QVector keywords; - QHash entries; - QVector actions; -}; /// A desktop entry. See @@DesktopEntries for details. class DesktopEntry: public QObject { Q_OBJECT; Q_PROPERTY(QString id MEMBER mId CONSTANT); /// Name of the specific application, such as "Firefox". - // clang-format off - Q_PROPERTY(QString name READ default WRITE default NOTIFY nameChanged BINDABLE bindableName); + Q_PROPERTY(QString name MEMBER mName CONSTANT); /// Short description of the application, such as "Web Browser". May be empty. - Q_PROPERTY(QString genericName READ default WRITE default NOTIFY genericNameChanged BINDABLE bindableGenericName); - /// Initial class or app id the app intends to use. May be useful for matching running apps - /// to desktop entries. - Q_PROPERTY(QString startupClass READ default WRITE default NOTIFY startupClassChanged BINDABLE bindableStartupClass); + Q_PROPERTY(QString genericName MEMBER mGenericName CONSTANT); /// If true, this application should not be displayed in menus and launchers. - Q_PROPERTY(bool noDisplay READ default WRITE default NOTIFY noDisplayChanged BINDABLE bindableNoDisplay); + Q_PROPERTY(bool noDisplay MEMBER mNoDisplay CONSTANT); /// Long description of the application, such as "View websites on the internet". May be empty. - Q_PROPERTY(QString comment READ default WRITE default NOTIFY commentChanged BINDABLE bindableComment); + Q_PROPERTY(QString comment MEMBER mComment CONSTANT); /// Name of the icon associated with this application. May be empty. - Q_PROPERTY(QString icon READ default WRITE default NOTIFY iconChanged BINDABLE bindableIcon); + Q_PROPERTY(QString icon MEMBER mIcon CONSTANT); /// The raw `Exec` string from the desktop entry. /// /// > [!WARNING] This cannot be reliably run as a command. See @@command for one you can run. - Q_PROPERTY(QString execString READ default WRITE default NOTIFY execStringChanged BINDABLE bindableExecString); + Q_PROPERTY(QString execString MEMBER mExecString CONSTANT); /// The parsed `Exec` command in the desktop entry. /// /// The entry can be run with @@execute(), or by using this command in @@ -76,14 +40,13 @@ class DesktopEntry: public QObject { /// the invoked process. See @@execute() for details. /// /// > [!NOTE] The provided command does not invoke a terminal even if @@runInTerminal is true. - Q_PROPERTY(QVector command READ default WRITE default NOTIFY commandChanged BINDABLE bindableCommand); + Q_PROPERTY(QVector command MEMBER mCommand CONSTANT); /// The working directory to execute from. - Q_PROPERTY(QString workingDirectory READ default WRITE default NOTIFY workingDirectoryChanged BINDABLE bindableWorkingDirectory); + Q_PROPERTY(QString workingDirectory MEMBER mWorkingDirectory CONSTANT); /// If the application should run in a terminal. - Q_PROPERTY(bool runInTerminal READ default WRITE default NOTIFY runInTerminalChanged BINDABLE bindableRunInTerminal); - Q_PROPERTY(QVector categories READ default WRITE default NOTIFY categoriesChanged BINDABLE bindableCategories); - Q_PROPERTY(QVector keywords READ default WRITE default NOTIFY keywordsChanged BINDABLE bindableKeywords); - // clang-format on + Q_PROPERTY(bool runInTerminal MEMBER mTerminal CONSTANT); + Q_PROPERTY(QVector categories MEMBER mCategories CONSTANT); + Q_PROPERTY(QVector keywords MEMBER mKeywords CONSTANT); Q_PROPERTY(QVector actions READ actions CONSTANT); QML_ELEMENT; QML_UNCREATABLE("DesktopEntry instances must be retrieved from DesktopEntries"); @@ -91,8 +54,7 @@ class DesktopEntry: public QObject { public: explicit DesktopEntry(QString id, QObject* parent): QObject(parent), mId(std::move(id)) {} - static ParsedDesktopEntryData parseText(const QString& id, const QString& text); - void updateState(const ParsedDesktopEntryData& newState); + void parseEntry(const QString& text); /// Run the application. Currently ignores @@runInTerminal and field codes. /// @@ -108,66 +70,30 @@ public: Q_INVOKABLE void execute() const; [[nodiscard]] bool isValid() const; + [[nodiscard]] bool noDisplay() const; [[nodiscard]] QVector actions() const; - [[nodiscard]] QBindable bindableName() const { return &this->bName; } - [[nodiscard]] QBindable bindableGenericName() const { return &this->bGenericName; } - [[nodiscard]] QBindable bindableStartupClass() const { return &this->bStartupClass; } - [[nodiscard]] QBindable bindableNoDisplay() const { return &this->bNoDisplay; } - [[nodiscard]] QBindable bindableComment() const { return &this->bComment; } - [[nodiscard]] QBindable bindableIcon() const { return &this->bIcon; } - [[nodiscard]] QBindable bindableExecString() const { return &this->bExecString; } - [[nodiscard]] QBindable> bindableCommand() const { return &this->bCommand; } - [[nodiscard]] QBindable bindableWorkingDirectory() const { - return &this->bWorkingDirectory; - } - [[nodiscard]] QBindable bindableRunInTerminal() const { return &this->bRunInTerminal; } - [[nodiscard]] QBindable> bindableCategories() const { - return &this->bCategories; - } - [[nodiscard]] QBindable> bindableKeywords() const { return &this->bKeywords; } - // currently ignores all field codes. static QVector parseExecString(const QString& execString); static void doExec(const QList& execString, const QString& workingDirectory); -signals: - void nameChanged(); - void genericNameChanged(); - void startupClassChanged(); - void noDisplayChanged(); - void commentChanged(); - void iconChanged(); - void execStringChanged(); - void commandChanged(); - void workingDirectoryChanged(); - void runInTerminalChanged(); - void categoriesChanged(); - void keywordsChanged(); - public: QString mId; - - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bName, &DesktopEntry::nameChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bGenericName, &DesktopEntry::genericNameChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bStartupClass, &DesktopEntry::startupClassChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, bool, bNoDisplay, &DesktopEntry::noDisplayChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bComment, &DesktopEntry::commentChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bIcon, &DesktopEntry::iconChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bExecString, &DesktopEntry::execStringChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QVector, bCommand, &DesktopEntry::commandChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QString, bWorkingDirectory, &DesktopEntry::workingDirectoryChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, bool, bRunInTerminal, &DesktopEntry::runInTerminalChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QVector, bCategories, &DesktopEntry::categoriesChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopEntry, QVector, bKeywords, &DesktopEntry::keywordsChanged); - // clang-format on + QString mName; + QString mGenericName; + bool mNoDisplay = false; + QString mComment; + QString mIcon; + QString mExecString; + QVector mCommand; + QString mWorkingDirectory; + bool mTerminal = false; + QVector mCategories; + QVector mKeywords; private: - void updateActions(const QVector& newActions); - - ParsedDesktopEntryData state; - QVector mActions; + QHash mEntries; + QHash mActions; friend class DesktopAction; }; @@ -176,13 +102,12 @@ private: class DesktopAction: public QObject { Q_OBJECT; Q_PROPERTY(QString id MEMBER mId CONSTANT); - // clang-format off - Q_PROPERTY(QString name READ default WRITE default NOTIFY nameChanged BINDABLE bindableName); - Q_PROPERTY(QString icon READ default WRITE default NOTIFY iconChanged BINDABLE bindableIcon); + Q_PROPERTY(QString name MEMBER mName CONSTANT); + Q_PROPERTY(QString icon MEMBER mIcon CONSTANT); /// The raw `Exec` string from the action. /// /// > [!WARNING] This cannot be reliably run as a command. See @@command for one you can run. - Q_PROPERTY(QString execString READ default WRITE default NOTIFY execStringChanged BINDABLE bindableExecString); + Q_PROPERTY(QString execString MEMBER mExecString CONSTANT); /// The parsed `Exec` command in the action. /// /// The entry can be run with @@execute(), or by using this command in @@ -191,8 +116,7 @@ class DesktopAction: public QObject { /// the invoked process. /// /// > [!NOTE] The provided command does not invoke a terminal even if @@runInTerminal is true. - Q_PROPERTY(QVector command READ default WRITE default NOTIFY commandChanged BINDABLE bindableCommand); - // clang-format on + Q_PROPERTY(QVector command MEMBER mCommand CONSTANT); QML_ELEMENT; QML_UNCREATABLE("DesktopAction instances must be retrieved from a DesktopEntry"); @@ -208,47 +132,18 @@ public: /// and @@DesktopEntry.workingDirectory. Q_INVOKABLE void execute() const; - [[nodiscard]] QBindable bindableName() const { return &this->bName; } - [[nodiscard]] QBindable bindableIcon() const { return &this->bIcon; } - [[nodiscard]] QBindable bindableExecString() const { return &this->bExecString; } - [[nodiscard]] QBindable> bindableCommand() const { return &this->bCommand; } - -signals: - void nameChanged(); - void iconChanged(); - void execStringChanged(); - void commandChanged(); - private: DesktopEntry* entry; QString mId; + QString mName; + QString mIcon; + QString mExecString; + QVector mCommand; QHash mEntries; - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(DesktopAction, QString, bName, &DesktopAction::nameChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopAction, QString, bIcon, &DesktopAction::iconChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopAction, QString, bExecString, &DesktopAction::execStringChanged); - Q_OBJECT_BINDABLE_PROPERTY(DesktopAction, QVector, bCommand, &DesktopAction::commandChanged); - // clang-format on - friend class DesktopEntry; }; -class DesktopEntryManager; - -class DesktopEntryScanner: public QRunnable { -public: - explicit DesktopEntryScanner(DesktopEntryManager* manager); - - void run() override; - // clang-format off - void scanDirectory(const QDir& dir, const QString& idPrefix, QList& entries); - // clang-format on - -private: - DesktopEntryManager* manager; -}; - class DesktopEntryManager: public QObject { Q_OBJECT; @@ -256,32 +151,20 @@ public: void scanDesktopEntries(); [[nodiscard]] DesktopEntry* byId(const QString& id); - [[nodiscard]] DesktopEntry* heuristicLookup(const QString& name); [[nodiscard]] ObjectModel* applications(); static DesktopEntryManager* instance(); - static const QStringList& desktopPaths(); - -signals: - void applicationsChanged(); - -private slots: - void handleFileChanges(); - void onScanCompleted(const QList& scanResults); - private: explicit DesktopEntryManager(); + void populateApplications(); + void scanPath(const QDir& dir, const QString& prefix = QString()); + QHash desktopEntries; QHash lowercaseDesktopEntries; ObjectModel mApplications {this}; - DesktopEntryMonitor* monitor = nullptr; - bool scanInProgress = false; - bool scanQueued = false; - - friend class DesktopEntryScanner; }; ///! Desktop entry index. @@ -303,17 +186,7 @@ public: explicit DesktopEntries(); /// Look up a desktop entry by name. Includes NoDisplay entries. May return null. - /// - /// While this function requires an exact match, @@heuristicLookup() will correctly - /// find an entry more often and is generally more useful. Q_INVOKABLE [[nodiscard]] static DesktopEntry* byId(const QString& id); - /// Look up a desktop entry by name using heuristics. Unlike @@byId(), - /// if no exact matches are found this function will try to guess - potentially incorrectly. - /// May return null. - Q_INVOKABLE [[nodiscard]] static DesktopEntry* heuristicLookup(const QString& name); [[nodiscard]] static ObjectModel* applications(); - -signals: - void applicationsChanged(); }; diff --git a/src/core/desktopentrymonitor.cpp b/src/core/desktopentrymonitor.cpp deleted file mode 100644 index bed6ef1..0000000 --- a/src/core/desktopentrymonitor.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include "desktopentrymonitor.hpp" - -#include -#include -#include -#include -#include -#include - -#include "desktopentry.hpp" - -namespace { -void addPathAndParents(QFileSystemWatcher& watcher, const QString& path) { - watcher.addPath(path); - - auto p = QFileInfo(path).absolutePath(); - while (!p.isEmpty()) { - watcher.addPath(p); - const auto parent = QFileInfo(p).dir().absolutePath(); - if (parent == p) break; - p = parent; - } -} -} // namespace - -DesktopEntryMonitor::DesktopEntryMonitor(QObject* parent): QObject(parent) { - this->debounceTimer.setSingleShot(true); - this->debounceTimer.setInterval(100); - - QObject::connect( - &this->watcher, - &QFileSystemWatcher::directoryChanged, - this, - &DesktopEntryMonitor::onDirectoryChanged - ); - QObject::connect( - &this->debounceTimer, - &QTimer::timeout, - this, - &DesktopEntryMonitor::processChanges - ); - - this->startMonitoring(); -} - -void DesktopEntryMonitor::startMonitoring() { - for (const auto& path: DesktopEntryManager::desktopPaths()) { - if (!QDir(path).exists()) continue; - addPathAndParents(this->watcher, path); - this->scanAndWatch(path); - } -} - -void DesktopEntryMonitor::scanAndWatch(const QString& dirPath) { - auto dir = QDir(dirPath); - if (!dir.exists()) return; - - this->watcher.addPath(dirPath); - - auto subdirs = dir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks); - for (const auto& subdir: subdirs) this->watcher.addPath(subdir.absoluteFilePath()); -} - -void DesktopEntryMonitor::onDirectoryChanged(const QString& /*path*/) { - this->debounceTimer.start(); -} - -void DesktopEntryMonitor::processChanges() { emit this->desktopEntriesChanged(); } \ No newline at end of file diff --git a/src/core/desktopentrymonitor.hpp b/src/core/desktopentrymonitor.hpp deleted file mode 100644 index eb3251d..0000000 --- a/src/core/desktopentrymonitor.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -class DesktopEntryMonitor: public QObject { - Q_OBJECT - -public: - explicit DesktopEntryMonitor(QObject* parent = nullptr); - ~DesktopEntryMonitor() override = default; - DesktopEntryMonitor(const DesktopEntryMonitor&) = delete; - DesktopEntryMonitor& operator=(const DesktopEntryMonitor&) = delete; - DesktopEntryMonitor(DesktopEntryMonitor&&) = delete; - DesktopEntryMonitor& operator=(DesktopEntryMonitor&&) = delete; - -signals: - void desktopEntriesChanged(); - -private slots: - void onDirectoryChanged(const QString& path); - void processChanges(); - -private: - void startMonitoring(); - void scanAndWatch(const QString& dirPath); - - QFileSystemWatcher watcher; - QTimer debounceTimer; -}; diff --git a/src/core/generation.cpp b/src/core/generation.cpp index c68af71..90a2939 100644 --- a/src/core/generation.cpp +++ b/src/core/generation.cpp @@ -11,12 +11,12 @@ #include #include #include +#include #include #include #include #include #include -#include #include #include "iconimageprovider.hpp" @@ -49,8 +49,7 @@ EngineGeneration::EngineGeneration(const QDir& rootPath, QmlScanner scanner) this->engine->addImportPath("qs:@/"); this->engine->setNetworkAccessManagerFactory(&this->interceptNetFactory); - this->incubationController.initLoop(); - this->engine->setIncubationController(&this->incubationController); + this->engine->setIncubationController(&this->delayedIncubationController); this->engine->addImageProvider("icon", new IconImageProvider()); this->engine->addImageProvider("qsimage", new QsImageProvider()); @@ -135,7 +134,7 @@ void EngineGeneration::onReload(EngineGeneration* old) { // new generation acquires it then incubators will hang intermittently qCDebug(logIncubator) << "Locking incubation controllers of old generation" << old; old->incubationControllersLocked = true; - old->updateIncubationMode(); + old->assignIncubationController(); } QObject::connect(this->engine, &QQmlEngine::quit, this, &EngineGeneration::quit); @@ -162,9 +161,8 @@ void EngineGeneration::postReload() { if (this->engine == nullptr || this->root == nullptr) return; QsEnginePlugin::runOnReload(); - - emit this->firePostReload(); - QObject::disconnect(this, &EngineGeneration::firePostReload, nullptr, nullptr); + PostReloadHook::postReloadTree(this->root); + this->singletonRegistry.onPostReload(); } void EngineGeneration::setWatchingFiles(bool watching) { @@ -224,11 +222,6 @@ void EngineGeneration::onFileChanged(const QString& name) { if (!this->watcher->files().contains(name)) { this->deletedWatchedFiles.push_back(name); } else { - // some editors (e.g vscode) perform file saving in two steps: truncate + write - // ignore the first event (truncate) with size 0 to prevent incorrect live reloading - auto fileInfo = QFileInfo(name); - if (fileInfo.isFile() && fileInfo.size() == 0) return; - emit this->filesChanged(); } } @@ -243,6 +236,90 @@ void EngineGeneration::onDirectoryChanged() { } } +void EngineGeneration::registerIncubationController(QQmlIncubationController* controller) { + // We only want controllers that we can swap out if destroyed. + // This happens if the window owning the active controller dies. + auto* obj = dynamic_cast(controller); + if (!obj) { + qCWarning(logIncubator) << "Could not register incubation controller as it is not a QObject" + << controller; + + return; + } + + QObject::connect( + obj, + &QObject::destroyed, + this, + &EngineGeneration::incubationControllerDestroyed, + Qt::UniqueConnection + ); + + this->incubationControllers.push_back(obj); + qCDebug(logIncubator) << "Registered incubation controller" << obj << "to generation" << this; + + // This function can run during destruction. + if (this->engine == nullptr) return; + + if (this->engine->incubationController() == &this->delayedIncubationController) { + this->assignIncubationController(); + } +} + +// Multiple controllers may be destroyed at once. Dynamic casts must be performed before working +// with any controllers. The QQmlIncubationController destructor will already have run by the +// point QObject::destroyed is called, so we can't cast to that. +void EngineGeneration::deregisterIncubationController(QQmlIncubationController* controller) { + auto* obj = dynamic_cast(controller); + if (!obj) { + qCCritical(logIncubator) << "Deregistering incubation controller which is not a QObject, " + "however only QObject controllers should be registered."; + } + + QObject::disconnect(obj, nullptr, this, nullptr); + + if (this->incubationControllers.removeOne(obj)) { + qCDebug(logIncubator) << "Deregistered incubation controller" << obj << "from" << this; + } else { + qCCritical(logIncubator) << "Failed to deregister incubation controller" << obj << "from" + << this << "as it was not registered to begin with"; + qCCritical(logIncubator) << "Current registered incuabation controllers" + << this->incubationControllers; + } + + // This function can run during destruction. + if (this->engine == nullptr) return; + + if (this->engine->incubationController() == controller) { + qCDebug(logIncubator + ) << "Destroyed incubation controller was currently active, reassigning from pool"; + this->assignIncubationController(); + } +} + +void EngineGeneration::incubationControllerDestroyed() { + auto* sender = this->sender(); + + if (this->incubationControllers.removeAll(sender) != 0) { + qCDebug(logIncubator) << "Destroyed incubation controller" << sender << "deregistered from" + << this; + } else { + qCCritical(logIncubator) << "Destroyed incubation controller" << sender + << "was not registered, but its destruction was observed by" << this; + + return; + } + + // This function can run during destruction. + if (this->engine == nullptr) return; + + if (dynamic_cast(this->engine->incubationController()) == sender) { + qCDebug(logIncubator + ) << "Destroyed incubation controller was currently active, reassigning from pool"; + this->assignIncubationController(); + } +} + void EngineGeneration::onEngineWarnings(const QList& warnings) { for (const auto& error: warnings) { const auto& url = error.url(); @@ -284,23 +361,20 @@ void EngineGeneration::exit(int code) { this->destroy(); } -void EngineGeneration::trackWindowIncubationController(QQuickWindow* window) { - if (this->trackedWindows.contains(window)) return; +void EngineGeneration::assignIncubationController() { + QQmlIncubationController* controller = nullptr; - QObject::connect(window, &QObject::destroyed, this, &EngineGeneration::onTrackedWindowDestroyed); - this->trackedWindows.append(window); - this->updateIncubationMode(); -} + if (this->incubationControllersLocked || this->incubationControllers.isEmpty()) { + controller = &this->delayedIncubationController; + } else { + controller = dynamic_cast(this->incubationControllers.first()); + } -void EngineGeneration::onTrackedWindowDestroyed(QObject* object) { - this->trackedWindows.removeAll(static_cast(object)); // NOLINT - this->updateIncubationMode(); -} + qCDebug(logIncubator) << "Assigning incubation controller" << controller << "to generation" + << this + << "fallback:" << (controller == &this->delayedIncubationController); -void EngineGeneration::updateIncubationMode() { - // If we're in a situation with only hidden but tracked windows this might be wrong, - // but it seems to at least work. - this->incubationController.setIncubationMode(!this->trackedWindows.empty()); + this->engine->setIncubationController(controller); } EngineGeneration* EngineGeneration::currentGeneration() { diff --git a/src/core/generation.hpp b/src/core/generation.hpp index 4543408..5d3c5c6 100644 --- a/src/core/generation.hpp +++ b/src/core/generation.hpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include "incubator.hpp" @@ -41,7 +40,8 @@ public: void setWatchingFiles(bool watching); bool setExtraWatchedFiles(const QVector& files); - void trackWindowIncubationController(QQuickWindow* window); + void registerIncubationController(QQmlIncubationController* controller); + void deregisterIncubationController(QQmlIncubationController* controller); // takes ownership void registerExtension(const void* key, EngineGenerationExt* extension); @@ -65,7 +65,7 @@ public: QFileSystemWatcher* watcher = nullptr; QVector deletedWatchedFiles; QVector extraWatchedFiles; - QsIncubationController incubationController; + DelayedQmlIncubationController delayedIncubationController; bool reloadComplete = false; QuickshellGlobal* qsgInstance = nullptr; @@ -75,7 +75,6 @@ public: signals: void filesChanged(); void reloadFinished(); - void firePostReload(); public slots: void quit(); @@ -84,13 +83,13 @@ public slots: private slots: void onFileChanged(const QString& name); void onDirectoryChanged(); - void onTrackedWindowDestroyed(QObject* object); + void incubationControllerDestroyed(); static void onEngineWarnings(const QList& warnings); private: void postReload(); - void updateIncubationMode(); - QVector trackedWindows; + void assignIncubationController(); + QVector incubationControllers; bool incubationControllersLocked = false; QHash extensions; diff --git a/src/core/iconimageprovider.cpp b/src/core/iconimageprovider.cpp index 1dbe3e7..43e00fd 100644 --- a/src/core/iconimageprovider.cpp +++ b/src/core/iconimageprovider.cpp @@ -19,7 +19,8 @@ IconImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& re if (splitIdx != -1) { iconName = id.sliced(0, splitIdx); path = id.sliced(splitIdx + 6); - path = QString("/%1/%2").arg(path, iconName.sliced(iconName.lastIndexOf('/') + 1)); + qWarning() << "Searching custom icon paths is not yet supported. Icon path will be ignored for" + << id; } else { splitIdx = id.indexOf("?fallback="); if (splitIdx != -1) { @@ -31,8 +32,7 @@ IconImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& re } auto icon = QIcon::fromTheme(iconName); - if (icon.isNull() && !fallbackName.isEmpty()) icon = QIcon::fromTheme(fallbackName); - if (icon.isNull() && !path.isEmpty()) icon = QPixmap(path); + if (icon.isNull()) icon = QIcon::fromTheme(fallbackName); auto targetSize = requestedSize.isValid() ? requestedSize : QSize(100, 100); if (targetSize.width() == 0 || targetSize.height() == 0) targetSize = QSize(2, 2); diff --git a/src/core/iconprovider.cpp b/src/core/iconprovider.cpp index 383f7e1..99b423e 100644 --- a/src/core/iconprovider.cpp +++ b/src/core/iconprovider.cpp @@ -22,8 +22,8 @@ class PixmapCacheIconEngine: public QIconEngine { QIcon::Mode /*unused*/, QIcon::State /*unused*/ ) override { - qFatal() - << "Unexpected icon paint request bypassed pixmap method. Please report this as a bug."; + qFatal( + ) << "Unexpected icon paint request bypassed pixmap method. Please report this as a bug."; } QPixmap pixmap(const QSize& size, QIcon::Mode /*unused*/, QIcon::State /*unused*/) override { diff --git a/src/core/incubator.cpp b/src/core/incubator.cpp index f031b11..c9d149a 100644 --- a/src/core/incubator.cpp +++ b/src/core/incubator.cpp @@ -1,16 +1,7 @@ #include "incubator.hpp" -#include -#include -#include #include -#include -#include -#include -#include -#include #include -#include #include #include "logcat.hpp" @@ -24,112 +15,3 @@ void QsQmlIncubator::statusChanged(QQmlIncubator::Status status) { default: break; } } - -void QsIncubationController::initLoop() { - auto* app = static_cast(QGuiApplication::instance()); // NOLINT - this->renderLoop = QSGRenderLoop::instance(); - - QObject::connect( - app, - &QGuiApplication::screenAdded, - this, - &QsIncubationController::updateIncubationTime - ); - - QObject::connect( - app, - &QGuiApplication::screenRemoved, - this, - &QsIncubationController::updateIncubationTime - ); - - this->updateIncubationTime(); - - QObject::connect( - this->renderLoop, - &QSGRenderLoop::timeToIncubate, - this, - &QsIncubationController::incubate - ); - - QAnimationDriver* animationDriver = this->renderLoop->animationDriver(); - if (animationDriver) { - QObject::connect( - animationDriver, - &QAnimationDriver::stopped, - this, - &QsIncubationController::animationStopped - ); - } else { - qCInfo(logIncubator) << "Render loop does not have animation driver, animationStopped cannot " - "be used to trigger incubation."; - } -} - -void QsIncubationController::setIncubationMode(bool render) { - if (render == this->followRenderloop) return; - this->followRenderloop = render; - - if (render) { - qCDebug(logIncubator) << "Incubation mode changed: render loop driven"; - } else { - qCDebug(logIncubator) << "Incubation mode changed: event loop driven"; - } - - if (!render && this->incubatingObjectCount()) this->incubateLater(); -} - -void QsIncubationController::timerEvent(QTimerEvent* /*event*/) { - this->killTimer(this->timerId); - this->timerId = 0; - this->incubate(); -} - -void QsIncubationController::incubateLater() { - if (this->followRenderloop) { - if (this->timerId != 0) { - this->killTimer(this->timerId); - this->timerId = 0; - } - - // Incubate again at the end of the event processing queue - QMetaObject::invokeMethod(this, &QsIncubationController::incubate, Qt::QueuedConnection); - } else if (this->timerId == 0) { - // Wait for a while before processing the next batch. Using a - // timer to avoid starvation of system events. - this->timerId = this->startTimer(this->incubationTime); - } -} - -void QsIncubationController::incubate() { - if ((!this->followRenderloop || this->renderLoop) && this->incubatingObjectCount()) { - if (!this->followRenderloop) { - this->incubateFor(10); - if (this->incubatingObjectCount()) this->incubateLater(); - } else if (this->renderLoop->interleaveIncubation()) { - this->incubateFor(this->incubationTime); - } else { - this->incubateFor(this->incubationTime * 2); - if (this->incubatingObjectCount()) this->incubateLater(); - } - } -} - -void QsIncubationController::animationStopped() { this->incubate(); } - -void QsIncubationController::incubatingObjectCountChanged(int count) { - if (count - && (!this->followRenderloop - || (this->renderLoop && !this->renderLoop->interleaveIncubation()))) - { - this->incubateLater(); - } -} - -void QsIncubationController::updateIncubationTime() { - auto* screen = QGuiApplication::primaryScreen(); - if (!screen) return; - - // 1/3 frame on primary screen - this->incubationTime = qMax(1, static_cast(1000 / screen->refreshRate() / 3)); -} diff --git a/src/core/incubator.hpp b/src/core/incubator.hpp index 15dc49a..5ebb9a0 100644 --- a/src/core/incubator.hpp +++ b/src/core/incubator.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include @@ -26,37 +25,7 @@ signals: void failed(); }; -class QSGRenderLoop; - -class QsIncubationController - : public QObject - , public QQmlIncubationController { - Q_OBJECT - -public: - void initLoop(); - void setIncubationMode(bool render); - void incubateLater(); - -protected: - void timerEvent(QTimerEvent* event) override; - -public slots: - void incubate(); - void animationStopped(); - void updateIncubationTime(); - -protected: - void incubatingObjectCountChanged(int count) override; - -private: -// QPointer did not work with forward declarations prior to 6.7 -#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) - QPointer renderLoop = nullptr; -#else - QSGRenderLoop* renderLoop = nullptr; -#endif - int incubationTime = 0; - int timerId = 0; - bool followRenderloop = false; +class DelayedQmlIncubationController: public QQmlIncubationController { + // Do nothing. + // This ensures lazy loaders don't start blocking before onReload creates windows. }; diff --git a/src/core/instanceinfo.cpp b/src/core/instanceinfo.cpp index 1f71b8a..7f0132b 100644 --- a/src/core/instanceinfo.cpp +++ b/src/core/instanceinfo.cpp @@ -3,14 +3,12 @@ #include QDataStream& operator<<(QDataStream& stream, const InstanceInfo& info) { - stream << info.instanceId << info.configPath << info.shellId << info.launchTime << info.pid - << info.display; + stream << info.instanceId << info.configPath << info.shellId << info.launchTime << info.pid; return stream; } QDataStream& operator>>(QDataStream& stream, InstanceInfo& info) { - stream >> info.instanceId >> info.configPath >> info.shellId >> info.launchTime >> info.pid - >> info.display; + stream >> info.instanceId >> info.configPath >> info.shellId >> info.launchTime >> info.pid; return stream; } diff --git a/src/core/instanceinfo.hpp b/src/core/instanceinfo.hpp index 977e4c2..98ce614 100644 --- a/src/core/instanceinfo.hpp +++ b/src/core/instanceinfo.hpp @@ -11,7 +11,6 @@ struct InstanceInfo { QString shellId; QDateTime launchTime; pid_t pid = -1; - QString display; static InstanceInfo CURRENT; // NOLINT }; @@ -35,8 +34,6 @@ namespace qs::crash { struct CrashInfo { int logFd = -1; - int traceFd = -1; - int infoFd = -1; static CrashInfo INSTANCE; // NOLINT }; diff --git a/src/core/lazyloader.hpp b/src/core/lazyloader.hpp index 56cc964..dbaad4b 100644 --- a/src/core/lazyloader.hpp +++ b/src/core/lazyloader.hpp @@ -82,6 +82,9 @@ /// > Notably, @@Variants does not corrently support asynchronous /// > loading, meaning using it inside a LazyLoader will block similarly to not /// > having a loader to start with. +/// +/// > [!WARNING] LazyLoaders do not start loading before the first window is created, +/// > meaning if you create all windows inside of lazy loaders, none of them will ever load. class LazyLoader: public Reloadable { Q_OBJECT; /// The fully loaded item if the loader is @@loading or @@active, or `null` diff --git a/src/core/logging.cpp b/src/core/logging.cpp index d24225b..7f95e46 100644 --- a/src/core/logging.cpp +++ b/src/core/logging.cpp @@ -27,10 +27,7 @@ #include #include #include -#ifdef __linux__ #include -#include -#endif #include "instanceinfo.hpp" #include "logcat.hpp" @@ -46,57 +43,6 @@ using namespace qt_logging_registry; QS_LOGGING_CATEGORY(logLogging, "quickshell.logging", QtWarningMsg); -namespace { -bool copyFileData(int sourceFd, int destFd, qint64 size) { - auto usize = static_cast(size); - -#ifdef __linux__ - off_t offset = 0; - auto remaining = usize; - - while (remaining > 0) { - auto r = sendfile(destFd, sourceFd, &offset, remaining); - if (r == -1) { - if (errno == EINTR) continue; - return false; - } - if (r == 0) break; - remaining -= static_cast(r); - } - - return true; -#else - std::array buffer = {}; - auto remaining = totalTarget; - - while (remaining > 0) { - auto chunk = std::min(remaining, buffer.size()); - auto r = ::read(sourceFd, buffer.data(), chunk); - if (r == -1) { - if (errno == EINTR) continue; - return false; - } - if (r == 0) break; - - auto readBytes = static_cast(r); - size_t written = 0; - while (written < readBytes) { - auto w = ::write(destFd, buffer.data() + written, readBytes - written); - if (w == -1) { - if (errno == EINTR) continue; - return false; - } - written += static_cast(w); - } - - remaining -= readBytes; - } - - return true; -#endif -} -} // namespace - bool LogMessage::operator==(const LogMessage& other) const { // note: not including time return this->type == other.type && this->category == other.category && this->body == other.body; @@ -367,12 +313,8 @@ void ThreadLogging::init() { if (logMfd != -1) { this->file = new QFile(); - - if (this->file->open(logMfd, QFile::ReadWrite, QFile::AutoCloseHandle)) { - this->fileStream.setDevice(this->file); - } else { - qCCritical(logLogging) << "Failed to open early logging memfd."; - } + this->file->open(logMfd, QFile::ReadWrite, QFile::AutoCloseHandle); + this->fileStream.setDevice(this->file); } if (dlogMfd != -1) { @@ -380,19 +322,14 @@ void ThreadLogging::init() { this->detailedFile = new QFile(); // buffered by WriteBuffer - if (this->detailedFile - ->open(dlogMfd, QFile::ReadWrite | QFile::Unbuffered, QFile::AutoCloseHandle)) - { - this->detailedWriter.setDevice(this->detailedFile); + this->detailedFile->open(dlogMfd, QFile::ReadWrite | QFile::Unbuffered, QFile::AutoCloseHandle); + this->detailedWriter.setDevice(this->detailedFile); - if (!this->detailedWriter.writeHeader()) { - qCCritical(logLogging) << "Could not write header for detailed logs."; - this->detailedWriter.setDevice(nullptr); - delete this->detailedFile; - this->detailedFile = nullptr; - } - } else { - qCCritical(logLogging) << "Failed to open early detailed logging memfd."; + if (!this->detailedWriter.writeHeader()) { + qCCritical(logLogging) << "Could not write header for detailed logs."; + this->detailedWriter.setDevice(nullptr); + delete this->detailedFile; + this->detailedFile = nullptr; } } @@ -415,8 +352,7 @@ void ThreadLogging::initFs() { auto* runDir = QsPaths::instance()->instanceRunDir(); if (!runDir) { - qCCritical( - logLogging + qCCritical(logLogging ) << "Could not start filesystem logging as the runtime directory could not be created."; return; } @@ -427,8 +363,7 @@ void ThreadLogging::initFs() { auto* detailedFile = new QFile(detailedPath); if (!file->open(QFile::ReadWrite | QFile::Truncate)) { - qCCritical( - logLogging + qCCritical(logLogging ) << "Could not start filesystem logger as the log file could not be created:" << path; delete file; @@ -439,14 +374,13 @@ void ThreadLogging::initFs() { // buffered by WriteBuffer if (!detailedFile->open(QFile::ReadWrite | QFile::Truncate | QFile::Unbuffered)) { - qCCritical( - logLogging + qCCritical(logLogging ) << "Could not start detailed filesystem logger as the log file could not be created:" << detailedPath; delete detailedFile; detailedFile = nullptr; } else { - struct flock lock = { + auto lock = flock { .l_type = F_WRLCK, .l_whence = SEEK_SET, .l_start = 0, @@ -468,11 +402,7 @@ void ThreadLogging::initFs() { auto* oldFile = this->file; if (oldFile) { oldFile->seek(0); - - if (!copyFileData(oldFile->handle(), file->handle(), oldFile->size())) { - qCritical(logLogging) << "Failed to copy log from memfd with error code " << errno - << qt_error_string(errno); - } + sendfile(file->handle(), oldFile->handle(), nullptr, oldFile->size()); } this->file = file; @@ -484,10 +414,7 @@ void ThreadLogging::initFs() { auto* oldFile = this->detailedFile; if (oldFile) { oldFile->seek(0); - if (!copyFileData(oldFile->handle(), detailedFile->handle(), oldFile->size())) { - qCritical(logLogging) << "Failed to copy detailed log from memfd with error code " << errno - << qt_error_string(errno); - } + sendfile(detailedFile->handle(), oldFile->handle(), nullptr, oldFile->size()); } crash::CrashInfo::INSTANCE.logFd = detailedFile->handle(); @@ -530,14 +457,10 @@ void ThreadLogging::onMessage(const LogMessage& msg, bool showInSparse) { this->fileStream << Qt::endl; } - if (!this->detailedWriter.write(msg) || (this->detailedFile && !this->detailedFile->flush())) { - this->detailedWriter.setDevice(nullptr); - - if (this->detailedFile) { - this->detailedFile->close(); - this->detailedFile = nullptr; - qCCritical(logLogging) << "Detailed logger failed to write. Ending detailed logs."; - } + if (this->detailedWriter.write(msg)) { + this->detailedFile->flush(); + } else if (this->detailedFile != nullptr) { + qCCritical(logLogging) << "Detailed logger failed to write. Ending detailed logs."; } } @@ -810,11 +733,11 @@ bool EncodedLogReader::readVarInt(quint32* slot) { if (!this->reader.skip(1)) return false; *slot = qFromLittleEndian(n); } else if ((bytes[1] != 0xff || bytes[2] != 0xff) && readLength >= 3) { - auto n = *reinterpret_cast(bytes.data() + 1); // NOLINT + auto n = *reinterpret_cast(bytes.data() + 1); if (!this->reader.skip(3)) return false; *slot = qFromLittleEndian(n); } else if (readLength == 7) { - auto n = *reinterpret_cast(bytes.data() + 3); // NOLINT + auto n = *reinterpret_cast(bytes.data() + 3); if (!this->reader.skip(7)) return false; *slot = qFromLittleEndian(n); } else return false; @@ -950,7 +873,7 @@ bool LogReader::continueReading() { } void LogFollower::FcntlWaitThread::run() { - struct flock lock = { + auto lock = flock { .l_type = F_RDLCK, // won't block other read locks when we take it .l_whence = SEEK_SET, .l_start = 0, diff --git a/src/core/model.cpp b/src/core/model.cpp index 47ef060..165c606 100644 --- a/src/core/model.cpp +++ b/src/core/model.cpp @@ -1,14 +1,81 @@ #include "model.hpp" -#include +#include #include #include +#include +#include +#include +#include +#include + +qint32 UntypedObjectModel::rowCount(const QModelIndex& parent) const { + if (parent != QModelIndex()) return 0; + return static_cast(this->valuesList.length()); +} + +QVariant UntypedObjectModel::data(const QModelIndex& index, qint32 role) const { + if (role != Qt::UserRole) return QVariant(); + return QVariant::fromValue(this->valuesList.at(index.row())); +} QHash UntypedObjectModel::roleNames() const { return {{Qt::UserRole, "modelData"}}; } +void UntypedObjectModel::insertObject(QObject* object, qsizetype index) { + auto iindex = index == -1 ? this->valuesList.length() : index; + emit this->objectInsertedPre(object, iindex); + + auto intIndex = static_cast(iindex); + this->beginInsertRows(QModelIndex(), intIndex, intIndex); + this->valuesList.insert(iindex, object); + this->endInsertRows(); + + emit this->valuesChanged(); + emit this->objectInsertedPost(object, iindex); +} + +void UntypedObjectModel::removeAt(qsizetype index) { + auto* object = this->valuesList.at(index); + emit this->objectRemovedPre(object, index); + + auto intIndex = static_cast(index); + this->beginRemoveRows(QModelIndex(), intIndex, intIndex); + this->valuesList.removeAt(index); + this->endRemoveRows(); + + emit this->valuesChanged(); + emit this->objectRemovedPost(object, index); +} + +bool UntypedObjectModel::removeObject(const QObject* object) { + auto index = this->valuesList.indexOf(object); + if (index == -1) return false; + + this->removeAt(index); + return true; +} + +void UntypedObjectModel::diffUpdate(const QVector& newValues) { + for (qsizetype i = 0; i < this->valuesList.length();) { + if (newValues.contains(this->valuesList.at(i))) i++; + else this->removeAt(i); + } + + qsizetype oi = 0; + for (auto* object: newValues) { + if (this->valuesList.length() == oi || this->valuesList.at(oi) != object) { + this->insertObject(object, oi); + } + + oi++; + } +} + +qsizetype UntypedObjectModel::indexOf(QObject* object) { return this->valuesList.indexOf(object); } + UntypedObjectModel* UntypedObjectModel::emptyInstance() { - static auto* instance = new ObjectModel(nullptr); + static auto* instance = new UntypedObjectModel(nullptr); // NOLINT return instance; } diff --git a/src/core/model.hpp b/src/core/model.hpp index 0e88025..6346c96 100644 --- a/src/core/model.hpp +++ b/src/core/model.hpp @@ -2,7 +2,7 @@ #include -#include +#include #include #include #include @@ -49,11 +49,14 @@ class UntypedObjectModel: public QAbstractListModel { public: explicit UntypedObjectModel(QObject* parent): QAbstractListModel(parent) {} + [[nodiscard]] qint32 rowCount(const QModelIndex& parent) const override; + [[nodiscard]] QVariant data(const QModelIndex& index, qint32 role) const override; [[nodiscard]] QHash roleNames() const override; - [[nodiscard]] virtual QList values() = 0; + [[nodiscard]] QList values() const { return this->valuesList; }; + void removeAt(qsizetype index); - Q_INVOKABLE virtual qsizetype indexOf(QObject* object) const = 0; + Q_INVOKABLE qsizetype indexOf(QObject* object); static UntypedObjectModel* emptyInstance(); @@ -68,6 +71,15 @@ signals: /// Sent immediately after an object is removed from the list. void objectRemovedPost(QObject* object, qsizetype index); +protected: + void insertObject(QObject* object, qsizetype index = -1); + bool removeObject(const QObject* object); + + // Assumes only one instance of a specific value + void diffUpdate(const QVector& newValues); + + QVector valuesList; + private: static qsizetype valuesCount(QQmlListProperty* property); static QObject* valueAt(QQmlListProperty* property, qsizetype index); @@ -78,20 +90,14 @@ class ObjectModel: public UntypedObjectModel { public: explicit ObjectModel(QObject* parent): UntypedObjectModel(parent) {} - [[nodiscard]] const QList& valueList() const { return this->mValuesList; } - [[nodiscard]] QList& valueList() { return this->mValuesList; } + [[nodiscard]] QVector& valueList() { return *std::bit_cast*>(&this->valuesList); } + + [[nodiscard]] const QVector& valueList() const { + return *std::bit_cast*>(&this->valuesList); + } void insertObject(T* object, qsizetype index = -1) { - auto iindex = index == -1 ? this->mValuesList.length() : index; - emit this->objectInsertedPre(object, iindex); - - auto intIndex = static_cast(iindex); - this->beginInsertRows(QModelIndex(), intIndex, intIndex); - this->mValuesList.insert(iindex, object); - this->endInsertRows(); - - emit this->valuesChanged(); - emit this->objectInsertedPost(object, iindex); + this->UntypedObjectModel::insertObject(object, index); } void insertObjectSorted(T* object, const std::function& compare) { @@ -104,71 +110,17 @@ public: } auto idx = iter - list.begin(); - this->insertObject(object, idx); + this->UntypedObjectModel::insertObject(object, idx); } - bool removeObject(const T* object) { - auto index = this->mValuesList.indexOf(object); - if (index == -1) return false; - - this->removeAt(index); - return true; - } - - void removeAt(qsizetype index) { - auto* object = this->mValuesList.at(index); - emit this->objectRemovedPre(object, index); - - auto intIndex = static_cast(index); - this->beginRemoveRows(QModelIndex(), intIndex, intIndex); - this->mValuesList.removeAt(index); - this->endRemoveRows(); - - emit this->valuesChanged(); - emit this->objectRemovedPost(object, index); - } + void removeObject(const T* object) { this->UntypedObjectModel::removeObject(object); } // Assumes only one instance of a specific value - void diffUpdate(const QList& newValues) { - for (qsizetype i = 0; i < this->mValuesList.length();) { - if (newValues.contains(this->mValuesList.at(i))) i++; - else this->removeAt(i); - } - - qsizetype oi = 0; - for (auto* object: newValues) { - if (this->mValuesList.length() == oi || this->mValuesList.at(oi) != object) { - this->insertObject(object, oi); - } - - oi++; - } + void diffUpdate(const QVector& newValues) { + this->UntypedObjectModel::diffUpdate(*std::bit_cast*>(&newValues)); } static ObjectModel* emptyInstance() { return static_cast*>(UntypedObjectModel::emptyInstance()); } - - [[nodiscard]] qint32 rowCount(const QModelIndex& parent) const override { - if (parent != QModelIndex()) return 0; - return static_cast(this->mValuesList.length()); - } - - [[nodiscard]] QVariant data(const QModelIndex& index, qint32 role) const override { - if (role != Qt::UserRole) return QVariant(); - // Values must be QObject derived, but we can't assert that here without breaking forward decls, - // so no static_cast. - return QVariant::fromValue(reinterpret_cast(this->mValuesList.at(index.row()))); - } - - qsizetype indexOf(QObject* object) const override { - return this->mValuesList.indexOf(reinterpret_cast(object)); - } - - [[nodiscard]] QList values() override { - return *reinterpret_cast*>(&this->mValuesList); - } - -private: - QList mValuesList; }; diff --git a/src/core/module.md b/src/core/module.md index 41f065d..b9404ea 100644 --- a/src/core/module.md +++ b/src/core/module.md @@ -21,6 +21,7 @@ headers = [ "model.hpp", "elapsedtimer.hpp", "desktopentry.hpp", + "objectrepeater.hpp", "qsmenu.hpp", "retainable.hpp", "popupanchor.hpp", diff --git a/src/core/objectrepeater.cpp b/src/core/objectrepeater.cpp new file mode 100644 index 0000000..7971952 --- /dev/null +++ b/src/core/objectrepeater.cpp @@ -0,0 +1,190 @@ +#include "objectrepeater.hpp" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QVariant ObjectRepeater::model() const { return this->mModel; } + +void ObjectRepeater::setModel(QVariant model) { + if (model == this->mModel) return; + + if (this->itemModel != nullptr) { + QObject::disconnect(this->itemModel, nullptr, this, nullptr); + } + + this->mModel = std::move(model); + emit this->modelChanged(); + this->reloadElements(); +} + +void ObjectRepeater::onModelDestroyed() { + this->mModel.clear(); + this->itemModel = nullptr; + emit this->modelChanged(); + this->reloadElements(); +} + +QQmlComponent* ObjectRepeater::delegate() const { return this->mDelegate; } + +void ObjectRepeater::setDelegate(QQmlComponent* delegate) { + if (delegate == this->mDelegate) return; + + if (this->mDelegate != nullptr) { + QObject::disconnect(this->mDelegate, nullptr, this, nullptr); + } + + this->mDelegate = delegate; + + if (delegate != nullptr) { + QObject::connect( + this->mDelegate, + &QObject::destroyed, + this, + &ObjectRepeater::onDelegateDestroyed + ); + } + + emit this->delegateChanged(); + this->reloadElements(); +} + +void ObjectRepeater::onDelegateDestroyed() { + this->mDelegate = nullptr; + emit this->delegateChanged(); + this->reloadElements(); +} + +void ObjectRepeater::reloadElements() { + for (auto i = this->valuesList.length() - 1; i >= 0; i--) { + this->removeComponent(i); + } + + if (this->mDelegate == nullptr || !this->mModel.isValid()) return; + + if (this->mModel.canConvert()) { + auto* model = this->mModel.value(); + this->itemModel = model; + + this->insertModelElements(model, 0, model->rowCount() - 1); // -1 is fine + + // clang-format off + QObject::connect(model, &QObject::destroyed, this, &ObjectRepeater::onModelDestroyed); + QObject::connect(model, &QAbstractItemModel::rowsInserted, this, &ObjectRepeater::onModelRowsInserted); + QObject::connect(model, &QAbstractItemModel::rowsRemoved, this, &ObjectRepeater::onModelRowsRemoved); + QObject::connect(model, &QAbstractItemModel::rowsMoved, this, &ObjectRepeater::onModelRowsMoved); + QObject::connect(model, &QAbstractItemModel::modelAboutToBeReset, this, &ObjectRepeater::onModelAboutToBeReset); + // clang-format on + } else if (this->mModel.canConvert()) { + auto values = this->mModel.value(); + auto len = values.count(); + + for (auto i = 0; i != len; i++) { + this->insertComponent(i, {{"modelData", QVariant::fromValue(values.at(i))}}); + } + } else if (this->mModel.canConvert>()) { + auto values = this->mModel.value>(); + + for (auto& value: values) { + this->insertComponent(this->valuesList.length(), {{"modelData", value}}); + } + } else { + qCritical() << this + << "Cannot create components as the model is not compatible:" << this->mModel; + } +} + +void ObjectRepeater::insertModelElements(QAbstractItemModel* model, int first, int last) { + auto roles = model->roleNames(); + auto roleDataVec = QVector(); + for (auto id: roles.keys()) { + roleDataVec.push_back(QModelRoleData(id)); + } + + auto values = QModelRoleDataSpan(roleDataVec); + auto props = QVariantMap(); + + for (auto i = first; i != last + 1; i++) { + auto index = model->index(i, 0); + model->multiData(index, values); + + for (auto [id, name]: roles.asKeyValueRange()) { + props.insert(name, *values.dataForRole(id)); + } + + this->insertComponent(i, props); + + props.clear(); + } +} + +void ObjectRepeater::onModelRowsInserted(const QModelIndex& parent, int first, int last) { + if (parent != QModelIndex()) return; + + this->insertModelElements(this->itemModel, first, last); +} + +void ObjectRepeater::onModelRowsRemoved(const QModelIndex& parent, int first, int last) { + if (parent != QModelIndex()) return; + + for (auto i = last; i != first - 1; i--) { + this->removeComponent(i); + } +} + +void ObjectRepeater::onModelRowsMoved( + const QModelIndex& sourceParent, + int sourceStart, + int sourceEnd, + const QModelIndex& destParent, + int destStart +) { + auto hasSource = sourceParent != QModelIndex(); + auto hasDest = destParent != QModelIndex(); + + if (!hasSource && !hasDest) return; + + if (hasSource) { + this->onModelRowsRemoved(sourceParent, sourceStart, sourceEnd); + } + + if (hasDest) { + this->onModelRowsInserted(destParent, destStart, destStart + (sourceEnd - sourceStart)); + } +} + +void ObjectRepeater::onModelAboutToBeReset() { + auto last = static_cast(this->valuesList.length() - 1); + this->onModelRowsRemoved(QModelIndex(), 0, last); // -1 is fine +} + +void ObjectRepeater::insertComponent(qsizetype index, const QVariantMap& properties) { + auto* context = QQmlEngine::contextForObject(this); + auto* instance = this->mDelegate->createWithInitialProperties(properties, context); + + if (instance == nullptr) { + qWarning().noquote() << this->mDelegate->errorString(); + qWarning() << this << "failed to create object for model data" << properties; + } else { + QQmlEngine::setObjectOwnership(instance, QQmlEngine::CppOwnership); + instance->setParent(this); + } + + this->insertObject(instance, index); +} + +void ObjectRepeater::removeComponent(qsizetype index) { + auto* instance = this->valuesList.at(index); + this->removeAt(index); + delete instance; +} diff --git a/src/core/objectrepeater.hpp b/src/core/objectrepeater.hpp new file mode 100644 index 0000000..409b12d --- /dev/null +++ b/src/core/objectrepeater.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "model.hpp" + +///! A Repeater / for loop / map for non Item derived objects. +/// > [!ERROR] Removed in favor of @@QtQml.Models.Instantiator +/// +/// The ObjectRepeater creates instances of the provided delegate for every entry in the +/// given model, similarly to a @@QtQuick.Repeater but for non visual types. +class ObjectRepeater: public ObjectModel { + Q_OBJECT; + /// The model providing data to the ObjectRepeater. + /// + /// Currently accepted model types are `list` lists, javascript arrays, + /// and [QAbstractListModel] derived models, though only one column will be repeated + /// from the latter. + /// + /// Note: @@ObjectModel is a [QAbstractListModel] with a single column. + /// + /// [QAbstractListModel]: https://doc.qt.io/qt-6/qabstractlistmodel.html + Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged); + /// The delegate component to repeat. + /// + /// The delegate is given the same properties as in a Repeater, except `index` which + /// is not currently implemented. + /// + /// If the model is a `list` or javascript array, a `modelData` property will be + /// exposed containing the entry from the model. If the model is a [QAbstractListModel], + /// the roles from the model will be exposed. + /// + /// Note: @@ObjectModel has a single role named `modelData` for compatibility with normal lists. + /// + /// [QAbstractListModel]: https://doc.qt.io/qt-6/qabstractlistmodel.html + Q_PROPERTY(QQmlComponent* delegate READ delegate WRITE setDelegate NOTIFY delegateChanged); + Q_CLASSINFO("DefaultProperty", "delegate"); + QML_ELEMENT; + QML_UNCREATABLE("ObjectRepeater has been removed in favor of QtQml.Models.Instantiator."); + +public: + explicit ObjectRepeater(QObject* parent = nullptr): ObjectModel(parent) {} + + [[nodiscard]] QVariant model() const; + void setModel(QVariant model); + + [[nodiscard]] QQmlComponent* delegate() const; + void setDelegate(QQmlComponent* delegate); + +signals: + void modelChanged(); + void delegateChanged(); + +private slots: + void onDelegateDestroyed(); + void onModelDestroyed(); + void onModelRowsInserted(const QModelIndex& parent, int first, int last); + void onModelRowsRemoved(const QModelIndex& parent, int first, int last); + + void onModelRowsMoved( + const QModelIndex& sourceParent, + int sourceStart, + int sourceEnd, + const QModelIndex& destParent, + int destStart + ); + + void onModelAboutToBeReset(); + +private: + void reloadElements(); + void insertModelElements(QAbstractItemModel* model, int first, int last); + void insertComponent(qsizetype index, const QVariantMap& properties); + void removeComponent(qsizetype index); + + QVariant mModel; + QAbstractItemModel* itemModel = nullptr; + QQmlComponent* mDelegate = nullptr; +}; diff --git a/src/core/paths.cpp b/src/core/paths.cpp index 6555e54..1f3c494 100644 --- a/src/core/paths.cpp +++ b/src/core/paths.cpp @@ -27,19 +27,12 @@ QsPaths* QsPaths::instance() { return instance; } -void QsPaths::init( - QString shellId, - QString pathId, - QString dataOverride, - QString stateOverride, - QString cacheOverride -) { +void QsPaths::init(QString shellId, QString pathId, QString dataOverride, QString stateOverride) { auto* instance = QsPaths::instance(); instance->shellId = std::move(shellId); instance->pathId = std::move(pathId); instance->shellDataOverride = std::move(dataOverride); instance->shellStateOverride = std::move(stateOverride); - instance->shellCacheOverride = std::move(cacheOverride); } QDir QsPaths::crashDir(const QString& id) { @@ -142,41 +135,13 @@ QDir* QsPaths::instanceRunDir() { else return &this->mInstanceRunDir; } -QDir* QsPaths::shellVfsDir() { - if (this->shellVfsState == DirState::Unknown) { - if (auto* baseRunDir = this->baseRunDir()) { - this->mShellVfsDir = QDir(baseRunDir->filePath("vfs")); - this->mShellVfsDir = QDir(this->mShellVfsDir.filePath(this->shellId)); - - qCDebug(logPaths) << "Initialized runtime vfs path:" << this->mShellVfsDir.path(); - - if (!this->mShellVfsDir.mkpath(".")) { - qCCritical(logPaths) << "Could not create runtime vfs directory at" - << this->mShellVfsDir.path(); - this->shellVfsState = DirState::Failed; - } else { - this->shellVfsState = DirState::Ready; - } - } else { - qCCritical(logPaths) << "Could not create shell runtime vfs path as it was not possible to " - "create the base runtime path."; - - this->shellVfsState = DirState::Failed; - } - } - - if (this->shellVfsState == DirState::Failed) return nullptr; - else return &this->mShellVfsDir; -} - void QsPaths::linkRunDir() { if (auto* runDir = this->instanceRunDir()) { auto pidDir = QDir(this->baseRunDir()->filePath("by-pid")); auto* shellDir = this->shellRunDir(); if (!shellDir) { - qCCritical( - logPaths + qCCritical(logPaths ) << "Could not create by-id symlink as the shell runtime path could not be created."; } else { auto shellPath = shellDir->filePath(runDir->dirName()); @@ -324,16 +289,9 @@ QDir QsPaths::shellStateDir() { QDir QsPaths::shellCacheDir() { if (this->shellCacheState == DirState::Unknown) { - QDir dir; - if (this->shellCacheOverride.isEmpty()) { - dir = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); - dir = QDir(dir.filePath("by-shell")); - dir = QDir(dir.filePath(this->shellId)); - } else { - auto basedir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation); - dir = QDir(this->shellCacheOverride.replace("$BASE", basedir)); - } - + auto dir = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + dir = QDir(dir.filePath("by-shell")); + dir = QDir(dir.filePath(this->shellId)); this->mShellCacheDir = dir; qCDebug(logPaths) << "Initialized cache path:" << dir.path(); @@ -361,7 +319,7 @@ void QsPaths::createLock() { return; } - struct flock lock = { + auto lock = flock { .l_type = F_WRLCK, .l_whence = SEEK_SET, .l_start = 0, @@ -379,8 +337,7 @@ void QsPaths::createLock() { qCDebug(logPaths) << "Created instance lock at" << path; } } else { - qCCritical( - logPaths + qCCritical(logPaths ) << "Could not create instance lock, as the instance runtime directory could not be created."; } } @@ -389,7 +346,7 @@ bool QsPaths::checkLock(const QString& path, InstanceLockInfo* info, bool allowD auto file = QFile(QDir(path).filePath("instance.lock")); if (!file.open(QFile::ReadOnly)) return false; - struct flock lock = { + auto lock = flock { .l_type = F_WRLCK, .l_whence = SEEK_SET, .l_start = 0, @@ -413,7 +370,7 @@ bool QsPaths::checkLock(const QString& path, InstanceLockInfo* info, bool allowD } QPair, QVector> -QsPaths::collectInstances(const QString& path, const QString& display) { +QsPaths::collectInstances(const QString& path) { qCDebug(logPaths) << "Collecting instances from" << path; auto liveInstances = QVector(); auto deadInstances = QVector(); @@ -427,11 +384,6 @@ QsPaths::collectInstances(const QString& path, const QString& display) { qCDebug(logPaths).nospace() << "Found instance " << info.instance.instanceId << " (pid " << info.pid << ") at " << path; - if (!display.isEmpty() && info.instance.display != display) { - qCDebug(logPaths) << "Skipped instance with mismatched display at" << path; - continue; - } - if (info.pid == -1) { deadInstances.push_back(info); } else { diff --git a/src/core/paths.hpp b/src/core/paths.hpp index c2500ed..9646ca4 100644 --- a/src/core/paths.hpp +++ b/src/core/paths.hpp @@ -17,24 +17,17 @@ QDataStream& operator>>(QDataStream& stream, InstanceLockInfo& info); class QsPaths { public: static QsPaths* instance(); - static void init( - QString shellId, - QString pathId, - QString dataOverride, - QString stateOverride, - QString cacheOverride - ); + static void init(QString shellId, QString pathId, QString dataOverride, QString stateOverride); static QDir crashDir(const QString& id); static QString basePath(const QString& id); static QString ipcPath(const QString& id); static bool checkLock(const QString& path, InstanceLockInfo* info = nullptr, bool allowDead = false); static QPair, QVector> - collectInstances(const QString& path, const QString& display); + collectInstances(const QString& path); QDir* baseRunDir(); QDir* shellRunDir(); - QDir* shellVfsDir(); QDir* instanceRunDir(); void linkRunDir(); void linkPathDir(); @@ -55,11 +48,9 @@ private: QString pathId; QDir mBaseRunDir; QDir mShellRunDir; - QDir mShellVfsDir; QDir mInstanceRunDir; DirState baseRunState = DirState::Unknown; DirState shellRunState = DirState::Unknown; - DirState shellVfsState = DirState::Unknown; DirState instanceRunState = DirState::Unknown; QDir mShellDataDir; @@ -71,5 +62,4 @@ private: QString shellDataOverride; QString shellStateOverride; - QString shellCacheOverride; }; diff --git a/src/core/popupanchor.cpp b/src/core/popupanchor.cpp index 151dd5d..bbcc3a5 100644 --- a/src/core/popupanchor.cpp +++ b/src/core/popupanchor.cpp @@ -28,7 +28,7 @@ void PopupAnchor::markClean() { this->lastState = this->state; } void PopupAnchor::markDirty() { this->lastState.reset(); } QWindow* PopupAnchor::backingWindow() const { - return this->bProxyWindow ? this->bProxyWindow->backingWindow() : nullptr; + return this->mProxyWindow ? this->mProxyWindow->backingWindow() : nullptr; } void PopupAnchor::setWindowInternal(QObject* window) { @@ -36,14 +36,14 @@ void PopupAnchor::setWindowInternal(QObject* window) { if (this->mWindow) { QObject::disconnect(this->mWindow, nullptr, this, nullptr); - QObject::disconnect(this->bProxyWindow, nullptr, this, nullptr); + QObject::disconnect(this->mProxyWindow, nullptr, this, nullptr); } if (window) { if (auto* proxy = qobject_cast(window)) { - this->bProxyWindow = proxy; + this->mProxyWindow = proxy; } else if (auto* interface = qobject_cast(window)) { - this->bProxyWindow = interface->proxyWindow(); + this->mProxyWindow = interface->proxyWindow(); } else { qWarning() << "Tried to set popup anchor window to" << window << "which is not a quickshell window."; @@ -55,7 +55,7 @@ void PopupAnchor::setWindowInternal(QObject* window) { QObject::connect(this->mWindow, &QObject::destroyed, this, &PopupAnchor::onWindowDestroyed); QObject::connect( - this->bProxyWindow, + this->mProxyWindow, &ProxyWindowBase::backerVisibilityChanged, this, &PopupAnchor::backingWindowVisibilityChanged @@ -70,7 +70,7 @@ void PopupAnchor::setWindowInternal(QObject* window) { setnull: if (this->mWindow) { this->mWindow = nullptr; - this->bProxyWindow = nullptr; + this->mProxyWindow = nullptr; emit this->windowChanged(); emit this->backingWindowVisibilityChanged(); @@ -100,7 +100,7 @@ void PopupAnchor::setItem(QQuickItem* item) { void PopupAnchor::onWindowDestroyed() { this->mWindow = nullptr; - this->bProxyWindow = nullptr; + this->mProxyWindow = nullptr; emit this->windowChanged(); emit this->backingWindowVisibilityChanged(); } @@ -186,11 +186,11 @@ void PopupAnchor::updatePlacement(const QPoint& anchorpoint, const QSize& size) } void PopupAnchor::updateAnchor() { - if (this->mItem && this->bProxyWindow) { + if (this->mItem && this->mProxyWindow) { auto baseRect = this->mUserRect.isEmpty() ? this->mItem->boundingRect() : this->mUserRect.qrect(); - auto rect = this->bProxyWindow->contentItem()->mapFromItem( + auto rect = this->mProxyWindow->contentItem()->mapFromItem( this->mItem, baseRect.marginsRemoved(this->mMargins.qmargins()) ); diff --git a/src/core/popupanchor.hpp b/src/core/popupanchor.hpp index 9f08512..a9b121e 100644 --- a/src/core/popupanchor.hpp +++ b/src/core/popupanchor.hpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -140,9 +139,7 @@ public: void markDirty(); [[nodiscard]] QObject* window() const { return this->mWindow; } - [[nodiscard]] QBindable bindableProxyWindow() const { - return &this->bProxyWindow; - } + [[nodiscard]] ProxyWindowBase* proxyWindow() const { return this->mProxyWindow; } [[nodiscard]] QWindow* backingWindow() const; void setWindowInternal(QObject* window); void setWindow(QObject* window); @@ -196,12 +193,11 @@ private slots: private: QObject* mWindow = nullptr; QQuickItem* mItem = nullptr; + ProxyWindowBase* mProxyWindow = nullptr; PopupAnchorState state; Box mUserRect; Margins mMargins; std::optional lastState; - - Q_OBJECT_BINDABLE_PROPERTY(PopupAnchor, ProxyWindowBase*, bProxyWindow); }; class PopupPositioner { diff --git a/src/core/qmlglobal.cpp b/src/core/qmlglobal.cpp index 6c26609..0aba306 100644 --- a/src/core/qmlglobal.cpp +++ b/src/core/qmlglobal.cpp @@ -29,7 +29,6 @@ #include "paths.hpp" #include "qmlscreen.hpp" #include "rootwrapper.hpp" -#include "scanenv.hpp" QuickshellSettings::QuickshellSettings() { QObject::connect( @@ -211,20 +210,8 @@ void QuickshellGlobal::onClipboardChanged(QClipboard::Mode mode) { if (mode == QClipboard::Clipboard) emit this->clipboardTextChanged(); } -QString QuickshellGlobal::shellDir() const { - return EngineGeneration::findObjectGeneration(this)->rootPath.path(); -} - QString QuickshellGlobal::configDir() const { - qWarning() << "Quickshell.configDir is deprecated and may be removed in a future release. Use " - "Quickshell.shellDir."; - return this->shellDir(); -} - -QString QuickshellGlobal::shellRoot() const { - qWarning() << "Quickshell.shellRoot is deprecated and may be removed in a future release. Use " - "Quickshell.shellDir."; - return this->shellDir(); + return EngineGeneration::findObjectGeneration(this)->rootPath.path(); } QString QuickshellGlobal::dataDir() const { // NOLINT @@ -239,14 +226,8 @@ QString QuickshellGlobal::cacheDir() const { // NOLINT return QsPaths::instance()->shellCacheDir().path(); } -QString QuickshellGlobal::shellPath(const QString& path) const { - return this->shellDir() % '/' % path; -} - QString QuickshellGlobal::configPath(const QString& path) const { - qWarning() << "Quickshell.configPath() is deprecated and may be removed in a future release. Use " - "Quickshell.shellPath()."; - return this->shellPath(path); + return this->configDir() % '/' % path; } QString QuickshellGlobal::dataPath(const QString& path) const { @@ -314,16 +295,6 @@ QString QuickshellGlobal::iconPath(const QString& icon, const QString& fallback) return IconImageProvider::requestString(icon, "", fallback); } -bool QuickshellGlobal::hasThemeIcon(const QString& icon) { return QIcon::hasThemeIcon(icon); } - -bool QuickshellGlobal::hasVersion(qint32 major, qint32 minor, const QStringList& features) { - return qs::scan::env::PreprocEnv::hasVersion(major, minor, features); -} - -bool QuickshellGlobal::hasVersion(qint32 major, qint32 minor) { - return QuickshellGlobal::hasVersion(major, minor, QStringList()); -} - QuickshellGlobal* QuickshellGlobal::create(QQmlEngine* engine, QJSEngine* /*unused*/) { auto* qsg = new QuickshellGlobal(); auto* generation = EngineGeneration::findEngineGeneration(engine); diff --git a/src/core/qmlglobal.hpp b/src/core/qmlglobal.hpp index 94b42f6..d05b96d 100644 --- a/src/core/qmlglobal.hpp +++ b/src/core/qmlglobal.hpp @@ -108,11 +108,9 @@ class QuickshellGlobal: public QObject { /// /// The root directory is the folder containing the entrypoint to your shell, often referred /// to as `shell.qml`. - Q_PROPERTY(QString shellDir READ shellDir CONSTANT); - /// > [!WARNING] Deprecated: Renamed to @@shellDir for clarity. Q_PROPERTY(QString configDir READ configDir CONSTANT); - /// > [!WARNING] Deprecated: Renamed to @@shellDir for consistency. - Q_PROPERTY(QString shellRoot READ shellRoot CONSTANT); + /// > [!WARNING] Deprecated: Returns @@configDir. + Q_PROPERTY(QString shellRoot READ configDir CONSTANT); /// Quickshell's working directory. Defaults to whereever quickshell was launched from. Q_PROPERTY(QString workingDirectory READ workingDirectory WRITE setWorkingDirectory NOTIFY workingDirectoryChanged); /// If true then the configuration will be reloaded whenever any files change. @@ -127,21 +125,18 @@ class QuickshellGlobal: public QObject { /// Usually `~/.local/share/quickshell/by-shell/` /// /// Can be overridden using `//@ pragma DataDir $BASE/path` in the root qml file, where `$BASE` - /// corresponds to `$XDG_DATA_HOME` (usually `~/.local/share`). + /// corrosponds to `$XDG_DATA_HOME` (usually `~/.local/share`). Q_PROPERTY(QString dataDir READ dataDir CONSTANT); /// The per-shell state directory. /// /// Usually `~/.local/state/quickshell/by-shell/` /// /// Can be overridden using `//@ pragma StateDir $BASE/path` in the root qml file, where `$BASE` - /// corresponds to `$XDG_STATE_HOME` (usually `~/.local/state`). + /// corrosponds to `$XDG_STATE_HOME` (usually `~/.local/state`). Q_PROPERTY(QString stateDir READ stateDir CONSTANT); /// The per-shell cache directory. /// /// Usually `~/.cache/quickshell/by-shell/` - /// - /// Can be overridden using `//@ pragma CacheDir $BASE/path` in the root qml file, where `$BASE` - /// corresponds to `$XDG_CACHE_HOME` (usually `~/.cache`). Q_PROPERTY(QString cacheDir READ cacheDir CONSTANT); // clang-format on QML_SINGLETON; @@ -202,11 +197,7 @@ public: /// Setting the `fallback` parameter of `iconPath` will attempt to load the fallback /// icon if the requested one could not be loaded. Q_INVOKABLE static QString iconPath(const QString& icon, const QString& fallback); - /// Check if specified icon has an available icon in your icon theme - Q_INVOKABLE static bool hasThemeIcon(const QString& icon); /// Equivalent to `${Quickshell.configDir}/${path}` - Q_INVOKABLE [[nodiscard]] QString shellPath(const QString& path) const; - /// > [!WARNING] Deprecated: Renamed to @@shellPath() for clarity. Q_INVOKABLE [[nodiscard]] QString configPath(const QString& path) const; /// Equivalent to `${Quickshell.dataDir}/${path}` Q_INVOKABLE [[nodiscard]] QString dataPath(const QString& path) const; @@ -219,28 +210,11 @@ public: /// /// The popup can also be blocked by setting `QS_NO_RELOAD_POPUP=1`. Q_INVOKABLE void inhibitReloadPopup() { this->mInhibitReloadPopup = true; } - /// Check if Quickshell's version is at least `major.minor` and the listed - /// unreleased features are available. If Quickshell is newer than the given version - /// it is assumed that all unreleased features are present. The unreleased feature list - /// may be omitted. - /// - /// > [!NOTE] You can feature gate code blocks using Quickshell's preprocessor which - /// > has the same function available. - /// > - /// > ```qml - /// > //@ if hasVersion(0, 3, ["feature"]) - /// > ... - /// > //@ endif - /// > ``` - Q_INVOKABLE static bool hasVersion(qint32 major, qint32 minor, const QStringList& features); - Q_INVOKABLE static bool hasVersion(qint32 major, qint32 minor); void clearReloadPopupInhibit() { this->mInhibitReloadPopup = false; } [[nodiscard]] bool isReloadPopupInhibited() const { return this->mInhibitReloadPopup; } - [[nodiscard]] QString shellDir() const; [[nodiscard]] QString configDir() const; - [[nodiscard]] QString shellRoot() const; [[nodiscard]] QString workingDirectory() const; void setWorkingDirectory(QString workingDirectory); diff --git a/src/core/qsmenu.hpp b/src/core/qsmenu.hpp index 90df8b9..6684c68 100644 --- a/src/core/qsmenu.hpp +++ b/src/core/qsmenu.hpp @@ -46,8 +46,8 @@ class QsMenuHandle: public QObject { public: explicit QsMenuHandle(QObject* parent): QObject(parent) {} - virtual void refHandle() {} - virtual void unrefHandle() {} + virtual void refHandle() {}; + virtual void unrefHandle() {}; [[nodiscard]] virtual QsMenuEntry* menu() = 0; diff --git a/src/core/region.cpp b/src/core/region.cpp index 11892d6..e36ed7d 100644 --- a/src/core/region.cpp +++ b/src/core/region.cpp @@ -1,13 +1,13 @@ #include "region.hpp" #include +#include #include #include #include #include #include #include -#include #include PendingRegion::PendingRegion(QObject* parent): QObject(parent) { @@ -19,6 +19,7 @@ PendingRegion::PendingRegion(QObject* parent): QObject(parent) { QObject::connect(this, &PendingRegion::widthChanged, this, &PendingRegion::changed); QObject::connect(this, &PendingRegion::heightChanged, this, &PendingRegion::changed); QObject::connect(this, &PendingRegion::childrenChanged, this, &PendingRegion::changed); + QObject::connect(this, &PendingRegion::regionsChanged, this, &PendingRegion::childrenChanged); } void PendingRegion::setItem(QQuickItem* item) { @@ -41,21 +42,33 @@ void PendingRegion::setItem(QQuickItem* item) { emit this->itemChanged(); } -void PendingRegion::onItemDestroyed() { this->mItem = nullptr; } +void PendingRegion::onItemDestroyed() { + this->mItem = nullptr; + emit this->itemChanged(); +} -void PendingRegion::onChildDestroyed() { this->mRegions.removeAll(this->sender()); } +void PendingRegion::onChildDestroyed() { + this->mRegions.removeAll(this->sender()); + emit this->regionsChanged(); +} -QQmlListProperty PendingRegion::regions() { - return QQmlListProperty( - this, - nullptr, - &PendingRegion::regionsAppend, - &PendingRegion::regionsCount, - &PendingRegion::regionAt, - &PendingRegion::regionsClear, - &PendingRegion::regionsReplace, - &PendingRegion::regionsRemoveLast - ); +const QList& PendingRegion::regions() const { return this->mRegions; } + +void PendingRegion::setRegions(const QList& regions) { + if (regions == this->mRegions) return; + + for (auto* region: this->mRegions) { + QObject::disconnect(region, nullptr, this, nullptr); + } + + this->mRegions = regions; + + for (auto* region: regions) { + QObject::connect(region, &QObject::destroyed, this, &PendingRegion::onChildDestroyed); + QObject::connect(region, &PendingRegion::changed, this, &PendingRegion::childrenChanged); + } + + emit this->regionsChanged(); } bool PendingRegion::empty() const { @@ -117,58 +130,3 @@ QRegion PendingRegion::applyTo(const QRect& rect) const { return this->applyTo(baseRegion); } } - -void PendingRegion::regionsAppend(QQmlListProperty* prop, PendingRegion* region) { - auto* self = static_cast(prop->object); // NOLINT - if (!region) return; - - QObject::connect(region, &QObject::destroyed, self, &PendingRegion::onChildDestroyed); - QObject::connect(region, &PendingRegion::changed, self, &PendingRegion::childrenChanged); - - self->mRegions.append(region); - - emit self->childrenChanged(); -} - -PendingRegion* PendingRegion::regionAt(QQmlListProperty* prop, qsizetype i) { - return static_cast(prop->object)->mRegions.at(i); // NOLINT -} - -void PendingRegion::regionsClear(QQmlListProperty* prop) { - auto* self = static_cast(prop->object); // NOLINT - - for (auto* region: self->mRegions) { - QObject::disconnect(region, nullptr, self, nullptr); - } - - self->mRegions.clear(); // NOLINT - emit self->childrenChanged(); -} - -qsizetype PendingRegion::regionsCount(QQmlListProperty* prop) { - return static_cast(prop->object)->mRegions.length(); // NOLINT -} - -void PendingRegion::regionsRemoveLast(QQmlListProperty* prop) { - auto* self = static_cast(prop->object); // NOLINT - - auto* last = self->mRegions.last(); - if (last != nullptr) QObject::disconnect(last, nullptr, self, nullptr); - - self->mRegions.removeLast(); - emit self->childrenChanged(); -} - -void PendingRegion::regionsReplace( - QQmlListProperty* prop, - qsizetype i, - PendingRegion* region -) { - auto* self = static_cast(prop->object); // NOLINT - - auto* old = self->mRegions.at(i); - if (old != nullptr) QObject::disconnect(old, nullptr, self, nullptr); - - self->mRegions.replace(i, region); - emit self->childrenChanged(); -} diff --git a/src/core/region.hpp b/src/core/region.hpp index 6637d7b..0335abb 100644 --- a/src/core/region.hpp +++ b/src/core/region.hpp @@ -82,7 +82,7 @@ class PendingRegion: public QObject { /// } /// } /// ``` - Q_PROPERTY(QQmlListProperty regions READ regions); + Q_PROPERTY(QList regions READ regions WRITE setRegions NOTIFY regionsChanged); Q_CLASSINFO("DefaultProperty", "regions"); QML_NAMED_ELEMENT(Region); @@ -91,7 +91,8 @@ public: void setItem(QQuickItem* item); - QQmlListProperty regions(); + [[nodiscard]] const QList& regions() const; + void setRegions(const QList& regions); [[nodiscard]] bool empty() const; [[nodiscard]] QRegion build() const; @@ -109,6 +110,7 @@ signals: void yChanged(); void widthChanged(); void heightChanged(); + void regionsChanged(); void childrenChanged(); /// Triggered when the region's geometry changes. @@ -122,14 +124,6 @@ private slots: void onChildDestroyed(); private: - static void regionsAppend(QQmlListProperty* prop, PendingRegion* region); - static PendingRegion* regionAt(QQmlListProperty* prop, qsizetype i); - static void regionsClear(QQmlListProperty* prop); - static qsizetype regionsCount(QQmlListProperty* prop); - static void regionsRemoveLast(QQmlListProperty* prop); - static void - regionsReplace(QQmlListProperty* prop, qsizetype i, PendingRegion* region); - QQuickItem* mItem = nullptr; qint32 mX = 0; diff --git a/src/core/reload.cpp b/src/core/reload.cpp index ea2abbf..0bdf8fc 100644 --- a/src/core/reload.cpp +++ b/src/core/reload.cpp @@ -129,18 +129,14 @@ QObject* Reloadable::getChildByReloadId(QObject* parent, const QString& reloadId void PostReloadHook::componentComplete() { auto* engineGeneration = EngineGeneration::findObjectGeneration(this); if (!engineGeneration || engineGeneration->reloadComplete) this->postReload(); - else { - // disconnected by EngineGeneration::postReload - QObject::connect( - engineGeneration, - &EngineGeneration::firePostReload, - this, - &PostReloadHook::postReload - ); - } } void PostReloadHook::postReload() { this->isPostReload = true; this->onPostReload(); } + +void PostReloadHook::postReloadTree(QObject* root) { + for (auto* child: root->children()) PostReloadHook::postReloadTree(child); + if (auto* self = dynamic_cast(root)) self->postReload(); +} diff --git a/src/core/reload.hpp b/src/core/reload.hpp index ae5d7c9..1d4e375 100644 --- a/src/core/reload.hpp +++ b/src/core/reload.hpp @@ -57,7 +57,7 @@ public: void reload(QObject* oldInstance = nullptr); - void classBegin() override {} + void classBegin() override {}; void componentComplete() override; // Reload objects in the parent->child graph recursively. @@ -122,19 +122,15 @@ private: class PostReloadHook : public QObject , public QQmlParserStatus { - Q_OBJECT; - QML_ANONYMOUS; - Q_INTERFACES(QQmlParserStatus); - public: PostReloadHook(QObject* parent = nullptr): QObject(parent) {} void classBegin() override {} void componentComplete() override; + void postReload(); virtual void onPostReload() = 0; -public slots: - void postReload(); + static void postReloadTree(QObject* root); protected: bool isPostReload = false; diff --git a/src/core/rootwrapper.cpp b/src/core/rootwrapper.cpp index 1e75819..2968402 100644 --- a/src/core/rootwrapper.cpp +++ b/src/core/rootwrapper.cpp @@ -4,7 +4,6 @@ #include #include -#include #include #include #include @@ -19,26 +18,15 @@ #include "instanceinfo.hpp" #include "qmlglobal.hpp" #include "scan.hpp" -#include "toolsupport.hpp" RootWrapper::RootWrapper(QString rootPath, QString shellId) : QObject(nullptr) , rootPath(std::move(rootPath)) , shellId(std::move(shellId)) , originalWorkingDirectory(QDir::current().absolutePath()) { - QObject::connect( - QuickshellSettings::instance(), - &QuickshellSettings::watchFilesChanged, - this, - &RootWrapper::onWatchFilesChanged - ); - - QObject::connect( - &this->configDirWatcher, - &QFileSystemWatcher::directoryChanged, - this, - &RootWrapper::updateTooling - ); + // clang-format off + QObject::connect(QuickshellSettings::instance(), &QuickshellSettings::watchFilesChanged, this, &RootWrapper::onWatchFilesChanged); + // clang-format on this->reloadGraph(true); @@ -58,10 +46,10 @@ void RootWrapper::reloadGraph(bool hard) { auto rootFile = QFileInfo(this->rootPath); auto rootPath = rootFile.dir(); auto scanner = QmlScanner(rootPath); - scanner.scanQmlRoot(this->rootPath); + scanner.scanQmlFile(this->rootPath); - qs::core::QmlToolingSupport::updateTooling(rootPath, scanner); - this->configDirWatcher.addPath(rootPath.path()); + auto* generation = new EngineGeneration(rootPath, std::move(scanner)); + generation->wrapper = this; // todo: move into EngineGeneration if (this->generation != nullptr) { @@ -71,33 +59,6 @@ void RootWrapper::reloadGraph(bool hard) { QDir::setCurrent(this->originalWorkingDirectory); - if (!scanner.scanErrors.isEmpty()) { - qCritical() << "Failed to load configuration"; - QString errorString = "Failed to load configuration"; - for (auto& error: scanner.scanErrors) { - const auto& file = error.file; - QString rel; - if (file.startsWith(rootPath.path() % '/')) { - rel = '@' % file.sliced(rootPath.path().length() + 1); - } else { - rel = file; - } - - auto msg = " error in " % rel % '[' % QString::number(error.line) % ":0]: " % error.message; - errorString += '\n' % msg; - qCritical().noquote() << msg; - } - - if (this->generation != nullptr && this->generation->qsgInstance != nullptr) { - emit this->generation->qsgInstance->reloadFailed(errorString); - } - - return; - } - - auto* generation = new EngineGeneration(rootPath, std::move(scanner)); - generation->wrapper = this; - QUrl url; url.setScheme("qs"); url.setPath("@/qs/" % rootFile.fileName()); @@ -207,9 +168,3 @@ void RootWrapper::onWatchFilesChanged() { } void RootWrapper::onWatchedFilesChanged() { this->reloadGraph(false); } - -void RootWrapper::updateTooling() { - if (!this->generation) return; - auto configDir = QFileInfo(this->rootPath).dir(); - qs::core::QmlToolingSupport::updateTooling(configDir, this->generation->scanner); -} diff --git a/src/core/rootwrapper.hpp b/src/core/rootwrapper.hpp index 1425d17..02d7a14 100644 --- a/src/core/rootwrapper.hpp +++ b/src/core/rootwrapper.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -23,12 +22,10 @@ private slots: void generationDestroyed(); void onWatchFilesChanged(); void onWatchedFilesChanged(); - void updateTooling(); private: QString rootPath; QString shellId; EngineGeneration* generation = nullptr; QString originalWorkingDirectory; - QFileSystemWatcher configDirWatcher; }; diff --git a/src/core/scan.cpp b/src/core/scan.cpp index 37b0fac..a29ee59 100644 --- a/src/core/scan.cpp +++ b/src/core/scan.cpp @@ -1,11 +1,9 @@ #include "scan.hpp" #include -#include #include #include #include -#include #include #include #include @@ -14,57 +12,44 @@ #include #include #include +#include #include #include "logcat.hpp" -#include "scanenv.hpp" QS_LOGGING_CATEGORY(logQmlScanner, "quickshell.qmlscanner", QtWarningMsg); -void QmlScanner::scanDir(const QDir& dir) { - if (this->scannedDirs.contains(dir)) return; - this->scannedDirs.push_back(dir); - - const auto& path = dir.path(); +void QmlScanner::scanDir(const QString& path) { + if (this->scannedDirs.contains(path)) return; + this->scannedDirs.push_back(path); qCDebug(logQmlScanner) << "Scanning directory" << path; - - struct Entry { - QString name; - bool singleton = false; - bool internal = false; - }; + auto dir = QDir(path); bool seenQmldir = false; - auto entries = QVector(); - - for (auto& name: dir.entryList(QDir::Files | QDir::NoDotAndDotDot)) { - if (name == "qmldir") { - qCDebug( - logQmlScanner + auto singletons = QVector(); + auto entries = QVector(); + for (auto& entry: dir.entryList(QDir::Files | QDir::NoDotAndDotDot)) { + if (entry == "qmldir") { + qCDebug(logQmlScanner ) << "Found qmldir file, qmldir synthesization will be disabled for directory" << path; seenQmldir = true; - } else if (name.at(0).isUpper() && name.endsWith(".qml")) { - auto& entry = entries.emplaceBack(); - - if (this->scanQmlFile(dir.filePath(name), entry.singleton, entry.internal)) { - entry.name = name; + } else if (entry.at(0).isUpper() && entry.endsWith(".qml")) { + if (this->scanQmlFile(dir.filePath(entry))) { + singletons.push_back(entry); } else { - entries.pop_back(); - } - } else if (name.at(0).isUpper() && name.endsWith(".qml.json")) { - if (this->scanQmlJson(dir.filePath(name))) { - entries.push_back({ - .name = name.first(name.length() - 5), - .singleton = true, - }); + entries.push_back(entry); } + } else if (entry.at(0).isUpper() && entry.endsWith(".qml.json")) { + this->scanQmlJson(dir.filePath(entry)); + singletons.push_back(entry.first(entry.length() - 5)); } } if (!seenQmldir) { - qCDebug(logQmlScanner) << "Synthesizing qmldir for directory" << path; + qCDebug(logQmlScanner) << "Synthesizing qmldir for directory" << path << "singletons" + << singletons; QString qmldir; auto stream = QTextStream(&qmldir); @@ -92,10 +77,13 @@ void QmlScanner::scanDir(const QDir& dir) { qCWarning(logQmlScanner) << "Module path" << path << "is outside of the config folder."; } - for (const auto& entry: entries) { - if (entry.internal) stream << "internal "; - if (entry.singleton) stream << "singleton "; - stream << entry.name.sliced(0, entry.name.length() - 4) << " 1.0 " << entry.name << '\n'; + for (auto& singleton: singletons) { + stream << "singleton " << singleton.sliced(0, singleton.length() - 4) << " 1.0 " << singleton + << "\n"; + } + + for (auto& entry: entries) { + stream << entry.sliced(0, entry.length() - 4) << " 1.0 " << entry << "\n"; } qCDebug(logQmlScanner) << "Synthesized qmldir for" << path << qPrintable("\n" + qmldir); @@ -103,7 +91,7 @@ void QmlScanner::scanDir(const QDir& dir) { } } -bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& internal) { +bool QmlScanner::scanQmlFile(const QString& path) { if (this->scannedFiles.contains(path)) return false; this->scannedFiles.push_back(path); @@ -118,121 +106,60 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna auto stream = QTextStream(&file); auto imports = QVector(); - bool inHeader = true; - auto ifScopes = QVector(); - bool sourceMasked = false; - int lineNum = 0; - QString overrideText; - bool isOverridden = false; - - auto pragmaEngine = QJSEngine(); - pragmaEngine.globalObject().setPrototype( - pragmaEngine.newQObject(new qs::scan::env::PreprocEnv()) - ); - - auto postError = [&, this](QString error) { - this->scanErrors.append({.file = path, .message = std::move(error), .line = lineNum}); - }; + bool singleton = false; while (!stream.atEnd()) { - ++lineNum; - bool hideMask = false; - auto rawLine = stream.readLine(); - auto line = rawLine.trimmed(); - if (!sourceMasked && inHeader) { - if (!singleton && line == "pragma Singleton") { - singleton = true; - } else if (line.startsWith("import")) { - // we dont care about "import qs" as we always load the root folder - if (auto importCursor = line.indexOf(" qs."); importCursor != -1) { - importCursor += 4; - QString path; + auto line = stream.readLine().trimmed(); + if (!singleton && line == "pragma Singleton") { + qCDebug(logQmlScanner) << "Discovered singleton" << path; + singleton = true; + } else if (line.startsWith("import")) { + // we dont care about "import qs" as we always load the root folder + if (auto importCursor = line.indexOf(" qs."); importCursor != -1) { + importCursor += 4; + QString path; - while (importCursor != line.length()) { - auto c = line.at(importCursor); - if (c == '.') c = '/'; - else if (c == ' ') break; - else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') - || c == '_') - { - } else { - qCWarning(logQmlScanner) << "Import line contains invalid characters: " << line; - goto next; - } - - path.append(c); - importCursor += 1; + while (importCursor != line.length()) { + auto c = line.at(importCursor); + if (c == '.') c = '/'; + else if (c == ' ') break; + else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') + || c == '_') + { + } else { + qCWarning(logQmlScanner) << "Import line contains invalid characters: " << line; + goto next; } - imports.append(this->rootPath.filePath(path)); - } else if (auto startQuot = line.indexOf('"'); - startQuot != -1 && line.length() >= startQuot + 3) - { - auto endQuot = line.indexOf('"', startQuot + 1); - if (endQuot == -1) continue; - - auto name = line.sliced(startQuot + 1, endQuot - startQuot - 1); - imports.push_back(name); + path.append(c); + importCursor += 1; } - } else if (!internal && line == "//@ pragma Internal") { - internal = true; - } else if (line.contains('{')) { - inHeader = false; + + imports.append(this->rootPath.filePath(path)); + } else if (auto startQuot = line.indexOf('"'); + startQuot != -1 && line.length() >= startQuot + 3) + { + auto endQuot = line.indexOf('"', startQuot + 1); + if (endQuot == -1) continue; + + auto name = line.sliced(startQuot + 1, endQuot - startQuot - 1); + imports.push_back(name); } - } - - if (line.startsWith("//@ if ")) { - auto code = line.sliced(7); - auto value = pragmaEngine.evaluate(code, path, 1234); - bool mask = true; - - if (value.isError()) { - postError(QString("Evaluating if: %0").arg(value.toString())); - } else if (!value.isBool()) { - postError(QString("If expression \"%0\" is not a boolean").arg(value.toString())); - } else if (value.toBool()) { - mask = false; - } - if (!sourceMasked && mask) hideMask = true; - mask = sourceMasked || mask; // cant unmask if a nested if passes - ifScopes.append(mask); - if (mask) isOverridden = true; - sourceMasked = mask; - } else if (line.startsWith("//@ endif")) { - if (ifScopes.isEmpty()) { - postError("endif without matching if"); - } else { - ifScopes.pop_back(); - - if (ifScopes.isEmpty()) sourceMasked = false; - else sourceMasked = ifScopes.last(); - } - } - - if (!hideMask && sourceMasked) overrideText.append("// MASKED: " % rawLine % '\n'); - else overrideText.append(rawLine % '\n'); + } else if (line.contains('{')) break; next:; } - if (!ifScopes.isEmpty()) { - postError("unclosed preprocessor if block"); - } - file.close(); - if (isOverridden) { - this->fileIntercepts.insert(path, overrideText); - } - if (logQmlScanner().isDebugEnabled() && !imports.isEmpty()) { qCDebug(logQmlScanner) << "Found imports" << imports; } - auto currentdir = QDir(QFileInfo(path).absolutePath()); + auto currentdir = QDir(QFileInfo(path).canonicalPath()); // the root can never be a singleton so it dosent matter if we skip it - this->scanDir(currentdir); + this->scanDir(currentdir.path()); for (auto& import: imports) { QString ipath; @@ -245,9 +172,9 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna } auto pathInfo = QFileInfo(ipath); - auto cpath = pathInfo.absoluteFilePath(); + auto cpath = pathInfo.canonicalFilePath(); - if (!pathInfo.exists()) { + if (cpath.isEmpty()) { qCWarning(logQmlScanner) << "Ignoring unresolvable import" << ipath << "from" << path; continue; } @@ -261,22 +188,16 @@ bool QmlScanner::scanQmlFile(const QString& path, bool& singleton, bool& interna else this->scanDir(cpath); } - return true; + return singleton; } -void QmlScanner::scanQmlRoot(const QString& path) { - bool singleton = false; - bool internal = false; - this->scanQmlFile(path, singleton, internal); -} - -bool QmlScanner::scanQmlJson(const QString& path) { +void QmlScanner::scanQmlJson(const QString& path) { qCDebug(logQmlScanner) << "Scanning qml.json file" << path; auto file = QFile(path); if (!file.open(QFile::ReadOnly | QFile::Text)) { qCWarning(logQmlScanner) << "Failed to open file" << path; - return false; + return; } auto data = file.readAll(); @@ -288,7 +209,7 @@ bool QmlScanner::scanQmlJson(const QString& path) { if (error.error != QJsonParseError::NoError) { qCCritical(logQmlScanner).nospace() << "Failed to parse qml.json file at " << path << ": " << error.errorString(); - return false; + return; } const QString body = @@ -298,7 +219,6 @@ bool QmlScanner::scanQmlJson(const QString& path) { this->fileIntercepts.insert(path.first(path.length() - 5), body); this->scannedFiles.push_back(path); - return true; } QPair QmlScanner::jsonToQml(const QJsonValue& value, int indent) { diff --git a/src/core/scan.hpp b/src/core/scan.hpp index 29f8f6a..6220bae 100644 --- a/src/core/scan.hpp +++ b/src/core/scan.hpp @@ -16,25 +16,18 @@ public: QmlScanner() = default; QmlScanner(const QDir& rootPath): rootPath(rootPath) {} - void scanDir(const QDir& dir); - void scanQmlRoot(const QString& path); + // path must be canonical + void scanDir(const QString& path); + // returns if the file has a singleton + bool scanQmlFile(const QString& path); - QVector scannedDirs; + QVector scannedDirs; QVector scannedFiles; QHash fileIntercepts; - struct ScanError { - QString file; - QString message; - int line; - }; - - QVector scanErrors; - private: QDir rootPath; - bool scanQmlFile(const QString& path, bool& singleton, bool& internal); - bool scanQmlJson(const QString& path); + void scanQmlJson(const QString& path); [[nodiscard]] static QPair jsonToQml(const QJsonValue& value, int indent = 0); }; diff --git a/src/core/scanenv.cpp b/src/core/scanenv.cpp deleted file mode 100644 index 047f472..0000000 --- a/src/core/scanenv.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "scanenv.hpp" - -#include -#include - -#include "build.hpp" - -namespace qs::scan::env { - -bool PreprocEnv::hasVersion(int major, int minor, const QStringList& features) { - if (QS_VERSION_MAJOR > major) return true; - if (QS_VERSION_MAJOR == major && QS_VERSION_MINOR > minor) return true; - - auto availFeatures = QString(QS_UNRELEASED_FEATURES).split(';'); - - for (const auto& feature: features) { - if (!availFeatures.contains(feature)) return false; - } - - return QS_VERSION_MAJOR == major && QS_VERSION_MINOR == minor; -} - -QString PreprocEnv::env(const QString& variable) { - return qEnvironmentVariable(variable.toStdString().c_str()); -} - -bool PreprocEnv::isEnvSet(const QString& variable) { - return qEnvironmentVariableIsSet(variable.toStdString().c_str()); -} - -} // namespace qs::scan::env diff --git a/src/core/scanenv.hpp b/src/core/scanenv.hpp deleted file mode 100644 index c1c6814..0000000 --- a/src/core/scanenv.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace qs::scan::env { - -class PreprocEnv: public QObject { - Q_OBJECT; - -public: - Q_INVOKABLE static bool - hasVersion(int major, int minor, const QStringList& features = QStringList()); - - Q_INVOKABLE static QString env(const QString& variable); - Q_INVOKABLE static bool isEnvSet(const QString& variable); -}; - -} // namespace qs::scan::env diff --git a/src/core/scriptmodel.cpp b/src/core/scriptmodel.cpp index 5407e2b..6837c4a 100644 --- a/src/core/scriptmodel.cpp +++ b/src/core/scriptmodel.cpp @@ -19,7 +19,7 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) { auto newIter = newValues.begin(); // TODO: cache this - auto getCmpKey = [this](const QVariant& v) { + auto getCmpKey = [&](const QVariant& v) { if (v.canConvert()) { auto vMap = v.value(); if (vMap.contains(this->cmpKey)) { @@ -30,7 +30,7 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) { return v; }; - auto variantCmp = [&, this](const QVariant& a, const QVariant& b) { + auto variantCmp = [&](const QVariant& a, const QVariant& b) { if (!this->cmpKey.isEmpty()) return getCmpKey(a) == getCmpKey(b); else return a == b; }; @@ -72,8 +72,8 @@ void ScriptModel::updateValuesUnique(const QVariantList& newValues) { do { ++iter; } while (iter != this->mValues.end() - && std::find_if(newIter, newValues.end(), eqPredicate(*iter)) - == newValues.end()); + && std::find_if(newIter, newValues.end(), eqPredicate(*iter)) == newValues.end() + ); auto index = static_cast(std::distance(this->mValues.begin(), iter)); auto startIndex = static_cast(std::distance(this->mValues.begin(), startIter)); diff --git a/src/core/singleton.cpp b/src/core/singleton.cpp index 15668c9..61ac992 100644 --- a/src/core/singleton.cpp +++ b/src/core/singleton.cpp @@ -51,3 +51,9 @@ void SingletonRegistry::onReload(SingletonRegistry* old) { singleton->reload(old == nullptr ? nullptr : old->registry.value(url)); } } + +void SingletonRegistry::onPostReload() { + for (auto* singleton: this->registry.values()) { + PostReloadHook::postReloadTree(singleton); + } +} diff --git a/src/core/singleton.hpp b/src/core/singleton.hpp index 200c97f..e63ab12 100644 --- a/src/core/singleton.hpp +++ b/src/core/singleton.hpp @@ -26,6 +26,7 @@ public: void registerSingleton(const QUrl& url, Singleton* singleton); void onReload(SingletonRegistry* old); + void onPostReload(); private: QHash registry; diff --git a/src/core/toolsupport.cpp b/src/core/toolsupport.cpp deleted file mode 100644 index 8aa5ac9..0000000 --- a/src/core/toolsupport.cpp +++ /dev/null @@ -1,241 +0,0 @@ -#include "toolsupport.hpp" -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "logcat.hpp" -#include "paths.hpp" -#include "scan.hpp" - -namespace qs::core { - -namespace { -QS_LOGGING_CATEGORY(logTooling, "quickshell.tooling", QtWarningMsg); -} - -bool QmlToolingSupport::updateTooling(const QDir& configRoot, QmlScanner& scanner) { - auto* vfs = QsPaths::instance()->shellVfsDir(); - - if (!vfs) { - qCCritical(logTooling) << "Tooling dir could not be created"; - return false; - } - - if (!QmlToolingSupport::lockTooling()) { - return false; - } - - if (!QmlToolingSupport::updateQmllsConfig(configRoot, false)) { - QDir(vfs->filePath("qs")).removeRecursively(); - return false; - } - - QmlToolingSupport::updateToolingFs(scanner, configRoot, vfs->filePath("qs")); - return true; -} - -bool QmlToolingSupport::lockTooling() { - if (QmlToolingSupport::toolingLock) return true; - - auto lockPath = QsPaths::instance()->shellVfsDir()->filePath("tooling.lock"); - auto* file = new QFile(lockPath); - - if (!file->open(QFile::WriteOnly)) { - qCCritical(logTooling) << "Could not open tooling lock for write"; - return false; - } - - struct flock lock = { - .l_type = F_WRLCK, - .l_whence = SEEK_SET, // NOLINT (fcntl.h??) - .l_start = 0, - .l_len = 0, - .l_pid = 0, - }; - - if (fcntl(file->handle(), F_SETLK, &lock) == 0) { - qCInfo(logTooling) << "Acquired tooling support lock"; - QmlToolingSupport::toolingLock = file; - return true; - } else if (errno == EACCES || errno == EAGAIN) { - qCInfo(logTooling) << "Tooling support locked by another instance"; - return false; - } else { - qCCritical(logTooling).nospace() << "Could not create tooling lock at " << lockPath - << " with error code " << errno << ": " << qt_error_string(); - return false; - } -} - -QString QmlToolingSupport::getQmllsConfig() { - static auto config = []() { - // We can't replicate the algorithm used to create the import path list as it can have distro - // specific patches, e.g. nixos. - auto importPaths = QQmlEngine().importPathList(); - importPaths.removeIf([](const QString& path) { return path.startsWith("qrc:"); }); - - auto vfsPath = QsPaths::instance()->shellVfsDir()->path(); - auto importPathsStr = importPaths.join(u':'); - - QString qmllsConfig; - auto print = QDebug(&qmllsConfig).nospace(); - print << "[General]\nno-cmake-calls=true\nbuildDir=" << vfsPath - << "\nimportPaths=" << importPathsStr << '\n'; - - return qmllsConfig; - }(); - - return config; -} - -bool QmlToolingSupport::updateQmllsConfig(const QDir& configRoot, bool create) { - auto shellConfigPath = configRoot.filePath(".qmlls.ini"); - auto vfsConfigPath = QsPaths::instance()->shellVfsDir()->filePath(".qmlls.ini"); - - auto shellFileInfo = QFileInfo(shellConfigPath); - if (!create && !shellFileInfo.exists() && !shellFileInfo.isSymLink()) { - if (QmlToolingSupport::toolingEnabled) { - qInfo() << "QML tooling support disabled"; - QmlToolingSupport::toolingEnabled = false; - } else { - qCInfo(logTooling) << "Not enabling QML tooling support, qmlls.ini is missing at path" - << shellConfigPath; - } - - QFile::remove(vfsConfigPath); - return false; - } - - auto vfsFile = QFile(vfsConfigPath); - - if (!vfsFile.open(QFile::ReadWrite | QFile::Text)) { - qCCritical(logTooling) << "Failed to create qmlls config in vfs"; - return false; - } - - auto config = QmlToolingSupport::getQmllsConfig(); - - if (vfsFile.readAll() != config) { - if (!vfsFile.resize(0) || !vfsFile.write(config.toUtf8())) { - qCCritical(logTooling) << "Failed to write qmlls config in vfs"; - return false; - } - - qCDebug(logTooling) << "Wrote qmlls config in vfs"; - } - - if (!shellFileInfo.isSymLink() || shellFileInfo.symLinkTarget() != vfsConfigPath) { - QFile::remove(shellConfigPath); - - if (!QFile::link(vfsConfigPath, shellConfigPath)) { - qCCritical(logTooling) << "Failed to create qmlls config symlink"; - return false; - } - - qCDebug(logTooling) << "Created qmlls config symlink"; - } - - if (!QmlToolingSupport::toolingEnabled) { - qInfo() << "QML tooling support enabled"; - QmlToolingSupport::toolingEnabled = true; - } - - return true; -} - -void QmlToolingSupport::updateToolingFs( - QmlScanner& scanner, - const QDir& scanDir, - const QDir& linkDir -) { - QList files; - QSet subdirs; - - auto scanPath = scanDir.path(); - - linkDir.mkpath("."); - - for (auto& path: scanner.scannedFiles) { - if (path.length() < scanPath.length() + 1 || !path.startsWith(scanPath)) continue; - auto name = path.sliced(scanPath.length() + 1); - - if (name.contains('/')) { - auto dirname = name.first(name.indexOf('/')); - subdirs.insert(dirname); - continue; - } - - auto fileInfo = QFileInfo(path); - if (!fileInfo.isFile()) continue; - - auto spath = linkDir.filePath(name); - auto sFileInfo = QFileInfo(spath); - - if (!sFileInfo.isSymLink() || sFileInfo.symLinkTarget() != path) { - QFile::remove(spath); - - if (QFile::link(path, spath)) { - qCDebug(logTooling) << "Created symlink to" << path << "at" << spath; - files.append(spath); - } else { - qCCritical(logTooling) << "Could not create symlink to" << path << "at" << spath; - } - } else { - files.append(spath); - } - } - - for (auto [path, text]: scanner.fileIntercepts.asKeyValueRange()) { - if (path.length() < scanPath.length() + 1 || !path.startsWith(scanPath)) continue; - auto name = path.sliced(scanPath.length() + 1); - - if (name.contains('/')) { - auto dirname = name.first(name.indexOf('/')); - subdirs.insert(dirname); - continue; - } - - auto spath = linkDir.filePath(name); - auto file = QFile(spath); - if (!file.open(QFile::ReadWrite | QFile::Text)) { - qCCritical(logTooling) << "Failed to open injected file" << spath; - continue; - } - - if (file.readAll() == text) { - files.append(spath); - continue; - } - - if (file.resize(0) && file.write(text.toUtf8())) { - files.append(spath); - qCDebug(logTooling) << "Wrote injected file" << spath; - } else { - qCCritical(logTooling) << "Failed to write injected file" << spath; - } - } - - for (auto& name: linkDir.entryList(QDir::Files | QDir::System)) { // System = broken symlinks - auto path = linkDir.filePath(name); - - if (!files.contains(path)) { - if (QFile::remove(path)) qCDebug(logTooling) << "Removed old file at" << path; - else qCWarning(logTooling) << "Failed to remove old file at" << path; - } - } - - for (const auto& subdir: subdirs) { - QmlToolingSupport::updateToolingFs(scanner, scanDir.filePath(subdir), linkDir.filePath(subdir)); - } -} - -} // namespace qs::core diff --git a/src/core/toolsupport.hpp b/src/core/toolsupport.hpp deleted file mode 100644 index 9fb7921..0000000 --- a/src/core/toolsupport.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include - -#include "scan.hpp" - -namespace qs::core { - -class QmlToolingSupport { -public: - static bool updateTooling(const QDir& configRoot, QmlScanner& scanner); - -private: - static QString getQmllsConfig(); - static bool lockTooling(); - static bool updateQmllsConfig(const QDir& configRoot, bool create); - static void updateToolingFs(QmlScanner& scanner, const QDir& scanDir, const QDir& linkDir); - static inline bool toolingEnabled = false; - static inline QFile* toolingLock = nullptr; -}; - -} // namespace qs::core diff --git a/src/core/util.hpp b/src/core/util.hpp index bb8dd85..88583d0 100644 --- a/src/core/util.hpp +++ b/src/core/util.hpp @@ -29,7 +29,7 @@ struct StringLiteral16 { } [[nodiscard]] constexpr const QChar* qCharPtr() const noexcept { - return std::bit_cast(&this->value); // NOLINT + return std::bit_cast(&this->value); } [[nodiscard]] Q_ALWAYS_INLINE operator QString() const noexcept { @@ -251,6 +251,37 @@ public: GuardedEmitBlocker block() { return GuardedEmitBlocker(&this->blocked); } }; +template +class SimpleObjectHandleOps { + using Traits = MemberPointerTraits; + +public: + static bool setObject(Traits::Class* parent, Traits::Type value) { + if (value == parent->*member) return false; + + if (parent->*member != nullptr) { + QObject::disconnect(parent->*member, &QObject::destroyed, parent, destroyedSlot); + } + + parent->*member = value; + + if (value != nullptr) { + QObject::connect(parent->*member, &QObject::destroyed, parent, destroyedSlot); + } + + if constexpr (changedSignal != nullptr) { + emit(parent->*changedSignal)(); + } + + return true; + } +}; + +template +bool setSimpleObjectHandle(auto* parent, auto* value) { + return SimpleObjectHandleOps::setObject(parent, value); +} + template class MethodFunctor { using PtrMeta = MemberPointerTraits; diff --git a/src/crash/CMakeLists.txt b/src/crash/CMakeLists.txt index a891ee9..7fdd830 100644 --- a/src/crash/CMakeLists.txt +++ b/src/crash/CMakeLists.txt @@ -6,51 +6,12 @@ qt_add_library(quickshell-crash STATIC qs_pch(quickshell-crash SET large) -if (VENDOR_CPPTRACE) - message(STATUS "Vendoring cpptrace...") - include(FetchContent) - - # For use without internet access see: https://cmake.org/cmake/help/latest/module/FetchContent.html#variable:FETCHCONTENT_SOURCE_DIR_%3CuppercaseName%3E - FetchContent_Declare( - cpptrace - GIT_REPOSITORY https://github.com/jeremy-rifkin/cpptrace.git - GIT_TAG v1.0.4 - ) - - set(CPPTRACE_UNWIND_WITH_LIBUNWIND TRUE) - FetchContent_MakeAvailable(cpptrace) -else () - find_package(cpptrace REQUIRED) - - # useful for cross after you have already checked cpptrace is built correctly - if (NOT DO_NOT_CHECK_CPPTRACE_USABILITY) - try_run(CPPTRACE_SIGNAL_SAFE_UNWIND CPPTRACE_SIGNAL_SAFE_UNWIND_COMP - SOURCE_FROM_CONTENT check.cxx " - #include - int main() { - return cpptrace::can_signal_safe_unwind() ? 0 : 1; - } - " - LOG_DESCRIPTION "Checking ${CPPTRACE_SIGNAL_SAFE_UNWIND}" - LINK_LIBRARIES cpptrace::cpptrace - COMPILE_OUTPUT_VARIABLE CPPTRACE_SIGNAL_SAFE_UNWIND_LOG - CXX_STANDARD 20 - CXX_STANDARD_REQUIRED ON - ) - - if (NOT CPPTRACE_SIGNAL_SAFE_UNWIND_COMP) - message(STATUS "${CPPTRACE_SIGNAL_SAFE_UNWIND_LOG}") - message(FATAL_ERROR "Failed to compile cpptrace signal safe unwind tester.") - endif() - - if (NOT CPPTRACE_SIGNAL_SAFE_UNWIND EQUAL 0) - message(STATUS "Cpptrace signal safe unwind test exited with: ${CPPTRACE_SIGNAL_SAFE_UNWIND}") - message(FATAL_ERROR "Cpptrace was built without CPPTRACE_UNWIND_WITH_LIBUNWIND set to true. Enable libunwind support in the package or set VENDOR_CPPTRACE to true when building Quickshell.") - endif() - endif () -endif () +find_package(PkgConfig REQUIRED) +pkg_check_modules(breakpad REQUIRED IMPORTED_TARGET breakpad) +# only need client?? take only includes from pkg config todo +target_link_libraries(quickshell-crash PRIVATE PkgConfig::breakpad -lbreakpad_client) # quick linked for pch compat -target_link_libraries(quickshell-crash PRIVATE quickshell-build Qt::Quick Qt::Widgets cpptrace::cpptrace) +target_link_libraries(quickshell-crash PRIVATE quickshell-build Qt::Quick Qt::Widgets) target_link_libraries(quickshell PRIVATE quickshell-crash) diff --git a/src/crash/handler.cpp b/src/crash/handler.cpp index c875c2e..1433a87 100644 --- a/src/crash/handler.cpp +++ b/src/crash/handler.cpp @@ -1,13 +1,12 @@ #include "handler.hpp" -#include #include -#include -#include #include #include -#include -#include +#include +#include +#include +#include #include #include #include @@ -20,71 +19,92 @@ extern char** environ; // NOLINT +using namespace google_breakpad; + namespace qs::crash { namespace { - QS_LOGGING_CATEGORY(logCrashHandler, "quickshell.crashhandler", QtWarningMsg); +} -void writeEnvInt(char* buf, const char* name, int value) { - // NOLINTBEGIN (cppcoreguidelines-pro-bounds-pointer-arithmetic) - while (*name != '\0') *buf++ = *name++; - *buf++ = '='; +struct CrashHandlerPrivate { + ExceptionHandler* exceptionHandler = nullptr; + int minidumpFd = -1; + int infoFd = -1; - if (value < 0) { - *buf++ = '-'; - value = -value; + static bool minidumpCallback(const MinidumpDescriptor& descriptor, void* context, bool succeeded); +}; + +CrashHandler::CrashHandler(): d(new CrashHandlerPrivate()) {} + +void CrashHandler::init() { + // MinidumpDescriptor has no move constructor and the copy constructor breaks fds. + auto createHandler = [this](const MinidumpDescriptor& desc) { + this->d->exceptionHandler = new ExceptionHandler( + desc, + nullptr, + &CrashHandlerPrivate::minidumpCallback, + this->d, + true, + -1 + ); + }; + + qCDebug(logCrashHandler) << "Starting crash handler..."; + + this->d->minidumpFd = memfd_create("quickshell:minidump", MFD_CLOEXEC); + + if (this->d->minidumpFd == -1) { + qCCritical(logCrashHandler + ) << "Failed to allocate minidump memfd, minidumps will be saved in the working directory."; + createHandler(MinidumpDescriptor(".")); + } else { + qCDebug(logCrashHandler) << "Created memfd" << this->d->minidumpFd + << "for holding possible minidumps."; + createHandler(MinidumpDescriptor(this->d->minidumpFd)); } - if (value == 0) { - *buf++ = '0'; - *buf = '\0'; + qCInfo(logCrashHandler) << "Crash handler initialized."; +} + +void CrashHandler::setRelaunchInfo(const RelaunchInfo& info) { + this->d->infoFd = memfd_create("quickshell:instance_info", MFD_CLOEXEC); + + if (this->d->infoFd == -1) { + qCCritical(logCrashHandler + ) << "Failed to allocate instance info memfd, crash recovery will not work."; return; } - auto* start = buf; - while (value > 0) { - *buf++ = static_cast('0' + (value % 10)); - value /= 10; - } + QFile file; + file.open(this->d->infoFd, QFile::ReadWrite); - *buf = '\0'; - std::reverse(start, buf); - // NOLINTEND + QDataStream ds(&file); + ds << info; + file.flush(); + + qCDebug(logCrashHandler) << "Stored instance info in memfd" << this->d->infoFd; } -void signalHandler( - int sig, - siginfo_t* /*info*/, // NOLINT (misc-include-cleaner) - void* /*context*/ +CrashHandler::~CrashHandler() { + delete this->d->exceptionHandler; + delete this->d; +} + +bool CrashHandlerPrivate::minidumpCallback( + const MinidumpDescriptor& /*descriptor*/, + void* context, + bool /*success*/ ) { - if (CrashInfo::INSTANCE.traceFd != -1) { - auto traceBuffer = std::array(); - auto frameCount = cpptrace::safe_generate_raw_trace(traceBuffer.data(), traceBuffer.size(), 1); - - for (size_t i = 0; i < static_cast(frameCount); i++) { - auto frame = cpptrace::safe_object_frame(); - cpptrace::get_safe_object_frame(traceBuffer[i], &frame); - - auto* wptr = reinterpret_cast(&frame); - auto* end = wptr + sizeof(cpptrace::safe_object_frame); // NOLINT - while (wptr != end) { - auto r = write(CrashInfo::INSTANCE.traceFd, &frame, sizeof(cpptrace::safe_object_frame)); - if (r < 0 && errno == EINTR) continue; - if (r <= 0) goto fail; - wptr += r; // NOLINT - } - } - - fail:; - } - + // A fork that just dies to ensure the coredump is caught by the system. auto coredumpPid = fork(); + if (coredumpPid == 0) { - raise(sig); - _exit(-1); + return false; } + auto* self = static_cast(context); + auto exe = std::array(); if (readlink("/proc/self/exe", exe.data(), exe.size() - 1) == -1) { perror("Failed to find crash reporter executable.\n"); @@ -96,19 +116,17 @@ void signalHandler( auto env = std::array(); auto envi = 0; - // dup to remove CLOEXEC - auto infoFdStr = std::array(); - writeEnvInt(infoFdStr.data(), "__QUICKSHELL_CRASH_INFO_FD", dup(CrashInfo::INSTANCE.infoFd)); + auto infoFd = dup(self->infoFd); + auto infoFdStr = std::array(); + memcpy(infoFdStr.data(), "__QUICKSHELL_CRASH_INFO_FD=-1" /*\0*/, 30); + if (infoFd != -1) my_uitos(&infoFdStr[27], infoFd, 10); env[envi++] = infoFdStr.data(); - auto corePidStr = std::array(); - writeEnvInt(corePidStr.data(), "__QUICKSHELL_CRASH_DUMP_PID", coredumpPid); + auto corePidStr = std::array(); + memcpy(corePidStr.data(), "__QUICKSHELL_CRASH_DUMP_PID=-1" /*\0*/, 31); + if (coredumpPid != -1) my_uitos(&corePidStr[28], coredumpPid, 10); env[envi++] = corePidStr.data(); - auto sigStr = std::array(); - writeEnvInt(sigStr.data(), "__QUICKSHELL_CRASH_SIGNAL", sig); - env[envi++] = sigStr.data(); - auto populateEnv = [&]() { auto senvi = 0; while (envi != 4095) { @@ -120,18 +138,30 @@ void signalHandler( env[envi] = nullptr; }; + sigset_t sigset; + sigemptyset(&sigset); // NOLINT (include) + sigprocmask(SIG_SETMASK, &sigset, nullptr); // NOLINT + auto pid = fork(); if (pid == -1) { perror("Failed to fork and launch crash reporter.\n"); - _exit(-1); + return false; } else if (pid == 0) { - // dup to remove CLOEXEC - auto dumpFdStr = std::array(); - auto logFdStr = std::array(); - writeEnvInt(dumpFdStr.data(), "__QUICKSHELL_CRASH_DUMP_FD", dup(CrashInfo::INSTANCE.traceFd)); - writeEnvInt(logFdStr.data(), "__QUICKSHELL_CRASH_LOG_FD", dup(CrashInfo::INSTANCE.logFd)); + // if already -1 will return -1 + auto dumpFd = dup(self->minidumpFd); + auto logFd = dup(CrashInfo::INSTANCE.logFd); + + // allow up to 10 digits, which should never happen + auto dumpFdStr = std::array(); + auto logFdStr = std::array(); + + memcpy(dumpFdStr.data(), "__QUICKSHELL_CRASH_DUMP_FD=-1" /*\0*/, 30); + memcpy(logFdStr.data(), "__QUICKSHELL_CRASH_LOG_FD=-1" /*\0*/, 29); + + if (dumpFd != -1) my_uitos(&dumpFdStr[27], dumpFd, 10); + if (logFd != -1) my_uitos(&logFdStr[26], logFd, 10); env[envi++] = dumpFdStr.data(); env[envi++] = logFdStr.data(); @@ -148,82 +178,8 @@ void signalHandler( perror("Failed to relaunch quickshell.\n"); _exit(-1); } -} -} // namespace - -void CrashHandler::init() { - qCDebug(logCrashHandler) << "Starting crash handler..."; - - CrashInfo::INSTANCE.traceFd = memfd_create("quickshell:trace", MFD_CLOEXEC); - - if (CrashInfo::INSTANCE.traceFd == -1) { - qCCritical(logCrashHandler) << "Failed to allocate trace memfd, stack traces will not be " - "available in crash reports."; - } else { - qCDebug(logCrashHandler) << "Created memfd" << CrashInfo::INSTANCE.traceFd - << "for holding possible stack traces."; - } - - { - // Preload anything dynamically linked to avoid malloc etc in the dynamic loader. - // See cpptrace documentation for more information. - auto buffer = std::array(); - cpptrace::safe_generate_raw_trace(buffer.data(), buffer.size()); - auto frame = cpptrace::safe_object_frame(); - cpptrace::get_safe_object_frame(buffer[0], &frame); - } - - // NOLINTBEGIN (misc-include-cleaner) - - // Set up alternate signal stack for stack overflow handling - auto ss = stack_t(); - ss.ss_sp = new char[SIGSTKSZ]; - ss.ss_size = SIGSTKSZ; - ss.ss_flags = 0; - sigaltstack(&ss, nullptr); - - // Install signal handlers - struct sigaction sa {}; - sa.sa_sigaction = &signalHandler; - sa.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESETHAND; - sigemptyset(&sa.sa_mask); - - sigaction(SIGSEGV, &sa, nullptr); - sigaction(SIGABRT, &sa, nullptr); - sigaction(SIGFPE, &sa, nullptr); - sigaction(SIGILL, &sa, nullptr); - sigaction(SIGBUS, &sa, nullptr); - sigaction(SIGTRAP, &sa, nullptr); - - // NOLINTEND (misc-include-cleaner) - - qCInfo(logCrashHandler) << "Crash handler initialized."; -} - -void CrashHandler::setRelaunchInfo(const RelaunchInfo& info) { - CrashInfo::INSTANCE.infoFd = memfd_create("quickshell:instance_info", MFD_CLOEXEC); - - if (CrashInfo::INSTANCE.infoFd == -1) { - qCCritical( - logCrashHandler - ) << "Failed to allocate instance info memfd, crash recovery will not work."; - return; - } - - QFile file; - - if (!file.open(CrashInfo::INSTANCE.infoFd, QFile::ReadWrite)) { - qCCritical( - logCrashHandler - ) << "Failed to open instance info memfd, crash recovery will not work."; - } - - QDataStream ds(&file); - ds << info; - file.flush(); - - qCDebug(logCrashHandler) << "Stored instance info in memfd" << CrashInfo::INSTANCE.infoFd; + return false; // should make sure it hits the system coredump handler } } // namespace qs::crash diff --git a/src/crash/handler.hpp b/src/crash/handler.hpp index 9488d71..2a1d86f 100644 --- a/src/crash/handler.hpp +++ b/src/crash/handler.hpp @@ -5,10 +5,19 @@ #include "../core/instanceinfo.hpp" namespace qs::crash { +struct CrashHandlerPrivate; + class CrashHandler { public: - static void init(); - static void setRelaunchInfo(const RelaunchInfo& info); + explicit CrashHandler(); + ~CrashHandler(); + Q_DISABLE_COPY_MOVE(CrashHandler); + + void init(); + void setRelaunchInfo(const RelaunchInfo& info); + +private: + CrashHandlerPrivate* d; }; } // namespace qs::crash diff --git a/src/crash/interface.cpp b/src/crash/interface.cpp index a3422d3..c633440 100644 --- a/src/crash/interface.cpp +++ b/src/crash/interface.cpp @@ -66,8 +66,7 @@ CrashReporterGui::CrashReporterGui(QString reportFolder, int pid) mainLayout->addSpacing(textHeight); if (qtVersionMatches) { - mainLayout->addWidget( - new QLabel("Please open a bug report for this issue via github or email.") + mainLayout->addWidget(new QLabel("Please open a bug report for this issue via github or email.") ); } else { mainLayout->addWidget(new QLabel( @@ -78,7 +77,7 @@ CrashReporterGui::CrashReporterGui(QString reportFolder, int pid) mainLayout->addWidget(new ReportLabel( "Github:", - "https://github.com/quickshell-mirror/quickshell/issues/new?template=crash2.yml", + "https://github.com/quickshell-mirror/quickshell/issues/new?template=crash.yml", this )); @@ -114,7 +113,7 @@ void CrashReporterGui::openFolder() { void CrashReporterGui::openReportUrl() { QDesktopServices::openUrl( - QUrl("https://github.com/outfoxxed/quickshell/issues/new?template=crash2.yml") + QUrl("https://github.com/outfoxxed/quickshell/issues/new?template=crash.yml") ); } diff --git a/src/crash/main.cpp b/src/crash/main.cpp index c406ba6..b9f0eab 100644 --- a/src/crash/main.cpp +++ b/src/crash/main.cpp @@ -1,10 +1,7 @@ #include "main.hpp" #include #include -#include -#include -#include #include #include #include @@ -16,17 +13,13 @@ #include #include #include -#include #include #include -#include #include "../core/instanceinfo.hpp" #include "../core/logcat.hpp" #include "../core/logging.hpp" -#include "../core/logging_p.hpp" #include "../core/paths.hpp" -#include "../core/ringbuf.hpp" #include "build.hpp" #include "interface.hpp" @@ -68,76 +61,6 @@ int tryDup(int fd, const QString& path) { return 0; } -QString readRecentLogs(int logFd, int maxLines, qint64 maxAgeSecs) { - QFile file; - if (!file.open(logFd, QFile::ReadOnly, QFile::AutoCloseHandle)) { - return QStringLiteral("(failed to open log fd)\n"); - } - - file.seek(0); - - qs::log::EncodedLogReader reader; - reader.setDevice(&file); - - bool readable = false; - quint8 logVersion = 0; - quint8 readerVersion = 0; - if (!reader.readHeader(&readable, &logVersion, &readerVersion) || !readable) { - return QStringLiteral("(failed to read log header)\n"); - } - - // Read all messages, keeping last maxLines in a ring buffer - auto tail = RingBuffer(maxLines); - qs::log::LogMessage message; - while (reader.read(&message)) { - tail.emplace(message); - } - - if (tail.size() == 0) { - return QStringLiteral("(no logs)\n"); - } - - // Filter to only messages within maxAgeSecs of the newest message - auto cutoff = tail.at(0).time.addSecs(-maxAgeSecs); - - QString result; - auto stream = QTextStream(&result); - for (auto i = tail.size() - 1; i != -1; i--) { - if (tail.at(i).time < cutoff) continue; - qs::log::LogMessage::formatMessage(stream, tail.at(i), false, true); - stream << '\n'; - } - - if (result.isEmpty()) { - return QStringLiteral("(no recent logs)\n"); - } - - return result; -} - -cpptrace::stacktrace resolveStacktrace(int dumpFd) { - QFile sourceFile; - if (!sourceFile.open(dumpFd, QFile::ReadOnly, QFile::AutoCloseHandle)) { - qCCritical(logCrashReporter) << "Failed to open trace memfd."; - return {}; - } - - sourceFile.seek(0); - auto data = sourceFile.readAll(); - - auto frameCount = static_cast(data.size()) / sizeof(cpptrace::safe_object_frame); - if (frameCount == 0) return {}; - - const auto* frames = reinterpret_cast(data.constData()); - - cpptrace::object_trace objectTrace; - for (size_t i = 0; i < frameCount; i++) { - objectTrace.frames.push_back(frames[i].resolve()); // NOLINT - } - - return objectTrace.resolve(); -} - void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instance) { qCDebug(logCrashReporter) << "Recording crash information at" << crashDir.path(); @@ -148,25 +71,32 @@ void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instance) { } auto crashProc = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_PID").toInt(); - auto crashSignal = qEnvironmentVariable("__QUICKSHELL_CRASH_SIGNAL").toInt(); auto dumpFd = qEnvironmentVariable("__QUICKSHELL_CRASH_DUMP_FD").toInt(); auto logFd = qEnvironmentVariable("__QUICKSHELL_CRASH_LOG_FD").toInt(); - qCDebug(logCrashReporter) << "Resolving stacktrace from fd" << dumpFd; - auto stacktrace = resolveStacktrace(dumpFd); + qCDebug(logCrashReporter) << "Saving minidump from fd" << dumpFd; + auto dumpDupStatus = tryDup(dumpFd, crashDir.filePath("minidump.dmp.log")); + if (dumpDupStatus != 0) { + qCCritical(logCrashReporter) << "Failed to write minidump:" << dumpDupStatus; + } - qCDebug(logCrashReporter) << "Reading recent log lines from fd" << logFd; - auto logDupFd = dup(logFd); - auto recentLogs = readRecentLogs(logFd, 100, 10); - - qCDebug(logCrashReporter) << "Saving log from fd" << logDupFd; - auto logDupStatus = tryDup(logDupFd, crashDir.filePath("log.qslog.log")); + qCDebug(logCrashReporter) << "Saving log from fd" << logFd; + auto logDupStatus = tryDup(logFd, crashDir.filePath("log.qslog.log")); if (logDupStatus != 0) { qCCritical(logCrashReporter) << "Failed to save log:" << logDupStatus; } + auto copyBinStatus = 0; + if (!DISTRIBUTOR_DEBUGINFO_AVAILABLE) { + qCDebug(logCrashReporter) << "Copying binary to crash folder"; + if (!QFile(QCoreApplication::applicationFilePath()).copy(crashDir.filePath("executable.txt"))) { + copyBinStatus = 1; + qCCritical(logCrashReporter) << "Failed to copy binary."; + } + } + { - auto extraInfoFile = QFile(crashDir.filePath("report.txt")); + auto extraInfoFile = QFile(crashDir.filePath("info.txt")); if (!extraInfoFile.open(QFile::WriteOnly)) { qCCritical(logCrashReporter) << "Failed to open crash info file for writing."; } else { @@ -181,12 +111,16 @@ void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instance) { stream << "\n===== Runtime Information =====\n"; stream << "Runtime Qt Version: " << qVersion() << '\n'; - stream << "Signal: " << strsignal(crashSignal) << " (" << crashSignal << ")\n"; // NOLINT stream << "Crashed process ID: " << crashProc << '\n'; stream << "Run ID: " << instance.instanceId << '\n'; stream << "Shell ID: " << instance.shellId << '\n'; stream << "Config Path: " << instance.configPath << '\n'; + stream << "\n===== Report Integrity =====\n"; + stream << "Minidump save status: " << dumpDupStatus << '\n'; + stream << "Log save status: " << logDupStatus << '\n'; + stream << "Binary copy status: " << copyBinStatus << '\n'; + stream << "\n===== System Information =====\n\n"; stream << "/etc/os-release:"; auto osReleaseFile = QFile("/etc/os-release"); @@ -206,18 +140,6 @@ void recordCrashInfo(const QDir& crashDir, const InstanceInfo& instance) { stream << "FAILED TO OPEN\n"; } - stream << "\n===== Stacktrace =====\n"; - if (stacktrace.empty()) { - stream << "(no trace available)\n"; - } else { - auto formatter = cpptrace::formatter().header(std::string()); - auto traceStr = formatter.format(stacktrace); - stream << QString::fromStdString(traceStr) << '\n'; - } - - stream << "\n===== Log Tail =====\n"; - stream << recentLogs; - extraInfoFile.close(); } } @@ -239,10 +161,7 @@ void qsCheckCrash(int argc, char** argv) { auto infoFd = qEnvironmentVariable("__QUICKSHELL_CRASH_INFO_FD").toInt(); QFile file; - if (!file.open(infoFd, QFile::ReadOnly, QFile::AutoCloseHandle)) { - qFatal() << "Failed to open instance info fd."; - } - + file.open(infoFd, QFile::ReadOnly, QFile::AutoCloseHandle); file.seek(0); auto ds = QDataStream(&file); diff --git a/src/dbus/dbusmenu/dbusmenu.cpp b/src/dbus/dbusmenu/dbusmenu.cpp index bcb354d..c0b4386 100644 --- a/src/dbus/dbusmenu/dbusmenu.cpp +++ b/src/dbus/dbusmenu/dbusmenu.cpp @@ -183,7 +183,7 @@ void DBusMenuItem::updateProperties(const QVariantMap& properties, const QString } } else if (removed.isEmpty() || removed.contains("icon-data")) { imageChanged = this->image.hasData(); - this->image.data.clear(); + image.data.clear(); } auto type = properties.value("type"); @@ -312,8 +312,8 @@ void DBusMenu::prepareToShow(qint32 item, qint32 depth) { auto responseCallback = [this, item, depth](QDBusPendingCallWatcher* call) { const QDBusPendingReply reply = *call; if (reply.isError()) { - qCDebug(logDbusMenu) << "Error in AboutToShow, but showing anyway for menu" << item << "of" - << this << reply.error(); + qCWarning(logDbusMenu) << "Error in AboutToShow, but showing anyway for menu" << item << "of" + << this << reply.error(); } this->updateLayout(item, depth); diff --git a/src/dbus/dbusmenu/dbusmenu.hpp b/src/dbus/dbusmenu/dbusmenu.hpp index 06cbc34..1192baa 100644 --- a/src/dbus/dbusmenu/dbusmenu.hpp +++ b/src/dbus/dbusmenu/dbusmenu.hpp @@ -36,7 +36,7 @@ class DBusMenuPngImage: public QsIndexedImageHandle { public: explicit DBusMenuPngImage(): QsIndexedImageHandle(QQuickImageProvider::Image) {} - [[nodiscard]] bool hasData() const { return !this->data.isEmpty(); } + [[nodiscard]] bool hasData() const { return !data.isEmpty(); } QImage requestImage(const QString& id, QSize* size, const QSize& requestedSize) override; QByteArray data; diff --git a/src/dbus/properties.cpp b/src/dbus/properties.cpp index 2c478ef..81f26d2 100644 --- a/src/dbus/properties.cpp +++ b/src/dbus/properties.cpp @@ -214,10 +214,8 @@ void DBusPropertyGroup::updatePropertySet(const QVariantMap& properties, bool co } } -void DBusPropertyGroup::tryUpdateProperty( - DBusPropertyCore* property, - const QVariant& variant -) const { +void DBusPropertyGroup::tryUpdateProperty(DBusPropertyCore* property, const QVariant& variant) + const { property->mExists = true; auto error = property->store(variant); @@ -248,13 +246,8 @@ void DBusPropertyGroup::requestPropertyUpdate(DBusPropertyCore* property) { const QDBusPendingReply reply = *call; if (reply.isError()) { - if (!property->isRequired() && reply.error().type() == QDBusError::InvalidArgs) { - qCDebug(logDbusProperties) << "Error updating non-required property" << propStr; - qCDebug(logDbusProperties) << reply.error(); - } else { - qCWarning(logDbusProperties).noquote() << "Error updating property" << propStr; - qCWarning(logDbusProperties) << reply.error(); - } + qCWarning(logDbusProperties).noquote() << "Error updating property" << propStr; + qCWarning(logDbusProperties) << reply.error(); } else { this->tryUpdateProperty(property, reply.value().variant()); } diff --git a/src/dbus/properties.hpp b/src/dbus/properties.hpp index 1596cb7..a5fce98 100644 --- a/src/dbus/properties.hpp +++ b/src/dbus/properties.hpp @@ -168,9 +168,9 @@ class DBusBindableProperty: public DBusPropertyCore { public: explicit DBusBindableProperty() { this->group()->attachProperty(this); } - [[nodiscard]] QString name() const override { return Name; } - [[nodiscard]] QStringView nameRef() const override { return Name; } - [[nodiscard]] bool isRequired() const override { return required; } + [[nodiscard]] QString name() const override { return Name; }; + [[nodiscard]] QStringView nameRef() const override { return Name; }; + [[nodiscard]] bool isRequired() const override { return required; }; [[nodiscard]] QString valueString() override { QString str; @@ -217,7 +217,7 @@ protected: private: [[nodiscard]] constexpr Owner* owner() const { - auto* self = std::bit_cast(this); // NOLINT + auto* self = std::bit_cast(this); return std::bit_cast(self - offset()); // NOLINT } diff --git a/src/debug/lint.cpp b/src/debug/lint.cpp index 5e12f76..dd65a28 100644 --- a/src/debug/lint.cpp +++ b/src/debug/lint.cpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include "../core/logcat.hpp" diff --git a/src/io/CMakeLists.txt b/src/io/CMakeLists.txt index 991beaa..8b5c20a 100644 --- a/src/io/CMakeLists.txt +++ b/src/io/CMakeLists.txt @@ -21,10 +21,9 @@ qt_add_qml_module(quickshell-io FileView.qml ) -qs_add_module_deps_light(quickshell-io Quickshell) install_qml_module(quickshell-io) -target_link_libraries(quickshell-io PRIVATE Qt::Quick quickshell-ipc) +target_link_libraries(quickshell-io PRIVATE Qt::Quick) target_link_libraries(quickshell PRIVATE quickshell-ioplugin) qs_module_pch(quickshell-io) diff --git a/src/io/datastream.hpp b/src/io/datastream.hpp index b91ec04..d83e571 100644 --- a/src/io/datastream.hpp +++ b/src/io/datastream.hpp @@ -55,7 +55,7 @@ public: // the buffer will be sent in both slots if there is data remaining from a previous parser virtual void parseBytes(QByteArray& incoming, QByteArray& buffer) = 0; - virtual void streamEnded(QByteArray& /*buffer*/) {} + virtual void streamEnded(QByteArray& /*buffer*/) {}; signals: /// Emitted when data is read from the stream. @@ -63,7 +63,7 @@ signals: }; ///! DataStreamParser for delimited data streams. -/// DataStreamParser for delimited data streams. @@DataStreamParser.read(s) is emitted once per delimited chunk of the stream. +/// DataStreamParser for delimited data streams. @@read() is emitted once per delimited chunk of the stream. class SplitParser: public DataStreamParser { Q_OBJECT; /// The delimiter for parsed data. May be multiple characters. Defaults to `\n`. diff --git a/src/io/fileview.cpp b/src/io/fileview.cpp index 04d77bd..1585f26 100644 --- a/src/io/fileview.cpp +++ b/src/io/fileview.cpp @@ -93,8 +93,7 @@ void FileViewReader::run() { FileViewReader::read(this->owner, this->state, this->doStringConversion, this->shouldCancel); if (this->shouldCancel.loadAcquire()) { - qCDebug(logFileView) << "Read" << this << "of" << this->state.path << "canceled for" - << this->owner; + qCDebug(logFileView) << "Read" << this << "of" << state.path << "canceled for" << this->owner; } } @@ -207,7 +206,7 @@ void FileViewWriter::run() { FileViewWriter::write(this->owner, this->state, this->doAtomicWrite, this->shouldCancel); if (this->shouldCancel.loadAcquire()) { - qCDebug(logFileView) << "Write" << this << "of" << this->state.path << "canceled for" + qCDebug(logFileView) << "Write" << this << "of" << state.path << "canceled for" << this->owner; } } diff --git a/src/io/ipc.cpp b/src/io/ipc.cpp index c381567..768299e 100644 --- a/src/io/ipc.cpp +++ b/src/io/ipc.cpp @@ -190,14 +190,6 @@ QString WirePropertyDefinition::toString() const { return "property " % this->name % ": " % this->type; } -QString WireSignalDefinition::toString() const { - if (this->rettype.isEmpty()) { - return "signal " % this->name % "()"; - } else { - return "signal " % this->name % "(" % this->retname % ": " % this->rettype % ')'; - } -} - QString WireTargetDefinition::toString() const { QString accum = "target " % this->name; @@ -209,10 +201,6 @@ QString WireTargetDefinition::toString() const { accum += "\n " % prop.toString(); } - for (const auto& sig: this->signalFunctions) { - accum += "\n " % sig.toString(); - } - return accum; } diff --git a/src/io/ipc.hpp b/src/io/ipc.hpp index 32486d6..d2b865a 100644 --- a/src/io/ipc.hpp +++ b/src/io/ipc.hpp @@ -146,31 +146,14 @@ struct WirePropertyDefinition { DEFINE_SIMPLE_DATASTREAM_OPS(WirePropertyDefinition, data.name, data.type); -struct WireSignalDefinition { - QString name; - QString retname; - QString rettype; - - [[nodiscard]] QString toString() const; -}; - -DEFINE_SIMPLE_DATASTREAM_OPS(WireSignalDefinition, data.name, data.retname, data.rettype); - struct WireTargetDefinition { QString name; QVector functions; QVector properties; - QVector signalFunctions; [[nodiscard]] QString toString() const; }; -DEFINE_SIMPLE_DATASTREAM_OPS( - WireTargetDefinition, - data.name, - data.functions, - data.properties, - data.signalFunctions -); +DEFINE_SIMPLE_DATASTREAM_OPS(WireTargetDefinition, data.name, data.functions, data.properties); } // namespace qs::io::ipc diff --git a/src/io/ipccomm.cpp b/src/io/ipccomm.cpp index 03b688a..7203a30 100644 --- a/src/io/ipccomm.cpp +++ b/src/io/ipccomm.cpp @@ -1,11 +1,10 @@ #include "ipccomm.hpp" -#include +#include #include #include #include #include -#include #include #include @@ -20,6 +19,10 @@ using namespace qs::ipc; namespace qs::io::ipc::comm { +struct NoCurrentGeneration: std::monostate {}; +struct TargetNotFound: std::monostate {}; +struct EntryNotFound: std::monostate {}; + using QueryResponse = std::variant< std::monostate, NoCurrentGeneration, @@ -311,106 +314,4 @@ int getProperty(IpcClient* client, const QString& target, const QString& propert return -1; } -int listenToSignal(IpcClient* client, const QString& target, const QString& signal, bool once) { - if (target.isEmpty()) { - qCCritical(logBare) << "Target required to listen for signals."; - return -1; - } else if (signal.isEmpty()) { - qCCritical(logBare) << "Signal required to listen."; - return -1; - } - - client->sendMessage(IpcCommand(SignalListenCommand {.target = target, .signal = signal})); - - while (true) { - SignalListenResponse slot; - if (!client->waitForResponse(slot)) return -1; - - if (std::holds_alternative(slot)) { - auto& result = std::get(slot); - QTextStream(stdout) << result.response << Qt::endl; - if (once) return 0; - else continue; - } else if (std::holds_alternative(slot)) { - qCCritical(logBare) << "Target not found."; - } else if (std::holds_alternative(slot)) { - qCCritical(logBare) << "Signal not found."; - } else if (std::holds_alternative(slot)) { - qCCritical(logBare) << "Not ready to accept queries yet."; - } else { - qCCritical(logIpc) << "Received invalid IPC response from" << client; - } - break; - } - - return -1; -} - -void SignalListenCommand::exec(qs::ipc::IpcServerConnection* conn) { - auto resp = conn->responseStream(); - - if (auto* generation = EngineGeneration::currentGeneration()) { - auto* registry = IpcHandlerRegistry::forGeneration(generation); - - auto* handler = registry->findHandler(this->target); - if (!handler) { - resp << TargetNotFound(); - return; - } - - auto* signal = handler->findSignal(this->signal); - if (!signal) { - resp << EntryNotFound(); - return; - } - - new RemoteSignalListener(conn, *this); - } else { - conn->respond(SignalListenResponse(NoCurrentGeneration())); - } -} - -RemoteSignalListener::RemoteSignalListener( - qs::ipc::IpcServerConnection* conn, - SignalListenCommand command -) - : conn(conn) - , command(std::move(command)) { - conn->setParent(this); - - QObject::connect( - IpcSignalRemoteListener::instance(), - &IpcSignalRemoteListener::triggered, - this, - &RemoteSignalListener::onSignal - ); - - QObject::connect( - conn, - &qs::ipc::IpcServerConnection::destroyed, - this, - &RemoteSignalListener::onConnDestroyed - ); - - qCDebug(logIpc) << "Remote listener created for" << this->command.target << this->command.signal - << ":" << this; -} - -RemoteSignalListener::~RemoteSignalListener() { - qCDebug(logIpc) << "Destroying remote listener" << this; -} - -void RemoteSignalListener::onSignal( - const QString& target, - const QString& signal, - const QString& value -) { - if (target != this->command.target || signal != this->command.signal) return; - qCDebug(logIpc) << "Remote signal" << signal << "triggered on" << target << "with value" << value; - - this->conn->respond(SignalListenResponse(SignalResponse {.response = value})); -} - -void RemoteSignalListener::onConnDestroyed() { this->deleteLater(); } - } // namespace qs::io::ipc::comm diff --git a/src/io/ipccomm.hpp b/src/io/ipccomm.hpp index ac12979..bc7dbf9 100644 --- a/src/io/ipccomm.hpp +++ b/src/io/ipccomm.hpp @@ -2,8 +2,6 @@ #include #include -#include -#include #include #include "../ipc/ipc.hpp" @@ -50,52 +48,4 @@ DEFINE_SIMPLE_DATASTREAM_OPS(StringPropReadCommand, data.target, data.property); int getProperty(qs::ipc::IpcClient* client, const QString& target, const QString& property); -struct SignalListenCommand { - QString target; - QString signal; - - void exec(qs::ipc::IpcServerConnection* conn); -}; - -DEFINE_SIMPLE_DATASTREAM_OPS(SignalListenCommand, data.target, data.signal); - -int listenToSignal( - qs::ipc::IpcClient* client, - const QString& target, - const QString& signal, - bool once -); - -struct NoCurrentGeneration: std::monostate {}; -struct TargetNotFound: std::monostate {}; -struct EntryNotFound: std::monostate {}; - -struct SignalResponse { - QString response; -}; - -DEFINE_SIMPLE_DATASTREAM_OPS(SignalResponse, data.response); - -using SignalListenResponse = std:: - variant; - -class RemoteSignalListener: public QObject { - Q_OBJECT; - -public: - explicit RemoteSignalListener(qs::ipc::IpcServerConnection* conn, SignalListenCommand command); - - ~RemoteSignalListener() override; - - Q_DISABLE_COPY_MOVE(RemoteSignalListener); - -private slots: - void onSignal(const QString& target, const QString& signal, const QString& value); - void onConnDestroyed(); - -private: - qs::ipc::IpcServerConnection* conn; - SignalListenCommand command; -}; - } // namespace qs::io::ipc::comm diff --git a/src/io/ipchandler.cpp b/src/io/ipchandler.cpp index e80cf4b..5ffa0ad 100644 --- a/src/io/ipchandler.cpp +++ b/src/io/ipchandler.cpp @@ -1,7 +1,5 @@ #include "ipchandler.hpp" #include -#include -#include #include #include @@ -141,75 +139,6 @@ WirePropertyDefinition IpcProperty::wireDef() const { return wire; } -WireSignalDefinition IpcSignal::wireDef() const { - WireSignalDefinition wire; - wire.name = this->signal.name(); - if (this->targetSlot != IpcSignalListener::SLOT_VOID) { - wire.retname = this->signal.parameterNames().value(0); - if (this->targetSlot == IpcSignalListener::SLOT_STRING) wire.rettype = "string"; - else if (this->targetSlot == IpcSignalListener::SLOT_INT) wire.rettype = "int"; - else if (this->targetSlot == IpcSignalListener::SLOT_BOOL) wire.rettype = "bool"; - else if (this->targetSlot == IpcSignalListener::SLOT_REAL) wire.rettype = "real"; - else if (this->targetSlot == IpcSignalListener::SLOT_COLOR) wire.rettype = "color"; - } - return wire; -} - -// NOLINTBEGIN (cppcoreguidelines-interfaces-global-init) -// clang-format off -const int IpcSignalListener::SLOT_VOID = IpcSignalListener::staticMetaObject.indexOfSlot("invokeVoid()"); -const int IpcSignalListener::SLOT_STRING = IpcSignalListener::staticMetaObject.indexOfSlot("invokeString(QString)"); -const int IpcSignalListener::SLOT_INT = IpcSignalListener::staticMetaObject.indexOfSlot("invokeInt(int)"); -const int IpcSignalListener::SLOT_BOOL = IpcSignalListener::staticMetaObject.indexOfSlot("invokeBool(bool)"); -const int IpcSignalListener::SLOT_REAL = IpcSignalListener::staticMetaObject.indexOfSlot("invokeReal(double)"); -const int IpcSignalListener::SLOT_COLOR = IpcSignalListener::staticMetaObject.indexOfSlot("invokeColor(QColor)"); -// clang-format on -// NOLINTEND - -bool IpcSignal::resolve(QString& error) { - if (this->signal.parameterCount() > 1) { - error = "Due to technical limitations, IPC signals can have at most one argument."; - return false; - } - - auto slot = IpcSignalListener::SLOT_VOID; - - if (this->signal.parameterCount() == 1) { - auto paramType = this->signal.parameterType(0); - if (paramType == QMetaType::QString) slot = IpcSignalListener::SLOT_STRING; - else if (paramType == QMetaType::Int) slot = IpcSignalListener::SLOT_INT; - else if (paramType == QMetaType::Bool) slot = IpcSignalListener::SLOT_BOOL; - else if (paramType == QMetaType::Double) slot = IpcSignalListener::SLOT_REAL; - else if (paramType == QMetaType::QColor) slot = IpcSignalListener::SLOT_COLOR; - else { - error = QString("Type of argument (%2: %3) cannot be used across IPC.") - .arg(this->signal.parameterNames().value(0)) - .arg(QMetaType(paramType).name()); - - return false; - } - } - - this->targetSlot = slot; - return true; -} - -void IpcSignal::connectListener(IpcHandler* handler) { - if (this->targetSlot == -1) { - qFatal() << "Tried to connect unresolved IPC signal"; - } - - this->listener = std::make_shared(this->signal.name()); - QMetaObject::connect(handler, this->signal.methodIndex(), this->listener.get(), this->targetSlot); - - QObject::connect( - this->listener.get(), - &IpcSignalListener::triggered, - handler, - &IpcHandler::onSignalTriggered - ); -} - IpcCallStorage::IpcCallStorage(const IpcFunction& function): returnSlot(function.returnType) { for (const auto& arg: function.argumentTypes) { this->argumentSlots.emplace_back(arg); @@ -243,28 +172,16 @@ void IpcHandler::onPostReload() { // which should handle inheritance on the qml side. for (auto i = smeta.methodCount(); i != meta->methodCount(); i++) { const auto& method = meta->method(i); - if (method.methodType() == QMetaMethod::Slot) { - auto ipcFunc = IpcFunction(method); - QString error; + if (method.methodType() != QMetaMethod::Slot) continue; - if (!ipcFunc.resolve(error)) { - qmlWarning(this).nospace().noquote() - << "Error parsing function \"" << method.name() << "\": " << error; - } else { - this->functionMap.insert(method.name(), ipcFunc); - } - } else if (method.methodType() == QMetaMethod::Signal) { - qmlDebug(this) << "Signal detected: " << method.name(); - auto ipcSig = IpcSignal(method); - QString error; + auto ipcFunc = IpcFunction(method); + QString error; - if (!ipcSig.resolve(error)) { - qmlWarning(this).nospace().noquote() - << "Error parsing signal \"" << method.name() << "\": " << error; - } else { - ipcSig.connectListener(this); - this->signalMap.emplace(method.name(), std::move(ipcSig)); - } + if (!ipcFunc.resolve(error)) { + qmlWarning(this).nospace().noquote() + << "Error parsing function \"" << method.name() << "\": " << error; + } else { + this->functionMap.insert(method.name(), ipcFunc); } } @@ -305,11 +222,6 @@ IpcHandlerRegistry* IpcHandlerRegistry::forGeneration(EngineGeneration* generati return dynamic_cast(ext); } -void IpcHandler::onSignalTriggered(const QString& signal, const QString& value) const { - emit IpcSignalRemoteListener::instance() - -> triggered(this->registeredState.target, signal, value); -} - void IpcHandler::updateRegistration(bool destroying) { if (!this->complete) return; @@ -412,10 +324,6 @@ WireTargetDefinition IpcHandler::wireDef() const { wire.properties += prop.wireDef(); } - for (const auto& sig: this->signalMap.values()) { - wire.signalFunctions += sig.wireDef(); - } - return wire; } @@ -460,13 +368,6 @@ IpcProperty* IpcHandler::findProperty(const QString& name) { else return &*itr; } -IpcSignal* IpcHandler::findSignal(const QString& name) { - auto itr = this->signalMap.find(name); - - if (itr == this->signalMap.end()) return nullptr; - else return &*itr; -} - IpcHandler* IpcHandlerRegistry::findHandler(const QString& target) { return this->handlers.value(target); } @@ -481,9 +382,4 @@ QVector IpcHandlerRegistry::wireTargets() const { return wire; } -IpcSignalRemoteListener* IpcSignalRemoteListener::instance() { - static auto* instance = new IpcSignalRemoteListener(); - return instance; -} - } // namespace qs::io::ipc diff --git a/src/io/ipchandler.hpp b/src/io/ipchandler.hpp index eb274e3..1da3e71 100644 --- a/src/io/ipchandler.hpp +++ b/src/io/ipchandler.hpp @@ -1,10 +1,8 @@ #pragma once #include -#include #include -#include #include #include #include @@ -69,54 +67,6 @@ public: const IpcType* type = nullptr; }; -class IpcSignalListener: public QObject { - Q_OBJECT; - -public: - IpcSignalListener(QString signal): signal(std::move(signal)) {} - - static const int SLOT_VOID; - static const int SLOT_STRING; - static const int SLOT_INT; - static const int SLOT_BOOL; - static const int SLOT_REAL; - static const int SLOT_COLOR; - -signals: - void triggered(const QString& signal, const QString& value); - -private slots: - void invokeVoid() { this->triggered(this->signal, "void"); } - void invokeString(const QString& value) { this->triggered(this->signal, value); } - void invokeInt(int value) { this->triggered(this->signal, QString::number(value)); } - void invokeBool(bool value) { this->triggered(this->signal, value ? "true" : "false"); } - void invokeReal(double value) { this->triggered(this->signal, QString::number(value)); } - void invokeColor(QColor value) { this->triggered(this->signal, value.name(QColor::HexArgb)); } - -private: - QString signal; -}; - -class IpcHandler; - -class IpcSignal { -public: - explicit IpcSignal(QMetaMethod signal): signal(signal) {} - - bool resolve(QString& error); - - [[nodiscard]] WireSignalDefinition wireDef() const; - - QMetaMethod signal; - int targetSlot = -1; - - void connectListener(IpcHandler* handler); - -private: - void connectListener(QObject* handler, IpcSignalListener* listener) const; - std::shared_ptr listener; -}; - class IpcHandlerRegistry; ///! Handler for IPC message calls. @@ -150,11 +100,6 @@ class IpcHandlerRegistry; /// - `real` will be converted to a string and returned. /// - `color` will be converted to a hex string in the form `#AARRGGBB` and returned. /// -/// #### Signals -/// IPC handler signals can be observed remotely using `qs ipc wait` (one call) -/// and `qs ipc listen` (many calls). IPC signals may have zero or one argument, where -/// the argument is one of the types listed above, or no arguments for void. -/// /// #### Example /// The following example creates ipc functions to control and retrieve the appearance /// of a Rectangle. @@ -174,18 +119,10 @@ class IpcHandlerRegistry; /// /// function setColor(color: color): void { rect.color = color; } /// function getColor(): color { return rect.color; } -/// /// function setAngle(angle: real): void { rect.rotation = angle; } /// function getAngle(): real { return rect.rotation; } -/// -/// function setRadius(radius: int): void { -/// rect.radius = radius; -/// this.radiusChanged(radius); -/// } -/// +/// function setRadius(radius: int): void { rect.radius = radius; } /// function getRadius(): int { return rect.radius; } -/// -/// signal radiusChanged(newRadius: int); /// } /// } /// ``` @@ -199,7 +136,6 @@ class IpcHandlerRegistry; /// function getAngle(): real /// function setRadius(radius: int): void /// function getRadius(): int -/// signal radiusChanged(newRadius: int) /// ``` /// /// and then invoked using `qs ipc call`. @@ -228,7 +164,7 @@ class IpcHandler: public PostReloadHook { QML_ELEMENT; public: - explicit IpcHandler(QObject* parent = nullptr): PostReloadHook(parent) {} + explicit IpcHandler(QObject* parent = nullptr): PostReloadHook(parent) {}; ~IpcHandler() override; Q_DISABLE_COPY_MOVE(IpcHandler); @@ -243,15 +179,14 @@ public: QString listMembers(qsizetype indent); [[nodiscard]] IpcFunction* findFunction(const QString& name); [[nodiscard]] IpcProperty* findProperty(const QString& name); - [[nodiscard]] IpcSignal* findSignal(const QString& name); [[nodiscard]] WireTargetDefinition wireDef() const; signals: void enabledChanged(); void targetChanged(); -public slots: - void onSignalTriggered(const QString& signal, const QString& value) const; +private slots: + //void handleIpcPropertyChange(); private: void updateRegistration(bool destroying = false); @@ -269,7 +204,6 @@ private: QHash functionMap; QHash propertyMap; - QHash signalMap; friend class IpcHandlerRegistry; }; @@ -293,14 +227,4 @@ private: QHash> knownHandlers; }; -class IpcSignalRemoteListener: public QObject { - Q_OBJECT; - -public: - static IpcSignalRemoteListener* instance(); - -signals: - void triggered(const QString& target, const QString& signal, const QString& value); -}; - } // namespace qs::io::ipc diff --git a/src/io/jsonadapter.cpp b/src/io/jsonadapter.cpp index e80c6f2..80ac091 100644 --- a/src/io/jsonadapter.cpp +++ b/src/io/jsonadapter.cpp @@ -44,7 +44,7 @@ void JsonAdapter::deserializeAdapter(const QByteArray& data) { this->deserializeRec(json.object(), this, &JsonAdapter::staticMetaObject); - for (auto* object: this->oldCreatedObjects) { + for (auto* object: oldCreatedObjects) { delete object; // FIXME: QMetaType::destroy? } @@ -56,7 +56,7 @@ void JsonAdapter::deserializeAdapter(const QByteArray& data) { void JsonAdapter::connectNotifiers() { auto notifySlot = JsonAdapter::staticMetaObject.indexOfSlot("onPropertyChanged()"); - this->connectNotifiersRec(notifySlot, this, &JsonAdapter::staticMetaObject); + connectNotifiersRec(notifySlot, this, &JsonAdapter::staticMetaObject); } void JsonAdapter::connectNotifiersRec(int notifySlot, QObject* obj, const QMetaObject* base) { @@ -71,7 +71,7 @@ void JsonAdapter::connectNotifiersRec(int notifySlot, QObject* obj, const QMetaO auto val = prop.read(obj); if (val.canView()) { auto* pobj = prop.read(obj).view(); - if (pobj) this->connectNotifiersRec(notifySlot, pobj, &JsonObject::staticMetaObject); + if (pobj) connectNotifiersRec(notifySlot, pobj, &JsonObject::staticMetaObject); } else if (val.canConvert>()) { auto listVal = val.value>(); @@ -79,7 +79,7 @@ void JsonAdapter::connectNotifiersRec(int notifySlot, QObject* obj, const QMetaO for (auto i = 0; i != len; i++) { auto* pobj = listVal.at(&listVal, i); - if (pobj) this->connectNotifiersRec(notifySlot, pobj, &JsonObject::staticMetaObject); + if (pobj) connectNotifiersRec(notifySlot, pobj, &JsonObject::staticMetaObject); } } } @@ -111,7 +111,7 @@ QJsonObject JsonAdapter::serializeRec(const QObject* obj, const QMetaObject* bas auto* pobj = val.view(); if (pobj) { - json.insert(prop.name(), this->serializeRec(pobj, &JsonObject::staticMetaObject)); + json.insert(prop.name(), serializeRec(pobj, &JsonObject::staticMetaObject)); } else { json.insert(prop.name(), QJsonValue::Null); } @@ -124,7 +124,7 @@ QJsonObject JsonAdapter::serializeRec(const QObject* obj, const QMetaObject* bas auto* pobj = listVal.at(&listVal, i); if (pobj) { - array.push_back(this->serializeRec(pobj, &JsonObject::staticMetaObject)); + array.push_back(serializeRec(pobj, &JsonObject::staticMetaObject)); } else { array.push_back(QJsonValue::Null); } @@ -178,8 +178,8 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM currentValue->setParent(this); this->createdObjects.push_back(currentValue); - } else if (this->oldCreatedObjects.removeOne(currentValue)) { - this->createdObjects.push_back(currentValue); + } else if (oldCreatedObjects.removeOne(currentValue)) { + createdObjects.push_back(currentValue); } this->deserializeRec(jval.toObject(), currentValue, &JsonObject::staticMetaObject); @@ -212,8 +212,8 @@ void JsonAdapter::deserializeRec(const QJsonObject& json, QObject* obj, const QM if (jsonValue.isObject()) { if (isNew) { currentValue = lp.at(&lp, i); - if (this->oldCreatedObjects.removeOne(currentValue)) { - this->createdObjects.push_back(currentValue); + if (oldCreatedObjects.removeOne(currentValue)) { + createdObjects.push_back(currentValue); } } else { // FIXME: should be the type inside the QQmlListProperty but how can we get that? diff --git a/src/io/jsonadapter.hpp b/src/io/jsonadapter.hpp index 276d6a7..a447c41 100644 --- a/src/io/jsonadapter.hpp +++ b/src/io/jsonadapter.hpp @@ -91,7 +91,6 @@ class JsonAdapter , public QQmlParserStatus { Q_OBJECT; QML_ELEMENT; - Q_INTERFACES(QQmlParserStatus); public: void classBegin() override {} diff --git a/src/io/process.hpp b/src/io/process.hpp index 3c55745..ab8763e 100644 --- a/src/io/process.hpp +++ b/src/io/process.hpp @@ -102,7 +102,7 @@ class Process: public PostReloadHook { /// If the process is already running changing this property will affect the next /// started process. If the property has been changed after starting a process it will /// return the new value, not the one for the currently running process. - Q_PROPERTY(QVariantHash environment READ environment WRITE setEnvironment NOTIFY environmentChanged); + Q_PROPERTY(QHash environment READ environment WRITE setEnvironment NOTIFY environmentChanged); /// If the process's environment should be cleared prior to applying @@environment. /// Defaults to false. /// diff --git a/src/io/processcore.hpp b/src/io/processcore.hpp index 8d566c9..37ec409 100644 --- a/src/io/processcore.hpp +++ b/src/io/processcore.hpp @@ -13,7 +13,7 @@ namespace qs::io::process { class ProcessContext { Q_PROPERTY(QList command MEMBER command WRITE setCommand); - Q_PROPERTY(QVariantHash environment MEMBER environment WRITE setEnvironment); + Q_PROPERTY(QHash environment MEMBER environment WRITE setEnvironment); Q_PROPERTY(bool clearEnvironment MEMBER clearEnvironment WRITE setClearEnvironment); Q_PROPERTY(QString workingDirectory MEMBER workingDirectory WRITE setWorkingDirectory); Q_PROPERTY(bool unbindStdout MEMBER unbindStdout WRITE setUnbindStdout); diff --git a/src/ipc/ipc.cpp b/src/ipc/ipc.cpp index 4bfea4c..bf66801 100644 --- a/src/ipc/ipc.cpp +++ b/src/ipc/ipc.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include #include @@ -37,8 +36,7 @@ void IpcServer::start() { auto path = run->filePath("ipc.sock"); new IpcServer(path); } else { - qCCritical( - logIpc + qCCritical(logIpc ) << "Could not start IPC server as the instance runtime path could not be created."; } } @@ -62,7 +60,6 @@ IpcServerConnection::IpcServerConnection(QLocalSocket* socket, IpcServer* server void IpcServerConnection::onDisconnected() { qCInfo(logIpc) << "IPC connection disconnected" << this; - this->deleteLater(); } void IpcServerConnection::onReadyRead() { @@ -86,11 +83,6 @@ void IpcServerConnection::onReadyRead() { ); if (!this->stream.commitTransaction()) return; - - // async connections reparent - if (dynamic_cast(this->parent()) != nullptr) { - this->deleteLater(); - } } IpcClient::IpcClient(const QString& path) { @@ -128,9 +120,7 @@ int IpcClient::connect(const QString& id, const std::functionquit(); - else QCoreApplication::exit(0); + EngineGeneration::currentGeneration()->quit(); } } // namespace qs::ipc diff --git a/src/ipc/ipccommand.hpp b/src/ipc/ipccommand.hpp index 105ce1e..b221b46 100644 --- a/src/ipc/ipccommand.hpp +++ b/src/ipc/ipccommand.hpp @@ -16,7 +16,6 @@ using IpcCommand = std::variant< IpcKillCommand, qs::io::ipc::comm::QueryMetadataCommand, qs::io::ipc::comm::StringCallCommand, - qs::io::ipc::comm::SignalListenCommand, qs::io::ipc::comm::StringPropReadCommand>; } // namespace qs::ipc diff --git a/src/launch/command.cpp b/src/launch/command.cpp index 151fc24..64eb076 100644 --- a/src/launch/command.cpp +++ b/src/launch/command.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -12,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -89,9 +89,9 @@ int locateConfigFile(CommandState& cmd, QString& path) { } if (!manifestPath.isEmpty()) { - qWarning() - << "Config manifests (manifest.conf) are deprecated and will be removed in a future " - "release."; + qWarning( + ) << "Config manifests (manifest.conf) are deprecated and will be removed in a future " + "release."; qWarning() << "Consider using symlinks to a subfolder of quickshell's XDG config dirs."; auto file = QFile(manifestPath); @@ -109,7 +109,7 @@ int locateConfigFile(CommandState& cmd, QString& path) { } if (split[0].trimmed() == *cmd.config.name) { - path = QDir(QFileInfo(file).absolutePath()).filePath(split[1].trimmed()); + path = QDir(QFileInfo(file).canonicalPath()).filePath(split[1].trimmed()); break; } } @@ -129,8 +129,7 @@ int locateConfigFile(CommandState& cmd, QString& path) { if (path.isEmpty()) { if (name == "default") { - qCCritical( - logBare + qCCritical(logBare ) << "Could not find \"default\" config directory or shell.qml in any valid config path."; } else { qCCritical(logBare) << "Could not find" << name @@ -140,7 +139,8 @@ int locateConfigFile(CommandState& cmd, QString& path) { return -1; } - goto rpath; + path = QFileInfo(path).canonicalFilePath(); + return 0; } } @@ -153,8 +153,7 @@ int locateConfigFile(CommandState& cmd, QString& path) { return -1; } -rpath: - path = QFileInfo(path).absoluteFilePath(); + path = QFileInfo(path).canonicalFilePath(); return 0; } @@ -179,8 +178,7 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb } } else if (!cmd.instance.id->isEmpty()) { path = basePath->filePath("by-pid"); - auto [liveInstances, deadInstances] = - QsPaths::collectInstances(path, cmd.config.anyDisplay ? "" : getDisplayConnection()); + auto [liveInstances, deadInstances] = QsPaths::collectInstances(path); liveInstances.removeIf([&](const InstanceLockInfo& info) { return !info.instance.instanceId.startsWith(*cmd.instance.id); @@ -230,8 +228,7 @@ int selectInstance(CommandState& cmd, InstanceLockInfo* instance, bool deadFallb path = QDir(basePath->filePath("by-path")).filePath(pathId); - auto [liveInstances, deadInstances] = - QsPaths::collectInstances(path, cmd.config.anyDisplay ? "" : getDisplayConnection()); + auto [liveInstances, deadInstances] = QsPaths::collectInstances(path); auto instances = liveInstances; if (instances.isEmpty() && deadFallback) { @@ -314,10 +311,7 @@ int listInstances(CommandState& cmd) { path = QDir(basePath->filePath("by-path")).filePath(pathId); } - auto [liveInstances, deadInstances] = QsPaths::collectInstances( - path, - cmd.config.anyDisplay || cmd.instance.all ? "" : getDisplayConnection() - ); + auto [liveInstances, deadInstances] = QsPaths::collectInstances(path); sortInstances(liveInstances, cmd.config.newest); @@ -379,7 +373,6 @@ int listInstances(CommandState& cmd) { << " Process ID: " << instance.instance.pid << '\n' << " Shell ID: " << instance.instance.shellId << '\n' << " Config path: " << instance.instance.configPath << '\n' - << " Display connection: " << instance.instance.display << '\n' << " Launch time: " << launchTimeStr << (isDead ? "" : " (running for " + runtimeStr + ")") << '\n' << (gray ? "\033[0m" : ""); @@ -411,10 +404,6 @@ int ipcCommand(CommandState& cmd) { return qs::io::ipc::comm::queryMetadata(&client, *cmd.ipc.target, *cmd.ipc.name); } else if (*cmd.ipc.getprop) { return qs::io::ipc::comm::getProperty(&client, *cmd.ipc.target, *cmd.ipc.name); - } else if (*cmd.ipc.wait) { - return qs::io::ipc::comm::listenToSignal(&client, *cmd.ipc.target, *cmd.ipc.name, true); - } else if (*cmd.ipc.listen) { - return qs::io::ipc::comm::listenToSignal(&client, *cmd.ipc.target, *cmd.ipc.name, false); } else { QVector arguments; for (auto& arg: cmd.ipc.arguments) { @@ -464,7 +453,7 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) { QTextStream(stdout) << "\033[31mCOMPATIBILITY WARNING: Quickshell was built against Qt " << QT_VERSION_STR << " but the system has updated to Qt " << qVersion() << " without rebuilding the package. This is likely to cause crashes, so " - "you must rebuild the quickshell package.\n\033[0m"; + "you must rebuild the quickshell package.\n"; return 1; } @@ -519,8 +508,8 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) { } if (state.misc.printVersion) { - qCInfo(logBare).noquote().nospace() << "quickshell " << QS_VERSION << ", revision " - << GIT_REVISION << ", distributed by: " << DISTRIBUTOR; + qCInfo(logBare).noquote().nospace() + << "quickshell 0.1.0, revision " << GIT_REVISION << ", distributed by: " << DISTRIBUTOR; if (state.log.verbosity > 1) { qCInfo(logBare).noquote() << "\nBuildtime Qt Version:" << QT_VERSION_STR; @@ -556,18 +545,4 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) { return 0; } -QString getDisplayConnection() { - auto platform = qEnvironmentVariable("QT_QPA_PLATFORM"); - auto wlDisplay = qEnvironmentVariable("WAYLAND_DISPLAY"); - auto xDisplay = qEnvironmentVariable("DISPLAY"); - - if (platform == "wayland" || (platform.isEmpty() && !wlDisplay.isEmpty())) { - return "wayland," + wlDisplay; - } else if (platform == "xcb" || (platform.isEmpty() && !xDisplay.isEmpty())) { - return "x11," + xDisplay; - } else { - return "unk," + QGuiApplication::platformName(); - } -} - } // namespace qs::launch diff --git a/src/launch/launch.cpp b/src/launch/launch.cpp index ee7ca64..91e2e24 100644 --- a/src/launch/launch.cpp +++ b/src/launch/launch.cpp @@ -27,7 +27,7 @@ #include "build.hpp" #include "launch_p.hpp" -#if CRASH_HANDLER +#if CRASH_REPORTER #include "../crash/handler.hpp" #endif @@ -73,12 +73,10 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio bool useQApplication = false; bool nativeTextRendering = false; bool desktopSettingsAware = true; - bool useSystemStyle = false; QString iconTheme = qEnvironmentVariable("QS_ICON_THEME"); QHash envOverrides; QString dataDir; QString stateDir; - QString cacheDir; } pragmas; auto stream = QTextStream(&file); @@ -90,7 +88,6 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio if (pragma == "UseQApplication") pragmas.useQApplication = true; else if (pragma == "NativeTextRendering") pragmas.nativeTextRendering = true; else if (pragma == "IgnoreSystemSettings") pragmas.desktopSettingsAware = false; - else if (pragma == "RespectSystemStyle") pragmas.useSystemStyle = true; else if (pragma.startsWith("IconTheme ")) pragmas.iconTheme = pragma.sliced(10); else if (pragma.startsWith("Env ")) { auto envPragma = pragma.sliced(4); @@ -110,8 +107,6 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio pragmas.dataDir = pragma.sliced(8).trimmed(); } else if (pragma.startsWith("StateDir ")) { pragmas.stateDir = pragma.sliced(9).trimmed(); - } else if (pragma.startsWith("CacheDir ")) { - pragmas.cacheDir = pragma.sliced(9).trimmed(); } else { qCritical() << "Unrecognized pragma" << pragma; return -1; @@ -134,15 +129,15 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio .shellId = shellId, .launchTime = qs::Common::LAUNCH_TIME, .pid = getpid(), - .display = getDisplayConnection(), }; -#if CRASH_HANDLER - crash::CrashHandler::init(); +#if CRASH_REPORTER + auto crashHandler = crash::CrashHandler(); + crashHandler.init(); { auto* log = LogManager::instance(); - crash::CrashHandler::setRelaunchInfo({ + crashHandler.setRelaunchInfo({ .instance = InstanceInfo::CURRENT, .noColor = !log->colorLogs, .timestamp = log->timestampLogs, @@ -153,18 +148,13 @@ int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplicatio } #endif - QsPaths::init(shellId, pathId, pragmas.dataDir, pragmas.stateDir, pragmas.cacheDir); + QsPaths::init(shellId, pathId, pragmas.dataDir, pragmas.stateDir); QsPaths::instance()->linkRunDir(); QsPaths::instance()->linkPathDir(); LogManager::initFs(); Common::INITIAL_ENVIRONMENT = QProcessEnvironment::systemEnvironment(); - if (!pragmas.useSystemStyle) { - qunsetenv("QT_STYLE_OVERRIDE"); - qputenv("QT_QUICK_CONTROLS_STYLE", "Fusion"); - } - for (auto [var, val]: pragmas.envOverrides.asKeyValueRange()) { qputenv(var.toUtf8(), val.toUtf8()); } diff --git a/src/launch/launch_p.hpp b/src/launch/launch_p.hpp index f666e7a..7b8fca6 100644 --- a/src/launch/launch_p.hpp +++ b/src/launch/launch_p.hpp @@ -50,7 +50,6 @@ struct CommandState { QStringOption manifest; QStringOption name; bool newest = false; - bool anyDisplay = false; } config; struct { @@ -74,8 +73,6 @@ struct CommandState { CLI::App* show = nullptr; CLI::App* call = nullptr; CLI::App* getprop = nullptr; - CLI::App* wait = nullptr; - CLI::App* listen = nullptr; bool showOld = false; QStringOption target; QStringOption name; @@ -109,8 +106,6 @@ void exitDaemon(int code); int parseCommand(int argc, char** argv, CommandState& state); int runCommand(int argc, char** argv, QCoreApplication* coreApplication); -QString getDisplayConnection(); - int launch(const LaunchArgs& args, char** argv, QCoreApplication* coreApplication); } // namespace qs::launch diff --git a/src/launch/main.cpp b/src/launch/main.cpp index a324e09..2bcbebd 100644 --- a/src/launch/main.cpp +++ b/src/launch/main.cpp @@ -16,7 +16,7 @@ #include "build.hpp" #include "launch_p.hpp" -#if CRASH_HANDLER +#if CRASH_REPORTER #include "../crash/main.hpp" #endif @@ -25,17 +25,14 @@ namespace qs::launch { namespace { void checkCrashRelaunch(char** argv, QCoreApplication* coreApplication) { -#if CRASH_HANDLER +#if CRASH_REPORTER auto lastInfoFdStr = qEnvironmentVariable("__QUICKSHELL_CRASH_INFO_FD"); if (!lastInfoFdStr.isEmpty()) { auto lastInfoFd = lastInfoFdStr.toInt(); QFile file; - if (!file.open(lastInfoFd, QFile::ReadOnly, QFile::AutoCloseHandle)) { - qFatal() << "Failed to open crash info fd. Cannot restart."; - } - + file.open(lastInfoFd, QFile::ReadOnly, QFile::AutoCloseHandle); file.seek(0); auto ds = QDataStream(&file); @@ -104,7 +101,7 @@ void exitDaemon(int code) { int main(int argc, char** argv) { QCoreApplication::setApplicationName("quickshell"); -#if CRASH_HANDLER +#if CRASH_REPORTER qsCheckCrash(argc, argv); #endif diff --git a/src/launch/parsecommand.cpp b/src/launch/parsecommand.cpp index fc43b6b..fc16086 100644 --- a/src/launch/parsecommand.cpp +++ b/src/launch/parsecommand.cpp @@ -1,7 +1,6 @@ #include #include #include -#include #include #include // NOLINT: Need to include this for impls of some CLI11 classes @@ -17,7 +16,7 @@ int parseCommand(int argc, char** argv, CommandState& state) { .argv = argv, }; - auto addConfigSelection = [&](CLI::App* cmd, bool filtering = false) { + auto addConfigSelection = [&](CLI::App* cmd, bool withNewestOption = false) { auto* group = cmd->add_option_group("Config Selection") ->description( @@ -44,23 +43,15 @@ int parseCommand(int argc, char** argv, CommandState& state) { ->excludes(path); group->add_option("-m,--manifest", state.config.manifest) - ->description( - "[DEPRECATED] Path to a quickshell manifest.\n" - "If a manifest is specified, configs named by -c will point to its entries.\n" - "Defaults to $XDG_CONFIG_HOME/quickshell/manifest.conf" - ) + ->description("[DEPRECATED] Path to a quickshell manifest.\n" + "If a manifest is specified, configs named by -c will point to its entries.\n" + "Defaults to $XDG_CONFIG_HOME/quickshell/manifest.conf") ->envname("QS_MANIFEST") ->excludes(path); - if (filtering) { + if (withNewestOption) { group->add_flag("-n,--newest", state.config.newest) ->description("Operate on the most recently launched instance instead of the oldest"); - - group->add_flag("--any-display", state.config.anyDisplay) - ->description( - "If passed, instances will not be filtered by the display connection they " - "were launched on." - ); } return group; @@ -84,11 +75,9 @@ int parseCommand(int argc, char** argv, CommandState& state) { auto* group = noGroup ? cmd : cmd->add_option_group(noDisplay ? "" : "Logging"); group->add_flag("--no-color", state.log.noColor) - ->description( - "Disables colored logging.\n" - "Colored logging can also be disabled by specifying a non empty value " - "for the NO_COLOR environment variable." - ); + ->description("Disables colored logging.\n" + "Colored logging can also be disabled by specifying a non empty value " + "for the NO_COLOR environment variable."); group->add_flag("--log-times", state.log.timestamp) ->description("Log timestamps with each message."); @@ -97,11 +86,9 @@ int parseCommand(int argc, char** argv, CommandState& state) { ->description("Log rules to apply, in the format of QT_LOGGING_RULES."); group->add_flag("-v,--verbose", [&](size_t count) { state.log.verbosity = count; }) - ->description( - "Increases log verbosity.\n" - "-v will show INFO level internal logs.\n" - "-vv will show DEBUG level internal logs." - ); + ->description("Increases log verbosity.\n" + "-v will show INFO level internal logs.\n" + "-vv will show DEBUG level internal logs."); auto* hgroup = cmd->add_option_group(""); hgroup->add_flag("--no-detailed-logs", state.log.sparse); @@ -111,11 +98,9 @@ int parseCommand(int argc, char** argv, CommandState& state) { auto* group = cmd->add_option_group("Instance Selection"); group->add_option("-i,--id", state.instance.id) - ->description( - "The instance id to operate on.\n" - "You may also use a substring the id as long as it is unique, " - "for example \"abc\" will select \"abcdefg\"." - ); + ->description("The instance id to operate on.\n" + "You may also use a substring the id as long as it is unique, " + "for example \"abc\" will select \"abcdefg\"."); group->add_option("--pid", state.instance.pid) ->description("The process id of the instance to operate on."); @@ -172,11 +157,9 @@ int parseCommand(int argc, char** argv, CommandState& state) { auto* sub = cli->add_subcommand("list", "List running quickshell instances."); auto* all = sub->add_flag("-a,--all", state.instance.all) - ->description( - "List all instances.\n" - "If unspecified, only instances of" - "the selected config will be listed." - ); + ->description("List all instances.\n" + "If unspecified, only instances of" + "the selected config will be listed."); sub->add_flag("-j,--json", state.output.json, "Output the list as a json."); @@ -227,16 +210,6 @@ int parseCommand(int argc, char** argv, CommandState& state) { ->allow_extra_args(); } - auto signalCmd = [&](std::string cmd, std::string desc) { - auto* scmd = sub->add_subcommand(std::move(cmd), std::move(desc)); - scmd->add_option("target", state.ipc.target, "The target to listen on."); - scmd->add_option("signal", state.ipc.name, "The signal to listen for."); - return scmd; - }; - - state.ipc.wait = signalCmd("wait", "Wait for one IpcHandler signal."); - state.ipc.listen = signalCmd("listen", "Listen for IpcHandler signals."); - { auto* prop = sub->add_subcommand("prop", "Manipulate IpcHandler properties.")->require_subcommand(); @@ -262,10 +235,8 @@ int parseCommand(int argc, char** argv, CommandState& state) { ->allow_extra_args(); sub->add_flag("-s,--show", state.ipc.showOld) - ->description( - "Print information about a function or target if given, or all available " - "targets if not." - ); + ->description("Print information about a function or target if given, or all available " + "targets if not."); auto* instance = addInstanceSelection(sub); addConfigSelection(sub, true)->excludes(instance); diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt deleted file mode 100644 index 6075040..0000000 --- a/src/network/CMakeLists.txt +++ /dev/null @@ -1,24 +0,0 @@ -add_subdirectory(nm) - -qt_add_library(quickshell-network STATIC - network.cpp - device.cpp - wifi.cpp -) - -target_include_directories(quickshell-network PRIVATE - ${CMAKE_CURRENT_BINARY_DIR} -) - -qt_add_qml_module(quickshell-network - URI Quickshell.Networking - VERSION 0.1 - DEPENDENCIES QtQml -) - -qs_add_module_deps_light(quickshell-network Quickshell) -install_qml_module(quickshell-network) -target_link_libraries(quickshell-network PRIVATE quickshell-network-nm Qt::Qml Qt::DBus) -qs_add_link_dependencies(quickshell-network quickshell-dbus) -target_link_libraries(quickshell PRIVATE quickshell-networkplugin) -qs_module_pch(quickshell-network SET dbus) diff --git a/src/network/device.cpp b/src/network/device.cpp deleted file mode 100644 index 22e3949..0000000 --- a/src/network/device.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include "device.hpp" - -#include -#include -#include -#include -#include -#include - -#include "../core/logcat.hpp" - -namespace qs::network { - -namespace { -QS_LOGGING_CATEGORY(logNetworkDevice, "quickshell.network.device", QtWarningMsg); -} // namespace - -QString DeviceConnectionState::toString(DeviceConnectionState::Enum state) { - switch (state) { - case Unknown: return QStringLiteral("Unknown"); - case Connecting: return QStringLiteral("Connecting"); - case Connected: return QStringLiteral("Connected"); - case Disconnecting: return QStringLiteral("Disconnecting"); - case Disconnected: return QStringLiteral("Disconnected"); - default: return QStringLiteral("Unknown"); - } -} - -QString DeviceType::toString(DeviceType::Enum type) { - switch (type) { - case None: return QStringLiteral("None"); - case Wifi: return QStringLiteral("Wifi"); - default: return QStringLiteral("Unknown"); - } -} - -QString NMDeviceState::toString(NMDeviceState::Enum state) { - switch (state) { - case Unknown: return QStringLiteral("Unknown"); - case Unmanaged: return QStringLiteral("Not managed by NetworkManager"); - case Unavailable: return QStringLiteral("Unavailable"); - case Disconnected: return QStringLiteral("Disconnected"); - case Prepare: return QStringLiteral("Preparing to connect"); - case Config: return QStringLiteral("Connecting to a network"); - case NeedAuth: return QStringLiteral("Waiting for authentication"); - case IPConfig: return QStringLiteral("Requesting IPv4 and/or IPv6 addresses from the network"); - case IPCheck: - return QStringLiteral("Checking if further action is required for the requested connection"); - case Secondaries: - return QStringLiteral("Waiting for a required secondary connection to activate"); - case Activated: return QStringLiteral("Connected"); - case Deactivating: return QStringLiteral("Disconnecting"); - case Failed: return QStringLiteral("Failed to connect"); - default: return QStringLiteral("Unknown"); - }; -} - -NetworkDevice::NetworkDevice(DeviceType::Enum type, QObject* parent): QObject(parent), mType(type) { - this->bindableConnected().setBinding([this]() { - return this->bState == DeviceConnectionState::Connected; - }); -}; - -void NetworkDevice::setAutoconnect(bool autoconnect) { - if (this->bAutoconnect == autoconnect) return; - emit this->requestSetAutoconnect(autoconnect); -} - -void NetworkDevice::disconnect() { - if (this->bState == DeviceConnectionState::Disconnected) { - qCCritical(logNetworkDevice) << "Device" << this << "is already disconnected"; - return; - } - if (this->bState == DeviceConnectionState::Disconnecting) { - qCCritical(logNetworkDevice) << "Device" << this << "is already disconnecting"; - return; - } - qCDebug(logNetworkDevice) << "Disconnecting from device" << this; - this->requestDisconnect(); -} - -} // namespace qs::network diff --git a/src/network/device.hpp b/src/network/device.hpp deleted file mode 100644 index f3807c2..0000000 --- a/src/network/device.hpp +++ /dev/null @@ -1,133 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -namespace qs::network { - -///! Connection state of a NetworkDevice. -class DeviceConnectionState: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - Unknown = 0, - Connecting = 1, - Connected = 2, - Disconnecting = 3, - Disconnected = 4, - }; - Q_ENUM(Enum); - Q_INVOKABLE static QString toString(DeviceConnectionState::Enum state); -}; - -///! Type of network device. -class DeviceType: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - None = 0, - Wifi = 1, - }; - Q_ENUM(Enum); - Q_INVOKABLE static QString toString(DeviceType::Enum type); -}; - -///! NetworkManager-specific device state. -/// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceState. -class NMDeviceState: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - Unknown = 0, - Unmanaged = 10, - Unavailable = 20, - Disconnected = 30, - Prepare = 40, - Config = 50, - NeedAuth = 60, - IPConfig = 70, - IPCheck = 80, - Secondaries = 90, - Activated = 100, - Deactivating = 110, - Failed = 120, - }; - Q_ENUM(Enum); - Q_INVOKABLE static QString toString(NMDeviceState::Enum state); -}; - -///! A network device. -/// When @@type is `Wifi`, the device is a @@WifiDevice, which can be used to scan for and connect to access points. -class NetworkDevice: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_UNCREATABLE("Devices can only be acquired through Network"); - // clang-format off - /// The device type. - Q_PROPERTY(DeviceType::Enum type READ type CONSTANT); - /// The name of the device's control interface. - Q_PROPERTY(QString name READ name NOTIFY nameChanged BINDABLE bindableName); - /// The hardware address of the device in the XX:XX:XX:XX:XX:XX format. - Q_PROPERTY(QString address READ default NOTIFY addressChanged BINDABLE bindableAddress); - /// True if the device is connected. - Q_PROPERTY(bool connected READ default NOTIFY connectedChanged BINDABLE bindableConnected); - /// Connection state of the device. - Q_PROPERTY(qs::network::DeviceConnectionState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState); - /// A more specific device state when the backend is NetworkManager. - Q_PROPERTY(qs::network::NMDeviceState::Enum nmState READ default NOTIFY nmStateChanged BINDABLE bindableNmState); - /// True if the device is allowed to autoconnect. - Q_PROPERTY(bool autoconnect READ autoconnect WRITE setAutoconnect NOTIFY autoconnectChanged); - // clang-format on - -public: - explicit NetworkDevice(DeviceType::Enum type, QObject* parent = nullptr); - - /// Disconnects the device and prevents it from automatically activating further connections. - Q_INVOKABLE void disconnect(); - - [[nodiscard]] DeviceType::Enum type() const { return this->mType; }; - QBindable bindableName() { return &this->bName; }; - [[nodiscard]] QString name() const { return this->bName; }; - QBindable bindableAddress() { return &this->bAddress; }; - QBindable bindableConnected() { return &this->bConnected; }; - QBindable bindableState() { return &this->bState; }; - QBindable bindableNmState() { return &this->bNmState; }; - [[nodiscard]] bool autoconnect() const { return this->bAutoconnect; }; - QBindable bindableAutoconnect() { return &this->bAutoconnect; }; - void setAutoconnect(bool autoconnect); - -signals: - void requestDisconnect(); - void requestSetAutoconnect(bool autoconnect); - void nameChanged(); - void addressChanged(); - void connectedChanged(); - void stateChanged(); - void nmStateChanged(); - void autoconnectChanged(); - -private: - DeviceType::Enum mType; - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, QString, bName, &NetworkDevice::nameChanged); - Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, QString, bAddress, &NetworkDevice::addressChanged); - Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, bool, bConnected, &NetworkDevice::connectedChanged); - Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, DeviceConnectionState::Enum, bState, &NetworkDevice::stateChanged); - Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, NMDeviceState::Enum, bNmState, &NetworkDevice::nmStateChanged); - Q_OBJECT_BINDABLE_PROPERTY(NetworkDevice, bool, bAutoconnect, &NetworkDevice::autoconnectChanged); - // clang-format on -}; - -} // namespace qs::network diff --git a/src/network/module.md b/src/network/module.md deleted file mode 100644 index a0c8e64..0000000 --- a/src/network/module.md +++ /dev/null @@ -1,13 +0,0 @@ -name = "Quickshell.Networking" -description = "Network API" -headers = [ - "network.hpp", - "device.hpp", - "wifi.hpp", -] ------ -This module exposes Network management APIs provided by a supported network backend. -For now, the only backend available is the NetworkManager DBus interface. -Both DBus and NetworkManager must be running to use it. - -See the @@Quickshell.Networking.Networking singleton. diff --git a/src/network/network.cpp b/src/network/network.cpp deleted file mode 100644 index e325b05..0000000 --- a/src/network/network.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "network.hpp" -#include - -#include -#include -#include -#include -#include - -#include "../core/logcat.hpp" -#include "device.hpp" -#include "nm/backend.hpp" - -namespace qs::network { - -namespace { -QS_LOGGING_CATEGORY(logNetwork, "quickshell.network", QtWarningMsg); -} // namespace - -QString NetworkState::toString(NetworkState::Enum state) { - switch (state) { - case NetworkState::Connecting: return QStringLiteral("Connecting"); - case NetworkState::Connected: return QStringLiteral("Connected"); - case NetworkState::Disconnecting: return QStringLiteral("Disconnecting"); - case NetworkState::Disconnected: return QStringLiteral("Disconnected"); - default: return QStringLiteral("Unknown"); - } -} - -Networking::Networking(QObject* parent): QObject(parent) { - // Try to create the NetworkManager backend and bind to it. - auto* nm = new NetworkManager(this); - if (nm->isAvailable()) { - QObject::connect(nm, &NetworkManager::deviceAdded, this, &Networking::deviceAdded); - QObject::connect(nm, &NetworkManager::deviceRemoved, this, &Networking::deviceRemoved); - QObject::connect(this, &Networking::requestSetWifiEnabled, nm, &NetworkManager::setWifiEnabled); - this->bindableWifiEnabled().setBinding([nm]() { return nm->wifiEnabled(); }); - this->bindableWifiHardwareEnabled().setBinding([nm]() { return nm->wifiHardwareEnabled(); }); - - this->mBackend = nm; - this->mBackendType = NetworkBackendType::NetworkManager; - return; - } else { - delete nm; - } - - qCCritical(logNetwork) << "Network will not work. Could not find an available backend."; -} - -void Networking::deviceAdded(NetworkDevice* dev) { this->mDevices.insertObject(dev); } -void Networking::deviceRemoved(NetworkDevice* dev) { this->mDevices.removeObject(dev); } - -void Networking::setWifiEnabled(bool enabled) { - if (this->bWifiEnabled == enabled) return; - emit this->requestSetWifiEnabled(enabled); -} - -Network::Network(QString name, QObject* parent): QObject(parent), mName(std::move(name)) { - this->bStateChanging.setBinding([this] { - auto state = this->bState.value(); - return state == NetworkState::Connecting || state == NetworkState::Disconnecting; - }); -}; - -} // namespace qs::network diff --git a/src/network/network.hpp b/src/network/network.hpp deleted file mode 100644 index 8af7c9d..0000000 --- a/src/network/network.hpp +++ /dev/null @@ -1,142 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "../core/model.hpp" -#include "device.hpp" - -namespace qs::network { - -///! The connection state of a Network. -class NetworkState: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - Unknown = 0, - Connecting = 1, - Connected = 2, - Disconnecting = 3, - Disconnected = 4, - }; - Q_ENUM(Enum); - Q_INVOKABLE static QString toString(NetworkState::Enum state); -}; - -///! The backend supplying the Network service. -class NetworkBackendType: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - None = 0, - NetworkManager = 1, - }; - Q_ENUM(Enum); -}; - -class NetworkBackend: public QObject { - Q_OBJECT; - -public: - [[nodiscard]] virtual bool isAvailable() const = 0; - -protected: - explicit NetworkBackend(QObject* parent = nullptr): QObject(parent) {}; -}; - -///! The Network service. -/// An interface to a network backend (currently only NetworkManager), -/// which can be used to view, configure, and connect to various networks. -class Networking: public QObject { - Q_OBJECT; - QML_SINGLETON; - QML_ELEMENT; - // clang-format off - /// A list of all network devices. - QSDOC_TYPE_OVERRIDE(ObjectModel*); - Q_PROPERTY(UntypedObjectModel* devices READ devices CONSTANT); - /// The backend being used to power the Network service. - Q_PROPERTY(qs::network::NetworkBackendType::Enum backend READ backend CONSTANT); - /// Switch for the rfkill software block of all wireless devices. - Q_PROPERTY(bool wifiEnabled READ wifiEnabled WRITE setWifiEnabled NOTIFY wifiEnabledChanged); - /// State of the rfkill hardware block of all wireless devices. - Q_PROPERTY(bool wifiHardwareEnabled READ default NOTIFY wifiHardwareEnabledChanged BINDABLE bindableWifiHardwareEnabled); - // clang-format on - -public: - explicit Networking(QObject* parent = nullptr); - - [[nodiscard]] ObjectModel* devices() { return &this->mDevices; }; - [[nodiscard]] NetworkBackendType::Enum backend() const { return this->mBackendType; }; - QBindable bindableWifiEnabled() { return &this->bWifiEnabled; }; - [[nodiscard]] bool wifiEnabled() const { return this->bWifiEnabled; }; - void setWifiEnabled(bool enabled); - QBindable bindableWifiHardwareEnabled() { return &this->bWifiHardwareEnabled; }; - -signals: - void requestSetWifiEnabled(bool enabled); - void wifiEnabledChanged(); - void wifiHardwareEnabledChanged(); - -private slots: - void deviceAdded(NetworkDevice* dev); - void deviceRemoved(NetworkDevice* dev); - -private: - ObjectModel mDevices {this}; - NetworkBackend* mBackend = nullptr; - NetworkBackendType::Enum mBackendType = NetworkBackendType::None; - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bWifiEnabled, &Networking::wifiEnabledChanged); - Q_OBJECT_BINDABLE_PROPERTY(Networking, bool, bWifiHardwareEnabled, &Networking::wifiHardwareEnabledChanged); - // clang-format on -}; - -///! A network. -class Network: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_UNCREATABLE("BaseNetwork can only be aqcuired through network devices"); - - // clang-format off - /// The name of the network. - Q_PROPERTY(QString name READ name CONSTANT); - /// True if the network is connected. - Q_PROPERTY(bool connected READ default NOTIFY connectedChanged BINDABLE bindableConnected); - /// The connectivity state of the network. - Q_PROPERTY(NetworkState::Enum state READ default NOTIFY stateChanged BINDABLE bindableState); - /// If the network is currently connecting or disconnecting. Shorthand for checking @@state. - Q_PROPERTY(bool stateChanging READ default NOTIFY stateChangingChanged BINDABLE bindableStateChanging); - // clang-format on - -public: - explicit Network(QString name, QObject* parent = nullptr); - - [[nodiscard]] QString name() const { return this->mName; }; - QBindable bindableConnected() { return &this->bConnected; } - QBindable bindableState() { return &this->bState; } - QBindable bindableStateChanging() { return &this->bStateChanging; } - -signals: - void connectedChanged(); - void stateChanged(); - void stateChangingChanged(); - -protected: - QString mName; - - Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bConnected, &Network::connectedChanged); - Q_OBJECT_BINDABLE_PROPERTY(Network, NetworkState::Enum, bState, &Network::stateChanged); - Q_OBJECT_BINDABLE_PROPERTY(Network, bool, bStateChanging, &Network::stateChangingChanged); -}; - -} // namespace qs::network diff --git a/src/network/nm/CMakeLists.txt b/src/network/nm/CMakeLists.txt deleted file mode 100644 index bb8635e..0000000 --- a/src/network/nm/CMakeLists.txt +++ /dev/null @@ -1,79 +0,0 @@ -set_source_files_properties(org.freedesktop.NetworkManager.xml PROPERTIES - CLASSNAME DBusNetworkManagerProxy - NO_NAMESPACE TRUE - INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/dbus_types.hpp -) - -qt_add_dbus_interface(NM_DBUS_INTERFACES - org.freedesktop.NetworkManager.xml - dbus_nm_backend -) - -set_source_files_properties(org.freedesktop.NetworkManager.Device.xml PROPERTIES - CLASSNAME DBusNMDeviceProxy - NO_NAMESPACE TRUE -) - -qt_add_dbus_interface(NM_DBUS_INTERFACES - org.freedesktop.NetworkManager.Device.xml - dbus_nm_device -) - -set_source_files_properties(org.freedesktop.NetworkManager.Device.Wireless.xml PROPERTIES - CLASSNAME DBusNMWirelessProxy - NO_NAMESPACE TRUE -) - -qt_add_dbus_interface(NM_DBUS_INTERFACES - org.freedesktop.NetworkManager.Device.Wireless.xml - dbus_nm_wireless -) - -set_source_files_properties(org.freedesktop.NetworkManager.AccessPoint.xml PROPERTIES - CLASSNAME DBusNMAccessPointProxy - NO_NAMESPACE TRUE -) - -qt_add_dbus_interface(NM_DBUS_INTERFACES - org.freedesktop.NetworkManager.AccessPoint.xml - dbus_nm_accesspoint -) - -set_source_files_properties(org.freedesktop.NetworkManager.Settings.Connection.xml PROPERTIES - CLASSNAME DBusNMConnectionSettingsProxy - NO_NAMESPACE TRUE - INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/dbus_types.hpp -) - -qt_add_dbus_interface(NM_DBUS_INTERFACES - org.freedesktop.NetworkManager.Settings.Connection.xml - dbus_nm_connection_settings -) - -set_source_files_properties(org.freedesktop.NetworkManager.Connection.Active.xml PROPERTIES - CLASSNAME DBusNMActiveConnectionProxy - NO_NAMESPACE TRUE -) - -qt_add_dbus_interface(NM_DBUS_INTERFACES - org.freedesktop.NetworkManager.Connection.Active.xml - dbus_nm_active_connection -) - -qt_add_library(quickshell-network-nm STATIC - backend.cpp - device.cpp - connection.cpp - accesspoint.cpp - wireless.cpp - utils.cpp - enums.hpp - ${NM_DBUS_INTERFACES} -) - -target_include_directories(quickshell-network-nm PUBLIC - ${CMAKE_CURRENT_BINARY_DIR} -) - -target_link_libraries(quickshell-network-nm PRIVATE Qt::Qml Qt::DBus) -qs_add_link_dependencies(quickshell-network-nm quickshell-dbus) diff --git a/src/network/nm/accesspoint.cpp b/src/network/nm/accesspoint.cpp deleted file mode 100644 index b6e3dfb..0000000 --- a/src/network/nm/accesspoint.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "accesspoint.hpp" - -#include -#include -#include -#include -#include -#include -#include - -#include "../../core/logcat.hpp" -#include "../../dbus/properties.hpp" -#include "dbus_nm_accesspoint.h" -#include "enums.hpp" - -namespace qs::network { -using namespace qs::dbus; - -namespace { -QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg); -} - -NMAccessPoint::NMAccessPoint(const QString& path, QObject* parent): QObject(parent) { - this->proxy = new DBusNMAccessPointProxy( - "org.freedesktop.NetworkManager", - path, - QDBusConnection::systemBus(), - this - ); - - if (!this->proxy->isValid()) { - qCWarning(logNetworkManager) << "Cannot create DBus interface for access point at" << path; - return; - } - - QObject::connect( - &this->accessPointProperties, - &DBusPropertyGroup::getAllFinished, - this, - &NMAccessPoint::loaded, - Qt::SingleShotConnection - ); - - this->accessPointProperties.setInterface(this->proxy); - this->accessPointProperties.updateAllViaGetAll(); -} - -bool NMAccessPoint::isValid() const { return this->proxy && this->proxy->isValid(); } -QString NMAccessPoint::address() const { return this->proxy ? this->proxy->service() : QString(); } -QString NMAccessPoint::path() const { return this->proxy ? this->proxy->path() : QString(); } - -} // namespace qs::network - -namespace qs::dbus { - -DBusResult -DBusDataTransform::fromWire(quint32 wire) { - return DBusResult(static_cast(wire)); -} - -DBusResult -DBusDataTransform::fromWire(quint32 wire) { - return DBusResult(static_cast(wire)); -} - -DBusResult -DBusDataTransform::fromWire(quint32 wire) { - return DBusResult(static_cast(wire)); -} - -} // namespace qs::dbus diff --git a/src/network/nm/accesspoint.hpp b/src/network/nm/accesspoint.hpp deleted file mode 100644 index 8409089..0000000 --- a/src/network/nm/accesspoint.hpp +++ /dev/null @@ -1,92 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "../../dbus/properties.hpp" -#include "../wifi.hpp" -#include "dbus_nm_accesspoint.h" -#include "enums.hpp" - -namespace qs::dbus { - -template <> -struct DBusDataTransform { - using Wire = quint32; - using Data = qs::network::NM80211ApFlags::Enum; - static DBusResult fromWire(Wire wire); -}; - -template <> -struct DBusDataTransform { - using Wire = quint32; - using Data = qs::network::NM80211ApSecurityFlags::Enum; - static DBusResult fromWire(Wire wire); -}; - -template <> -struct DBusDataTransform { - using Wire = quint32; - using Data = qs::network::NM80211Mode::Enum; - static DBusResult fromWire(Wire wire); -}; - -} // namespace qs::dbus - -namespace qs::network { - -/// Proxy of a /org/freedesktop/NetworkManager/AccessPoint/* object. -class NMAccessPoint: public QObject { - Q_OBJECT; - -public: - explicit NMAccessPoint(const QString& path, QObject* parent = nullptr); - - [[nodiscard]] bool isValid() const; - [[nodiscard]] QString path() const; - [[nodiscard]] QString address() const; - [[nodiscard]] QByteArray ssid() const { return this->bSsid; }; - [[nodiscard]] quint8 signalStrength() const { return this->bSignalStrength; }; - [[nodiscard]] NM80211ApFlags::Enum flags() const { return this->bFlags; }; - [[nodiscard]] NM80211ApSecurityFlags::Enum wpaFlags() const { return this->bWpaFlags; }; - [[nodiscard]] NM80211ApSecurityFlags::Enum rsnFlags() const { return this->bRsnFlags; }; - [[nodiscard]] NM80211Mode::Enum mode() const { return this->bMode; }; - [[nodiscard]] QBindable bindableSecurity() { return &this->bSecurity; }; - [[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; }; - -signals: - void loaded(); - void ssidChanged(const QByteArray& ssid); - void signalStrengthChanged(quint8 signal); - void flagsChanged(NM80211ApFlags::Enum flags); - void wpaFlagsChanged(NM80211ApSecurityFlags::Enum wpaFlags); - void rsnFlagsChanged(NM80211ApSecurityFlags::Enum rsnFlags); - void modeChanged(NM80211Mode::Enum mode); - void securityChanged(WifiSecurityType::Enum security); - -private: - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, QByteArray, bSsid, &NMAccessPoint::ssidChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, quint8, bSignalStrength, &NMAccessPoint::signalStrengthChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, NM80211ApFlags::Enum, bFlags, &NMAccessPoint::flagsChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, NM80211ApSecurityFlags::Enum, bWpaFlags, &NMAccessPoint::wpaFlagsChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, NM80211ApSecurityFlags::Enum, bRsnFlags, &NMAccessPoint::rsnFlagsChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, NM80211Mode::Enum, bMode, &NMAccessPoint::modeChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMAccessPoint, WifiSecurityType::Enum, bSecurity, &NMAccessPoint::securityChanged); - - QS_DBUS_BINDABLE_PROPERTY_GROUP(NMAccessPointAdapter, accessPointProperties); - QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pSsid, bSsid, accessPointProperties, "Ssid"); - QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pSignalStrength, bSignalStrength, accessPointProperties, "Strength"); - QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pFlags, bFlags, accessPointProperties, "Flags"); - QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pWpaFlags, bWpaFlags, accessPointProperties, "WpaFlags"); - QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pRsnFlags, bRsnFlags, accessPointProperties, "RsnFlags"); - QS_DBUS_PROPERTY_BINDING(NMAccessPoint, pMode, bMode, accessPointProperties, "Mode"); - // clang-format on - - DBusNMAccessPointProxy* proxy = nullptr; -}; - -} // namespace qs::network diff --git a/src/network/nm/backend.cpp b/src/network/nm/backend.cpp deleted file mode 100644 index 4b61e33..0000000 --- a/src/network/nm/backend.cpp +++ /dev/null @@ -1,270 +0,0 @@ -#include "backend.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../core/logcat.hpp" -#include "../../dbus/properties.hpp" -#include "../device.hpp" -#include "../network.hpp" -#include "../wifi.hpp" -#include "dbus_nm_backend.h" -#include "dbus_nm_device.h" -#include "dbus_types.hpp" -#include "device.hpp" -#include "enums.hpp" -#include "wireless.hpp" - -namespace qs::network { - -namespace { -QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg); -} - -NetworkManager::NetworkManager(QObject* parent): NetworkBackend(parent) { - qDBusRegisterMetaType(); - - auto bus = QDBusConnection::systemBus(); - if (!bus.isConnected()) { - qCWarning( - logNetworkManager - ) << "Could not connect to DBus. NetworkManager backend will not work."; - return; - } - - this->proxy = new DBusNetworkManagerProxy( - "org.freedesktop.NetworkManager", - "/org/freedesktop/NetworkManager", - bus, - this - ); - - if (!this->proxy->isValid()) { - qCDebug( - logNetworkManager - ) << "NetworkManager is not currently running. This network backend will not work"; - } else { - this->init(); - } -} - -void NetworkManager::init() { - // clang-format off - QObject::connect(this->proxy, &DBusNetworkManagerProxy::DeviceAdded, this, &NetworkManager::onDevicePathAdded); - QObject::connect(this->proxy, &DBusNetworkManagerProxy::DeviceRemoved, this, &NetworkManager::onDevicePathRemoved); - // clang-format on - - this->dbusProperties.setInterface(this->proxy); - this->dbusProperties.updateAllViaGetAll(); - - this->registerDevices(); -} - -void NetworkManager::registerDevices() { - auto pending = this->proxy->GetAllDevices(); - auto* call = new QDBusPendingCallWatcher(pending, this); - - auto responseCallback = [this](QDBusPendingCallWatcher* call) { - const QDBusPendingReply> reply = *call; - - if (reply.isError()) { - qCWarning(logNetworkManager) << "Failed to get devices: " << reply.error().message(); - } else { - for (const QDBusObjectPath& devicePath: reply.value()) { - this->registerDevice(devicePath.path()); - } - } - - delete call; - }; - - QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); -} - -void NetworkManager::registerDevice(const QString& path) { - if (this->mDevices.contains(path)) { - qCDebug(logNetworkManager) << "Skipping duplicate registration of device" << path; - return; - } - - auto* temp = new DBusNMDeviceProxy( - "org.freedesktop.NetworkManager", - path, - QDBusConnection::systemBus(), - this - ); - - auto callback = [this, path, temp](uint value, const QDBusError& error) { - if (error.isValid()) { - qCWarning(logNetworkManager) << "Failed to get device type:" << error; - } else { - auto type = static_cast(value); - NMDevice* dev = nullptr; - this->mDevices.insert(path, nullptr); - - switch (type) { - case NMDeviceType::Wifi: dev = new NMWirelessDevice(path); break; - default: break; - } - - if (dev) { - if (!dev->isValid()) { - qCWarning(logNetworkManager) << "Ignoring invalid registration of" << path; - delete dev; - } else { - this->mDevices[path] = dev; - // Only register a frontend device while it's managed by NM. - auto onManagedChanged = [this, dev, type](bool managed) { - managed ? this->registerFrontendDevice(type, dev) : this->removeFrontendDevice(dev); - }; - // clang-format off - QObject::connect(dev, &NMDevice::addAndActivateConnection, this, &NetworkManager::addAndActivateConnection); - QObject::connect(dev, &NMDevice::activateConnection, this, &NetworkManager::activateConnection); - QObject::connect(dev, &NMDevice::managedChanged, this, onManagedChanged); - // clang-format on - - if (dev->managed()) this->registerFrontendDevice(type, dev); - } - } - temp->deleteLater(); - } - }; - - qs::dbus::asyncReadProperty(*temp, "DeviceType", callback); -} - -void NetworkManager::registerFrontendDevice(NMDeviceType::Enum type, NMDevice* dev) { - NetworkDevice* frontendDev = nullptr; - switch (type) { - case NMDeviceType::Wifi: { - auto* frontendWifiDev = new WifiDevice(dev); - auto* wifiDev = qobject_cast(dev); - // Bind WifiDevice-specific properties - auto translateMode = [wifiDev]() { - switch (wifiDev->mode()) { - case NM80211Mode::Unknown: return WifiDeviceMode::Unknown; - case NM80211Mode::Adhoc: return WifiDeviceMode::AdHoc; - case NM80211Mode::Infra: return WifiDeviceMode::Station; - case NM80211Mode::Ap: return WifiDeviceMode::AccessPoint; - case NM80211Mode::Mesh: return WifiDeviceMode::Mesh; - } - }; - // clang-format off - frontendWifiDev->bindableMode().setBinding(translateMode); - wifiDev->bindableScanning().setBinding([frontendWifiDev]() { return frontendWifiDev->scannerEnabled(); }); - QObject::connect(wifiDev, &NMWirelessDevice::networkAdded, frontendWifiDev, &WifiDevice::networkAdded); - QObject::connect(wifiDev, &NMWirelessDevice::networkRemoved, frontendWifiDev, &WifiDevice::networkRemoved); - // clang-format on - frontendDev = frontendWifiDev; - break; - } - default: return; - } - - // Bind generic NetworkDevice properties - auto translateState = [dev]() { - switch (dev->state()) { - case 0 ... 20: return DeviceConnectionState::Unknown; - case 30: return DeviceConnectionState::Disconnected; - case 40 ... 90: return DeviceConnectionState::Connecting; - case 100: return DeviceConnectionState::Connected; - case 110 ... 120: return DeviceConnectionState::Disconnecting; - } - }; - // clang-format off - frontendDev->bindableName().setBinding([dev]() { return dev->interface(); }); - frontendDev->bindableAddress().setBinding([dev]() { return dev->hwAddress(); }); - frontendDev->bindableNmState().setBinding([dev]() { return dev->state(); }); - frontendDev->bindableState().setBinding(translateState); - frontendDev->bindableAutoconnect().setBinding([dev]() { return dev->autoconnect(); }); - QObject::connect(frontendDev, &WifiDevice::requestDisconnect, dev, &NMDevice::disconnect); - QObject::connect(frontendDev, &NetworkDevice::requestSetAutoconnect, dev, &NMDevice::setAutoconnect); - // clang-format on - - this->mFrontendDevices.insert(dev->path(), frontendDev); - emit this->deviceAdded(frontendDev); -} - -void NetworkManager::removeFrontendDevice(NMDevice* dev) { - auto* frontendDev = this->mFrontendDevices.take(dev->path()); - if (frontendDev) { - emit this->deviceRemoved(frontendDev); - frontendDev->deleteLater(); - } -} - -void NetworkManager::onDevicePathAdded(const QDBusObjectPath& path) { - this->registerDevice(path.path()); -} - -void NetworkManager::onDevicePathRemoved(const QDBusObjectPath& path) { - auto iter = this->mDevices.find(path.path()); - if (iter == this->mDevices.end()) { - qCWarning(logNetworkManager) << "Sent removal signal for" << path.path() - << "which is not registered."; - } else { - auto* dev = iter.value(); - this->mDevices.erase(iter); - if (dev) { - this->removeFrontendDevice(dev); - delete dev; - } - } -} - -void NetworkManager::activateConnection( - const QDBusObjectPath& connPath, - const QDBusObjectPath& devPath -) { - auto pending = this->proxy->ActivateConnection(connPath, devPath, QDBusObjectPath("/")); - auto* call = new QDBusPendingCallWatcher(pending, this); - - auto responseCallback = [](QDBusPendingCallWatcher* call) { - const QDBusPendingReply reply = *call; - - if (reply.isError()) { - qCWarning(logNetworkManager) << "Failed to activate connection:" << reply.error().message(); - } - delete call; - }; - QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); -} - -void NetworkManager::addAndActivateConnection( - const ConnectionSettingsMap& settings, - const QDBusObjectPath& devPath, - const QDBusObjectPath& specificObjectPath -) { - auto pending = this->proxy->AddAndActivateConnection(settings, devPath, specificObjectPath); - auto* call = new QDBusPendingCallWatcher(pending, this); - - auto responseCallback = [](QDBusPendingCallWatcher* call) { - const QDBusPendingReply reply = *call; - - if (reply.isError()) { - qCWarning(logNetworkManager) - << "Failed to add and activate connection:" << reply.error().message(); - } - delete call; - }; - QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); -} - -void NetworkManager::setWifiEnabled(bool enabled) { - if (enabled == this->bWifiEnabled) return; - this->bWifiEnabled = enabled; - this->pWifiEnabled.write(); -} - -bool NetworkManager::isAvailable() const { return this->proxy && this->proxy->isValid(); }; - -} // namespace qs::network diff --git a/src/network/nm/backend.hpp b/src/network/nm/backend.hpp deleted file mode 100644 index 471f57a..0000000 --- a/src/network/nm/backend.hpp +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "../../dbus/properties.hpp" -#include "../network.hpp" -#include "dbus_nm_backend.h" -#include "device.hpp" - -namespace qs::network { - -class NetworkManager: public NetworkBackend { - Q_OBJECT; - -public: - explicit NetworkManager(QObject* parent = nullptr); - - [[nodiscard]] bool isAvailable() const override; - [[nodiscard]] bool wifiEnabled() const { return this->bWifiEnabled; }; - [[nodiscard]] bool wifiHardwareEnabled() const { return this->bWifiHardwareEnabled; }; - -signals: - void deviceAdded(NetworkDevice* device); - void deviceRemoved(NetworkDevice* device); - void wifiEnabledChanged(bool enabled); - void wifiHardwareEnabledChanged(bool enabled); - -public slots: - void setWifiEnabled(bool enabled); - -private slots: - void onDevicePathAdded(const QDBusObjectPath& path); - void onDevicePathRemoved(const QDBusObjectPath& path); - void activateConnection(const QDBusObjectPath& connPath, const QDBusObjectPath& devPath); - void addAndActivateConnection( - const ConnectionSettingsMap& settings, - const QDBusObjectPath& devPath, - const QDBusObjectPath& specificObjectPath - ); - -private: - void init(); - void registerDevices(); - void registerDevice(const QString& path); - void registerFrontendDevice(NMDeviceType::Enum type, NMDevice* dev); - void removeFrontendDevice(NMDevice* dev); - - QHash mDevices; - QHash mFrontendDevices; - - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, bool, bWifiEnabled, &NetworkManager::wifiEnabledChanged); - Q_OBJECT_BINDABLE_PROPERTY(NetworkManager, bool, bWifiHardwareEnabled, &NetworkManager::wifiHardwareEnabledChanged); - - QS_DBUS_BINDABLE_PROPERTY_GROUP(NetworkManager, dbusProperties); - QS_DBUS_PROPERTY_BINDING(NetworkManager, pWifiEnabled, bWifiEnabled, dbusProperties, "WirelessEnabled"); - QS_DBUS_PROPERTY_BINDING(NetworkManager, pWifiHardwareEnabled, bWifiHardwareEnabled, dbusProperties, "WirelessHardwareEnabled"); - // clang-format on - DBusNetworkManagerProxy* proxy = nullptr; -}; - -} // namespace qs::network diff --git a/src/network/nm/connection.cpp b/src/network/nm/connection.cpp deleted file mode 100644 index 39b6f66..0000000 --- a/src/network/nm/connection.cpp +++ /dev/null @@ -1,151 +0,0 @@ -#include "connection.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../core/logcat.hpp" -#include "../../dbus/properties.hpp" -#include "../wifi.hpp" -#include "dbus_nm_active_connection.h" -#include "dbus_nm_connection_settings.h" -#include "dbus_types.hpp" -#include "enums.hpp" -#include "utils.hpp" - -namespace qs::network { -using namespace qs::dbus; - -namespace { -QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg); -} - -NMConnectionSettings::NMConnectionSettings(const QString& path, QObject* parent): QObject(parent) { - qDBusRegisterMetaType(); - - this->proxy = new DBusNMConnectionSettingsProxy( - "org.freedesktop.NetworkManager", - path, - QDBusConnection::systemBus(), - this - ); - - if (!this->proxy->isValid()) { - qCWarning(logNetworkManager) << "Cannot create DBus interface for connection at" << path; - return; - } - - QObject::connect( - this->proxy, - &DBusNMConnectionSettingsProxy::Updated, - this, - &NMConnectionSettings::updateSettings - ); - this->bSecurity.setBinding([this]() { return securityFromConnectionSettings(this->bSettings); }); - - this->connectionSettingsProperties.setInterface(this->proxy); - this->connectionSettingsProperties.updateAllViaGetAll(); - - this->updateSettings(); -} - -void NMConnectionSettings::updateSettings() { - auto pending = this->proxy->GetSettings(); - auto* call = new QDBusPendingCallWatcher(pending, this); - - auto responseCallback = [this](QDBusPendingCallWatcher* call) { - const QDBusPendingReply reply = *call; - - if (reply.isError()) { - qCWarning(logNetworkManager) - << "Failed to get" << this->path() << "settings:" << reply.error().message(); - } else { - this->bSettings = reply.value(); - } - - if (!this->mLoaded) { - emit this->loaded(); - this->mLoaded = true; - } - delete call; - }; - - QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); -} - -void NMConnectionSettings::forget() { - auto pending = this->proxy->Delete(); - auto* call = new QDBusPendingCallWatcher(pending, this); - - auto responseCallback = [this](QDBusPendingCallWatcher* call) { - const QDBusPendingReply<> reply = *call; - - if (reply.isError()) { - qCWarning(logNetworkManager) - << "Failed to forget" << this->path() << ":" << reply.error().message(); - } - delete call; - }; - - QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); -} - -bool NMConnectionSettings::isValid() const { return this->proxy && this->proxy->isValid(); } -QString NMConnectionSettings::address() const { - return this->proxy ? this->proxy->service() : QString(); -} -QString NMConnectionSettings::path() const { return this->proxy ? this->proxy->path() : QString(); } - -NMActiveConnection::NMActiveConnection(const QString& path, QObject* parent): QObject(parent) { - this->proxy = new DBusNMActiveConnectionProxy( - "org.freedesktop.NetworkManager", - path, - QDBusConnection::systemBus(), - this - ); - - if (!this->proxy->isValid()) { - qCWarning(logNetworkManager) << "Cannot create DBus interface for connection at" << path; - return; - } - - // clang-format off - QObject::connect(&this->activeConnectionProperties, &DBusPropertyGroup::getAllFinished, this, &NMActiveConnection::loaded, Qt::SingleShotConnection); - QObject::connect(this->proxy, &DBusNMActiveConnectionProxy::StateChanged, this, &NMActiveConnection::onStateChanged); - // clang-format on - - this->activeConnectionProperties.setInterface(this->proxy); - this->activeConnectionProperties.updateAllViaGetAll(); -} - -void NMActiveConnection::onStateChanged(quint32 /*state*/, quint32 reason) { - auto enumReason = static_cast(reason); - if (this->mStateReason == enumReason) return; - this->mStateReason = enumReason; - emit this->stateReasonChanged(enumReason); -} - -bool NMActiveConnection::isValid() const { return this->proxy && this->proxy->isValid(); } -QString NMActiveConnection::address() const { - return this->proxy ? this->proxy->service() : QString(); -} -QString NMActiveConnection::path() const { return this->proxy ? this->proxy->path() : QString(); } - -} // namespace qs::network - -namespace qs::dbus { - -DBusResult -DBusDataTransform::fromWire(quint32 wire) { - return DBusResult(static_cast(wire)); -} - -} // namespace qs::dbus diff --git a/src/network/nm/connection.hpp b/src/network/nm/connection.hpp deleted file mode 100644 index 4f126c8..0000000 --- a/src/network/nm/connection.hpp +++ /dev/null @@ -1,105 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#include "../../dbus/properties.hpp" -#include "../wifi.hpp" -#include "dbus_nm_active_connection.h" -#include "dbus_nm_connection_settings.h" -#include "dbus_types.hpp" -#include "enums.hpp" - -namespace qs::dbus { - -template <> -struct DBusDataTransform { - using Wire = quint32; - using Data = qs::network::NMConnectionState::Enum; - static DBusResult fromWire(Wire wire); -}; - -} // namespace qs::dbus - -namespace qs::network { - -// Proxy of a /org/freedesktop/NetworkManager/Settings/Connection/* object. -class NMConnectionSettings: public QObject { - Q_OBJECT; - -public: - explicit NMConnectionSettings(const QString& path, QObject* parent = nullptr); - - void forget(); - - [[nodiscard]] bool isValid() const; - [[nodiscard]] QString path() const; - [[nodiscard]] QString address() const; - [[nodiscard]] ConnectionSettingsMap settings() const { return this->bSettings; }; - [[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; }; - [[nodiscard]] QBindable bindableSecurity() { return &this->bSecurity; }; - -signals: - void loaded(); - void settingsChanged(ConnectionSettingsMap settings); - void securityChanged(WifiSecurityType::Enum security); - void ssidChanged(QString ssid); - -private: - bool mLoaded = false; - void updateSettings(); - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(NMConnectionSettings, ConnectionSettingsMap, bSettings, &NMConnectionSettings::settingsChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMConnectionSettings, WifiSecurityType::Enum, bSecurity, &NMConnectionSettings::securityChanged); - QS_DBUS_BINDABLE_PROPERTY_GROUP(NMConnectionSettings, connectionSettingsProperties); - // clang-format on - - DBusNMConnectionSettingsProxy* proxy = nullptr; -}; - -// Proxy of a /org/freedesktop/NetworkManager/ActiveConnection/* object. -class NMActiveConnection: public QObject { - Q_OBJECT; - -public: - explicit NMActiveConnection(const QString& path, QObject* parent = nullptr); - - [[nodiscard]] bool isValid() const; - [[nodiscard]] QString path() const; - [[nodiscard]] QString address() const; - [[nodiscard]] QDBusObjectPath connection() const { return this->bConnection; }; - [[nodiscard]] NMConnectionState::Enum state() const { return this->bState; }; - [[nodiscard]] NMConnectionStateReason::Enum stateReason() const { return this->mStateReason; }; - -signals: - void loaded(); - void connectionChanged(QDBusObjectPath path); - void stateChanged(NMConnectionState::Enum state); - void stateReasonChanged(NMConnectionStateReason::Enum reason); - void uuidChanged(const QString& uuid); - -private slots: - void onStateChanged(quint32 state, quint32 reason); - -private: - NMConnectionStateReason::Enum mStateReason = NMConnectionStateReason::Unknown; - - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(NMActiveConnection, QDBusObjectPath, bConnection, &NMActiveConnection::connectionChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMActiveConnection, QString, bUuid, &NMActiveConnection::uuidChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMActiveConnection, NMConnectionState::Enum, bState, &NMActiveConnection::stateChanged); - - QS_DBUS_BINDABLE_PROPERTY_GROUP(NMActiveConnection, activeConnectionProperties); - QS_DBUS_PROPERTY_BINDING(NMActiveConnection, pConnection, bConnection, activeConnectionProperties, "Connection"); - QS_DBUS_PROPERTY_BINDING(NMActiveConnection, pUuid, bUuid, activeConnectionProperties, "Uuid"); - QS_DBUS_PROPERTY_BINDING(NMActiveConnection, pState, bState, activeConnectionProperties, "State"); - // clang-format on - DBusNMActiveConnectionProxy* proxy = nullptr; -}; - -} // namespace qs::network diff --git a/src/network/nm/dbus_types.hpp b/src/network/nm/dbus_types.hpp deleted file mode 100644 index dadbcf3..0000000 --- a/src/network/nm/dbus_types.hpp +++ /dev/null @@ -1,9 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -using ConnectionSettingsMap = QMap; -Q_DECLARE_METATYPE(ConnectionSettingsMap); diff --git a/src/network/nm/device.cpp b/src/network/nm/device.cpp deleted file mode 100644 index aad565d..0000000 --- a/src/network/nm/device.cpp +++ /dev/null @@ -1,143 +0,0 @@ -#include "device.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../core/logcat.hpp" -#include "../../dbus/properties.hpp" -#include "../device.hpp" -#include "connection.hpp" -#include "dbus_nm_device.h" - -namespace qs::network { -using namespace qs::dbus; - -namespace { -QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg); -} - -NMDevice::NMDevice(const QString& path, QObject* parent): QObject(parent) { - this->deviceProxy = new DBusNMDeviceProxy( - "org.freedesktop.NetworkManager", - path, - QDBusConnection::systemBus(), - this - ); - - if (!this->deviceProxy->isValid()) { - qCWarning(logNetworkManager) << "Cannot create DBus interface for device at" << path; - return; - } - - // clang-format off - QObject::connect(this, &NMDevice::availableConnectionPathsChanged, this, &NMDevice::onAvailableConnectionPathsChanged); - QObject::connect(this, &NMDevice::activeConnectionPathChanged, this, &NMDevice::onActiveConnectionPathChanged); - // clang-format on - - this->deviceProperties.setInterface(this->deviceProxy); - this->deviceProperties.updateAllViaGetAll(); -} - -void NMDevice::onActiveConnectionPathChanged(const QDBusObjectPath& path) { - const QString stringPath = path.path(); - - // Remove old active connection - if (this->mActiveConnection) { - QObject::disconnect(this->mActiveConnection, nullptr, this, nullptr); - delete this->mActiveConnection; - this->mActiveConnection = nullptr; - } - - // Create new active connection - if (stringPath != "/") { - auto* active = new NMActiveConnection(stringPath, this); - if (!active->isValid()) { - qCWarning(logNetworkManager) << "Ignoring invalid registration of" << stringPath; - delete active; - } else { - this->mActiveConnection = active; - QObject::connect( - active, - &NMActiveConnection::loaded, - this, - [this, active]() { emit this->activeConnectionLoaded(active); }, - Qt::SingleShotConnection - ); - } - } -} - -void NMDevice::onAvailableConnectionPathsChanged(const QList& paths) { - QSet newPathSet; - for (const QDBusObjectPath& path: paths) { - newPathSet.insert(path.path()); - } - const auto existingPaths = this->mConnections.keys(); - const QSet existingPathSet(existingPaths.begin(), existingPaths.end()); - - const auto addedConnections = newPathSet - existingPathSet; - const auto removedConnections = existingPathSet - newPathSet; - - for (const QString& path: addedConnections) { - this->registerConnection(path); - } - for (const QString& path: removedConnections) { - auto* connection = this->mConnections.take(path); - if (!connection) { - qCDebug(logNetworkManager) << "Sent removal signal for" << path << "which is not registered."; - } else { - delete connection; - } - }; -} - -void NMDevice::registerConnection(const QString& path) { - auto* connection = new NMConnectionSettings(path, this); - if (!connection->isValid()) { - qCWarning(logNetworkManager) << "Ignoring invalid registration of" << path; - delete connection; - } else { - this->mConnections.insert(path, connection); - QObject::connect( - connection, - &NMConnectionSettings::loaded, - this, - [this, connection]() { emit this->connectionLoaded(connection); }, - Qt::SingleShotConnection - ); - } -} - -void NMDevice::disconnect() { this->deviceProxy->Disconnect(); } - -void NMDevice::setAutoconnect(bool autoconnect) { - if (autoconnect == this->bAutoconnect) return; - this->bAutoconnect = autoconnect; - this->pAutoconnect.write(); -} - -bool NMDevice::isValid() const { return this->deviceProxy && this->deviceProxy->isValid(); } -QString NMDevice::address() const { - return this->deviceProxy ? this->deviceProxy->service() : QString(); -} -QString NMDevice::path() const { return this->deviceProxy ? this->deviceProxy->path() : QString(); } - -} // namespace qs::network - -namespace qs::dbus { - -DBusResult -DBusDataTransform::fromWire(quint32 wire) { - return DBusResult(static_cast(wire)); -} - -} // namespace qs::dbus diff --git a/src/network/nm/device.hpp b/src/network/nm/device.hpp deleted file mode 100644 index e3ff4b9..0000000 --- a/src/network/nm/device.hpp +++ /dev/null @@ -1,100 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "../../dbus/properties.hpp" -#include "connection.hpp" -#include "dbus_nm_device.h" - -namespace qs::dbus { - -template <> -struct DBusDataTransform { - using Wire = quint32; - using Data = qs::network::NMDeviceState::Enum; - static DBusResult fromWire(Wire wire); -}; - -} // namespace qs::dbus - -namespace qs::network { - -// Proxy of a /org/freedesktop/NetworkManager/Device/* object. -// Only the members from the org.freedesktop.NetworkManager.Device interface. -// Owns the lifetime of NMActiveConnection(s) and NMConnectionSetting(s). -class NMDevice: public QObject { - Q_OBJECT; - -public: - explicit NMDevice(const QString& path, QObject* parent = nullptr); - - [[nodiscard]] virtual bool isValid() const; - [[nodiscard]] QString path() const; - [[nodiscard]] QString address() const; - [[nodiscard]] QString interface() const { return this->bInterface; }; - [[nodiscard]] QString hwAddress() const { return this->bHwAddress; }; - [[nodiscard]] bool managed() const { return this->bManaged; }; - [[nodiscard]] NMDeviceState::Enum state() const { return this->bState; }; - [[nodiscard]] bool autoconnect() const { return this->bAutoconnect; }; - [[nodiscard]] NMActiveConnection* activeConnection() const { return this->mActiveConnection; }; - -signals: - void activateConnection(const QDBusObjectPath& connPath, const QDBusObjectPath& devPath); - void addAndActivateConnection( - const ConnectionSettingsMap& settings, - const QDBusObjectPath& devPath, - const QDBusObjectPath& apPath - ); - void connectionLoaded(NMConnectionSettings* connection); - void connectionRemoved(NMConnectionSettings* connection); - void availableConnectionPathsChanged(QList paths); - void activeConnectionPathChanged(const QDBusObjectPath& connection); - void activeConnectionLoaded(NMActiveConnection* active); - void interfaceChanged(const QString& interface); - void hwAddressChanged(const QString& hwAddress); - void managedChanged(bool managed); - void stateChanged(NMDeviceState::Enum state); - void autoconnectChanged(bool autoconnect); - -public slots: - void disconnect(); - void setAutoconnect(bool autoconnect); - -private slots: - void onAvailableConnectionPathsChanged(const QList& paths); - void onActiveConnectionPathChanged(const QDBusObjectPath& path); - -private: - void registerConnection(const QString& path); - - QHash mConnections; - NMActiveConnection* mActiveConnection = nullptr; - - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QString, bInterface, &NMDevice::interfaceChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QString, bHwAddress, &NMDevice::hwAddressChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMDevice, bool, bManaged, &NMDevice::managedChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMDevice, NMDeviceState::Enum, bState, &NMDevice::stateChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMDevice, bool, bAutoconnect, &NMDevice::autoconnectChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QList, bAvailableConnections, &NMDevice::availableConnectionPathsChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMDevice, QDBusObjectPath, bActiveConnection, &NMDevice::activeConnectionPathChanged); - - QS_DBUS_BINDABLE_PROPERTY_GROUP(NMDeviceAdapter, deviceProperties); - QS_DBUS_PROPERTY_BINDING(NMDevice, pName, bInterface, deviceProperties, "Interface"); - QS_DBUS_PROPERTY_BINDING(NMDevice, pAddress, bHwAddress, deviceProperties, "HwAddress"); - QS_DBUS_PROPERTY_BINDING(NMDevice, pManaged, bManaged, deviceProperties, "Managed"); - QS_DBUS_PROPERTY_BINDING(NMDevice, pState, bState, deviceProperties, "State"); - QS_DBUS_PROPERTY_BINDING(NMDevice, pAutoconnect, bAutoconnect, deviceProperties, "Autoconnect"); - QS_DBUS_PROPERTY_BINDING(NMDevice, pAvailableConnections, bAvailableConnections, deviceProperties, "AvailableConnections"); - QS_DBUS_PROPERTY_BINDING(NMDevice, pActiveConnection, bActiveConnection, deviceProperties, "ActiveConnection"); - // clang-format on - - DBusNMDeviceProxy* deviceProxy = nullptr; -}; - -} // namespace qs::network diff --git a/src/network/nm/enums.hpp b/src/network/nm/enums.hpp deleted file mode 100644 index 34e5b65..0000000 --- a/src/network/nm/enums.hpp +++ /dev/null @@ -1,156 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace qs::network { - -// Indicates the type of hardware represented by a device object. -class NMDeviceType: public QObject { - Q_OBJECT; - -public: - enum Enum : quint8 { - Unknown = 0, - Ethernet = 1, - Wifi = 2, - Unused1 = 3, - Unused2 = 4, - Bluetooth = 5, - OlpcMesh = 6, - Wimax = 7, - Modem = 8, - InfiniBand = 9, - Bond = 10, - Vlan = 11, - Adsl = 12, - Bridge = 13, - Generic = 14, - Team = 15, - Tun = 16, - IpTunnel = 17, - MacVlan = 18, - VxLan = 19, - Veth = 20, - MacSec = 21, - Dummy = 22, - Ppp = 23, - OvsInterface = 24, - OvsPort = 25, - OvsBridge = 26, - Wpan = 27, - Lowpan = 28, - Wireguard = 29, - WifiP2P = 30, - Vrf = 31, - Loopback = 32, - Hsr = 33, - IpVlan = 34, - }; - Q_ENUM(Enum); -}; - -// 802.11 specific device encryption and authentication capabilities. -// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMDeviceWifiCapabilities. -class NMWirelessCapabilities: public QObject { - Q_OBJECT; - -public: - enum Enum : quint16 { - None = 0, - CipherWep40 = 1, - CipherWep104 = 2, - CipherTkip = 4, - CipherCcmp = 8, - Wpa = 16, - Rsn = 32, - Ap = 64, - Adhoc = 128, - FreqValid = 256, - Freq2Ghz = 512, - Freq5Ghz = 1024, - Freq6Ghz = 2048, - Mesh = 4096, - IbssRsn = 8192, - }; - Q_ENUM(Enum); -}; - -// Indicates the 802.11 mode an access point is currently in. -// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NM80211Mode. -class NM80211Mode: public QObject { - Q_OBJECT; - -public: - enum Enum : quint8 { - Unknown = 0, - Adhoc = 1, - Infra = 2, - Ap = 3, - Mesh = 4, - }; - Q_ENUM(Enum); -}; - -// 802.11 access point flags. -// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NM80211ApSecurityFlags. -class NM80211ApFlags: public QObject { - Q_OBJECT; - -public: - enum Enum : quint8 { - None = 0, - Privacy = 1, - Wps = 2, - WpsPbc = 4, - WpsPin = 8, - }; - Q_ENUM(Enum); -}; - -// 802.11 access point security and authentication flags. -// These flags describe the current system requirements of an access point as determined from the access point's beacon. -// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NM80211ApSecurityFlags. -class NM80211ApSecurityFlags: public QObject { - Q_OBJECT; - -public: - enum Enum : quint16 { - None = 0, - PairWep40 = 1, - PairWep104 = 2, - PairTkip = 4, - PairCcmp = 8, - GroupWep40 = 16, - GroupWep104 = 32, - GroupTkip = 64, - GroupCcmp = 128, - KeyMgmtPsk = 256, - KeyMgmt8021x = 512, - KeyMgmtSae = 1024, - KeyMgmtOwe = 2048, - KeyMgmtOweTm = 4096, - KeyMgmtEapSuiteB192 = 8192, - }; - Q_ENUM(Enum); -}; - -// Indicates the state of a connection to a specific network while it is starting, connected, or disconnected from that network. -// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMActiveConnectionState. -class NMConnectionState: public QObject { - Q_OBJECT; - -public: - enum Enum : quint8 { - Unknown = 0, - Activating = 1, - Activated = 2, - Deactivating = 3, - Deactivated = 4 - }; - Q_ENUM(Enum); -}; - -} // namespace qs::network diff --git a/src/network/nm/org.freedesktop.NetworkManager.AccessPoint.xml b/src/network/nm/org.freedesktop.NetworkManager.AccessPoint.xml deleted file mode 100644 index c5e7737..0000000 --- a/src/network/nm/org.freedesktop.NetworkManager.AccessPoint.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/network/nm/org.freedesktop.NetworkManager.Connection.Active.xml b/src/network/nm/org.freedesktop.NetworkManager.Connection.Active.xml deleted file mode 100644 index fa0e778..0000000 --- a/src/network/nm/org.freedesktop.NetworkManager.Connection.Active.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/network/nm/org.freedesktop.NetworkManager.Device.Wireless.xml b/src/network/nm/org.freedesktop.NetworkManager.Device.Wireless.xml deleted file mode 100644 index ccfe333..0000000 --- a/src/network/nm/org.freedesktop.NetworkManager.Device.Wireless.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/network/nm/org.freedesktop.NetworkManager.Device.xml b/src/network/nm/org.freedesktop.NetworkManager.Device.xml deleted file mode 100644 index 322635f..0000000 --- a/src/network/nm/org.freedesktop.NetworkManager.Device.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/network/nm/org.freedesktop.NetworkManager.Settings.Connection.xml b/src/network/nm/org.freedesktop.NetworkManager.Settings.Connection.xml deleted file mode 100644 index 0283847..0000000 --- a/src/network/nm/org.freedesktop.NetworkManager.Settings.Connection.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/network/nm/org.freedesktop.NetworkManager.xml b/src/network/nm/org.freedesktop.NetworkManager.xml deleted file mode 100644 index d4470ea..0000000 --- a/src/network/nm/org.freedesktop.NetworkManager.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/network/nm/utils.cpp b/src/network/nm/utils.cpp deleted file mode 100644 index 0be29e5..0000000 --- a/src/network/nm/utils.cpp +++ /dev/null @@ -1,248 +0,0 @@ -#include "utils.hpp" - -// We depend on non-std Linux extensions that ctime doesn't put in the global namespace -// NOLINTNEXTLINE(modernize-deprecated-headers) -#include - -#include -#include -#include -#include -#include -#include - -#include "../wifi.hpp" -#include "dbus_types.hpp" -#include "enums.hpp" - -namespace qs::network { - -WifiSecurityType::Enum securityFromConnectionSettings(const ConnectionSettingsMap& settings) { - const QVariantMap& security = settings.value("802-11-wireless-security"); - if (security.isEmpty()) { - return WifiSecurityType::Open; - }; - - const QString keyMgmt = security["key-mgmt"].toString(); - const QString authAlg = security["auth-alg"].toString(); - const QList proto = security["proto"].toList(); - - if (keyMgmt == "none") { - return WifiSecurityType::StaticWep; - } else if (keyMgmt == "ieee8021x") { - if (authAlg == "leap") { - return WifiSecurityType::Leap; - } else { - return WifiSecurityType::DynamicWep; - } - } else if (keyMgmt == "wpa-psk") { - if (proto.contains("wpa") && proto.contains("rsn")) return WifiSecurityType::WpaPsk; - return WifiSecurityType::Wpa2Psk; - } else if (keyMgmt == "wpa-eap") { - if (proto.contains("wpa") && proto.contains("rsn")) return WifiSecurityType::WpaEap; - return WifiSecurityType::Wpa2Eap; - } else if (keyMgmt == "sae") { - return WifiSecurityType::Sae; - } else if (keyMgmt == "wpa-eap-suite-b-192") { - return WifiSecurityType::Wpa3SuiteB192; - } - return WifiSecurityType::Open; -} - -bool deviceSupportsApCiphers( - NMWirelessCapabilities::Enum caps, - NM80211ApSecurityFlags::Enum apFlags, - WifiSecurityType::Enum type -) { - bool havePair = false; - bool haveGroup = false; - // Device needs to support at least one pairwise and one group cipher - - if (type == WifiSecurityType::StaticWep) { - // Static WEP only uses group ciphers - havePair = true; - } else { - if (caps & NMWirelessCapabilities::CipherWep40 && apFlags & NM80211ApSecurityFlags::PairWep40) { - havePair = true; - } - if (caps & NMWirelessCapabilities::CipherWep104 && apFlags & NM80211ApSecurityFlags::PairWep104) - { - havePair = true; - } - if (caps & NMWirelessCapabilities::CipherTkip && apFlags & NM80211ApSecurityFlags::PairTkip) { - havePair = true; - } - if (caps & NMWirelessCapabilities::CipherCcmp && apFlags & NM80211ApSecurityFlags::PairCcmp) { - havePair = true; - } - } - - if (caps & NMWirelessCapabilities::CipherWep40 && apFlags & NM80211ApSecurityFlags::GroupWep40) { - haveGroup = true; - } - if (caps & NMWirelessCapabilities::CipherWep104 && apFlags & NM80211ApSecurityFlags::GroupWep104) - { - haveGroup = true; - } - if (type != WifiSecurityType::StaticWep) { - if (caps & NMWirelessCapabilities::CipherTkip && apFlags & NM80211ApSecurityFlags::GroupTkip) { - haveGroup = true; - } - if (caps & NMWirelessCapabilities::CipherCcmp && apFlags & NM80211ApSecurityFlags::GroupCcmp) { - haveGroup = true; - } - } - - return (havePair && haveGroup); -} - -bool securityIsValid( - WifiSecurityType::Enum type, - NMWirelessCapabilities::Enum caps, - bool adhoc, - NM80211ApFlags::Enum apFlags, - NM80211ApSecurityFlags::Enum apWpa, - NM80211ApSecurityFlags::Enum apRsn -) { - switch (type) { - case WifiSecurityType::Open: - if (apFlags & NM80211ApFlags::Privacy) return false; - if (apWpa || apRsn) return false; - break; - case WifiSecurityType::Leap: - if (adhoc) return false; - case WifiSecurityType::StaticWep: - if (!(apFlags & NM80211ApFlags::Privacy)) return false; - if (apWpa || apRsn) { - if (!deviceSupportsApCiphers(caps, apWpa, WifiSecurityType::StaticWep)) { - if (!deviceSupportsApCiphers(caps, apRsn, WifiSecurityType::StaticWep)) return false; - } - } - break; - case WifiSecurityType::DynamicWep: - if (adhoc) return false; - if (apRsn || !(apFlags & NM80211ApFlags::Privacy)) return false; - if (apWpa) { - if (!(apWpa & NM80211ApSecurityFlags::KeyMgmt8021x)) return false; - if (!deviceSupportsApCiphers(caps, apWpa, WifiSecurityType::DynamicWep)) return false; - } - break; - case WifiSecurityType::WpaPsk: - if (adhoc) return false; - if (!(caps & NMWirelessCapabilities::Wpa)) return false; - if (apWpa & NM80211ApSecurityFlags::KeyMgmtPsk) { - if (apWpa & NM80211ApSecurityFlags::PairTkip && caps & NMWirelessCapabilities::CipherTkip) { - return true; - } - if (apWpa & NM80211ApSecurityFlags::PairCcmp && caps & NMWirelessCapabilities::CipherCcmp) { - return true; - } - } - return false; - case WifiSecurityType::Wpa2Psk: - if (!(caps & NMWirelessCapabilities::Rsn)) return false; - if (adhoc) { - if (!(caps & NMWirelessCapabilities::IbssRsn)) return false; - if (apRsn & NM80211ApSecurityFlags::PairCcmp && caps & NMWirelessCapabilities::CipherCcmp) { - return true; - } - } else { - if (apRsn & NM80211ApSecurityFlags::KeyMgmtPsk) { - if (apRsn & NM80211ApSecurityFlags::PairTkip && caps & NMWirelessCapabilities::CipherTkip) { - return true; - } - if (apRsn & NM80211ApSecurityFlags::PairCcmp && caps & NMWirelessCapabilities::CipherCcmp) { - return true; - } - } - } - return false; - case WifiSecurityType::WpaEap: - if (adhoc) return false; - if (!(caps & NMWirelessCapabilities::Wpa)) return false; - if (!(apWpa & NM80211ApSecurityFlags::KeyMgmt8021x)) return false; - if (!deviceSupportsApCiphers(caps, apWpa, WifiSecurityType::WpaEap)) return false; - break; - case WifiSecurityType::Wpa2Eap: - if (adhoc) return false; - if (!(caps & NMWirelessCapabilities::Rsn)) return false; - if (!(apRsn & NM80211ApSecurityFlags::KeyMgmt8021x)) return false; - if (!deviceSupportsApCiphers(caps, apRsn, WifiSecurityType::Wpa2Eap)) return false; - break; - case WifiSecurityType::Sae: - if (!(caps & NMWirelessCapabilities::Rsn)) return false; - if (adhoc) { - if (!(caps & NMWirelessCapabilities::IbssRsn)) return false; - if (apRsn & NM80211ApSecurityFlags::PairCcmp && caps & NMWirelessCapabilities::CipherCcmp) { - return true; - } - } else { - if (apRsn & NM80211ApSecurityFlags::KeyMgmtSae) { - if (apRsn & NM80211ApSecurityFlags::PairTkip && caps & NMWirelessCapabilities::CipherTkip) { - return true; - } - if (apRsn & NM80211ApSecurityFlags::PairCcmp && caps & NMWirelessCapabilities::CipherCcmp) { - return true; - } - } - } - return false; - case WifiSecurityType::Owe: - if (adhoc) return false; - if (!(caps & NMWirelessCapabilities::Rsn)) return false; - if (!(apRsn & NM80211ApSecurityFlags::KeyMgmtOwe) - && !(apRsn & NM80211ApSecurityFlags::KeyMgmtOweTm)) - { - return false; - } - break; - case WifiSecurityType::Wpa3SuiteB192: - if (adhoc) return false; - if (!(caps & NMWirelessCapabilities::Rsn)) return false; - if (!(apRsn & NM80211ApSecurityFlags::KeyMgmtEapSuiteB192)) return false; - break; - default: return false; - } - return true; -} - -WifiSecurityType::Enum findBestWirelessSecurity( - NMWirelessCapabilities::Enum caps, - bool adHoc, - NM80211ApFlags::Enum apFlags, - NM80211ApSecurityFlags::Enum apWpa, - NM80211ApSecurityFlags::Enum apRsn -) { - // Loop through security types from most to least secure since the enum - // values are sequential and in priority order (0-10, excluding Unknown=11) - for (int i = WifiSecurityType::Wpa3SuiteB192; i <= WifiSecurityType::Open; ++i) { - auto type = static_cast(i); - if (securityIsValid(type, caps, adHoc, apFlags, apWpa, apRsn)) { - return type; - } - } - return WifiSecurityType::Unknown; -} - -// NOLINTBEGIN -QDateTime clockBootTimeToDateTime(qint64 clockBootTime) { - clockid_t clkId = CLOCK_BOOTTIME; - struct timespec tp {}; - - const QDateTime now = QDateTime::currentDateTime(); - int r = clock_gettime(clkId, &tp); - if (r == -1 && errno == EINVAL) { - clkId = CLOCK_MONOTONIC; - r = clock_gettime(clkId, &tp); - } - - // Convert to milliseconds - const qint64 nowInMs = tp.tv_sec * 1000 + tp.tv_nsec / 1000000; - - // Return a QDateTime of the millisecond diff - const qint64 offset = clockBootTime - nowInMs; - return QDateTime::fromMSecsSinceEpoch(now.toMSecsSinceEpoch() + offset); -} -// NOLINTEND - -} // namespace qs::network diff --git a/src/network/nm/utils.hpp b/src/network/nm/utils.hpp deleted file mode 100644 index ce8b784..0000000 --- a/src/network/nm/utils.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "../wifi.hpp" -#include "dbus_types.hpp" -#include "enums.hpp" - -namespace qs::network { - -WifiSecurityType::Enum securityFromConnectionSettings(const ConnectionSettingsMap& settings); - -bool deviceSupportsApCiphers( - NMWirelessCapabilities::Enum caps, - NM80211ApSecurityFlags::Enum apFlags, - WifiSecurityType::Enum type -); - -// In sync with NetworkManager/libnm-core/nm-utils.c:nm_utils_security_valid() -// Given a set of device capabilities, and a desired security type to check -// against, determines whether the combination of device, desired security type, -// and AP capabilities intersect. -bool securityIsValid( - WifiSecurityType::Enum type, - NMWirelessCapabilities::Enum caps, - bool adhoc, - NM80211ApFlags::Enum apFlags, - NM80211ApSecurityFlags::Enum apWpa, - NM80211ApSecurityFlags::Enum apRsn -); - -WifiSecurityType::Enum findBestWirelessSecurity( - NMWirelessCapabilities::Enum caps, - bool adHoc, - NM80211ApFlags::Enum apFlags, - NM80211ApSecurityFlags::Enum apWpa, - NM80211ApSecurityFlags::Enum apRsn -); - -QDateTime clockBootTimeToDateTime(qint64 clockBootTime); - -} // namespace qs::network diff --git a/src/network/nm/wireless.cpp b/src/network/nm/wireless.cpp deleted file mode 100644 index 9dff14b..0000000 --- a/src/network/nm/wireless.cpp +++ /dev/null @@ -1,457 +0,0 @@ -#include "wireless.hpp" -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../core/logcat.hpp" -#include "../../dbus/properties.hpp" -#include "../network.hpp" -#include "../wifi.hpp" -#include "accesspoint.hpp" -#include "connection.hpp" -#include "dbus_nm_wireless.h" -#include "dbus_types.hpp" -#include "device.hpp" -#include "enums.hpp" -#include "utils.hpp" - -namespace qs::network { -using namespace qs::dbus; - -namespace { -QS_LOGGING_CATEGORY(logNetworkManager, "quickshell.network.networkmanager", QtWarningMsg); -} - -NMWirelessNetwork::NMWirelessNetwork(QString ssid, QObject* parent) - : QObject(parent) - , mSsid(std::move(ssid)) - , bKnown(false) - , bSecurity(WifiSecurityType::Unknown) - , bReason(NMConnectionStateReason::None) - , bState(NMConnectionState::Deactivated) {} - -void NMWirelessNetwork::updateReferenceConnection() { - // If the network has no connections, the reference is nullptr. - if (this->mConnections.isEmpty()) { - this->mReferenceConn = nullptr; - this->bSecurity = WifiSecurityType::Unknown; - // Set security back to reference AP. - if (this->mReferenceAp) { - this->bSecurity.setBinding([this]() { return this->mReferenceAp->security(); }); - } - return; - }; - - // If the network has an active connection, use it as the reference. - if (this->mActiveConnection) { - auto* conn = this->mConnections.value(this->mActiveConnection->connection().path()); - if (conn && conn != this->mReferenceConn) { - this->mReferenceConn = conn; - this->bSecurity.setBinding([conn]() { return conn->security(); }); - } - return; - } - - // Otherwise, choose the connection with the strongest security settings. - NMConnectionSettings* selectedConn = nullptr; - for (auto* conn: this->mConnections.values()) { - if (!selectedConn || conn->security() > selectedConn->security()) { - selectedConn = conn; - } - } - if (this->mReferenceConn != selectedConn) { - this->mReferenceConn = selectedConn; - this->bSecurity.setBinding([selectedConn]() { return selectedConn->security(); }); - } -} - -void NMWirelessNetwork::updateReferenceAp() { - // If the network has no APs, the reference is a nullptr. - if (this->mAccessPoints.isEmpty()) { - this->mReferenceAp = nullptr; - this->bSignalStrength = 0; - return; - } - - // Otherwise, choose the AP with the strongest signal. - NMAccessPoint* selectedAp = nullptr; - for (auto* ap: this->mAccessPoints.values()) { - // Always prefer the active AP. - if (ap->path() == this->bActiveApPath) { - selectedAp = ap; - break; - } - if (!selectedAp || ap->signalStrength() > selectedAp->signalStrength()) { - selectedAp = ap; - } - } - if (this->mReferenceAp != selectedAp) { - this->mReferenceAp = selectedAp; - this->bSignalStrength.setBinding([selectedAp]() { return selectedAp->signalStrength(); }); - // Reference AP is used for security when there's no connection settings. - if (!this->mReferenceConn) { - this->bSecurity.setBinding([selectedAp]() { return selectedAp->security(); }); - } - } -} - -void NMWirelessNetwork::addAccessPoint(NMAccessPoint* ap) { - if (this->mAccessPoints.contains(ap->path())) return; - this->mAccessPoints.insert(ap->path(), ap); - auto onDestroyed = [this, ap]() { - if (this->mAccessPoints.take(ap->path())) { - this->updateReferenceAp(); - if (this->mAccessPoints.isEmpty() && this->mConnections.isEmpty()) emit this->disappeared(); - } - }; - // clang-format off - QObject::connect(ap, &NMAccessPoint::signalStrengthChanged, this, &NMWirelessNetwork::updateReferenceAp); - QObject::connect(ap, &NMAccessPoint::destroyed, this, onDestroyed); - // clang-format on - this->updateReferenceAp(); -}; - -void NMWirelessNetwork::addConnection(NMConnectionSettings* conn) { - if (this->mConnections.contains(conn->path())) return; - this->mConnections.insert(conn->path(), conn); - auto onDestroyed = [this, conn]() { - if (this->mConnections.take(conn->path())) { - this->updateReferenceConnection(); - if (this->mConnections.isEmpty()) this->bKnown = false; - if (this->mAccessPoints.isEmpty() && this->mConnections.isEmpty()) emit this->disappeared(); - } - }; - // clang-format off - QObject::connect(conn, &NMConnectionSettings::securityChanged, this, &NMWirelessNetwork::updateReferenceConnection); - QObject::connect(conn, &NMConnectionSettings::destroyed, this, onDestroyed); - // clang-format on - this->bKnown = true; - this->updateReferenceConnection(); -}; - -void NMWirelessNetwork::addActiveConnection(NMActiveConnection* active) { - if (this->mActiveConnection) return; - this->mActiveConnection = active; - this->bState.setBinding([active]() { return active->state(); }); - this->bReason.setBinding([active]() { return active->stateReason(); }); - auto onDestroyed = [this, active]() { - if (this->mActiveConnection && this->mActiveConnection == active) { - this->mActiveConnection = nullptr; - this->updateReferenceConnection(); - this->bState = NMConnectionState::Deactivated; - this->bReason = NMConnectionStateReason::None; - } - }; - QObject::connect(active, &NMActiveConnection::destroyed, this, onDestroyed); - this->updateReferenceConnection(); -}; - -void NMWirelessNetwork::forget() { - if (this->mConnections.isEmpty()) return; - for (auto* conn: this->mConnections.values()) { - conn->forget(); - } -} - -NMWirelessDevice::NMWirelessDevice(const QString& path, QObject* parent) - : NMDevice(path, parent) - , mScanTimer(this) { - this->wirelessProxy = new DBusNMWirelessProxy( - "org.freedesktop.NetworkManager", - path, - QDBusConnection::systemBus(), - this - ); - - if (!this->wirelessProxy->isValid()) { - qCWarning(logNetworkManager) << "Cannot create DBus interface for wireless device at" << path; - return; - } - - QObject::connect( - &this->wirelessProperties, - &DBusPropertyGroup::getAllFinished, - this, - &NMWirelessDevice::initWireless, - Qt::SingleShotConnection - ); - - QObject::connect(&this->mScanTimer, &QTimer::timeout, this, &NMWirelessDevice::onScanTimeout); - this->mScanTimer.setSingleShot(true); - - this->wirelessProperties.setInterface(this->wirelessProxy); - this->wirelessProperties.updateAllViaGetAll(); -} - -void NMWirelessDevice::initWireless() { - // clang-format off - QObject::connect(this->wirelessProxy, &DBusNMWirelessProxy::AccessPointAdded, this, &NMWirelessDevice::onAccessPointAdded); - QObject::connect(this->wirelessProxy, &DBusNMWirelessProxy::AccessPointRemoved, this, &NMWirelessDevice::onAccessPointRemoved); - QObject::connect(this, &NMWirelessDevice::accessPointLoaded, this, &NMWirelessDevice::onAccessPointLoaded); - QObject::connect(this, &NMWirelessDevice::connectionLoaded, this, &NMWirelessDevice::onConnectionLoaded); - QObject::connect(this, &NMWirelessDevice::activeConnectionLoaded, this, &NMWirelessDevice::onActiveConnectionLoaded); - QObject::connect(this, &NMWirelessDevice::scanningChanged, this, &NMWirelessDevice::onScanningChanged); - // clang-format on - this->registerAccessPoints(); -} - -void NMWirelessDevice::onAccessPointAdded(const QDBusObjectPath& path) { - this->registerAccessPoint(path.path()); -} - -void NMWirelessDevice::onAccessPointRemoved(const QDBusObjectPath& path) { - auto* ap = this->mAccessPoints.take(path.path()); - if (!ap) { - qCDebug(logNetworkManager) << "Sent removal signal for" << path.path() - << "which is not registered."; - return; - } - delete ap; -} - -void NMWirelessDevice::onAccessPointLoaded(NMAccessPoint* ap) { - const QString ssid = ap->ssid(); - if (!ssid.isEmpty()) { - auto mode = ap->mode(); - if (mode == NM80211Mode::Infra) { - auto* net = this->mNetworks.value(ssid); - if (!net) net = this->registerNetwork(ssid); - net->addAccessPoint(ap); - } - } -} - -void NMWirelessDevice::onConnectionLoaded(NMConnectionSettings* conn) { - const ConnectionSettingsMap& settings = conn->settings(); - // Filter connections that aren't wireless or have missing settings - if (settings["connection"]["id"].toString().isEmpty() - || settings["connection"]["uuid"].toString().isEmpty() - || !settings.contains("802-11-wireless") - || settings["802-11-wireless"]["ssid"].toString().isEmpty()) - { - return; - } - - const auto ssid = settings["802-11-wireless"]["ssid"].toString(); - const auto mode = settings["802-11-wireless"]["mode"].toString(); - - if (mode == "infrastructure") { - auto* net = this->mNetworks.value(ssid); - if (!net) net = this->registerNetwork(ssid); - net->addConnection(conn); - - // Check for active connections that loaded before their respective connection settings - auto* active = this->activeConnection(); - if (active && conn->path() == active->connection().path()) { - net->addActiveConnection(active); - } - } - // TODO: Create hotspots when mode == "ap" -} - -void NMWirelessDevice::onActiveConnectionLoaded(NMActiveConnection* active) { - // Find an exisiting network with connection settings that matches the active - const QString activeConnPath = active->connection().path(); - for (const auto& net: this->mNetworks.values()) { - for (auto* conn: net->connections()) { - if (activeConnPath == conn->path()) { - net->addActiveConnection(active); - return; - } - } - } -} - -void NMWirelessDevice::onScanTimeout() { - const QDateTime now = QDateTime::currentDateTime(); - const QDateTime lastScan = this->bLastScan; - const QDateTime lastScanRequest = this->mLastScanRequest; - - if (lastScan.isValid() && lastScan.msecsTo(now) < this->mScanIntervalMs) { - // Rate limit if backend last scan property updated within the interval - auto diff = static_cast(this->mScanIntervalMs - lastScan.msecsTo(now)); - this->mScanTimer.start(diff); - } else if (lastScanRequest.isValid() && lastScanRequest.msecsTo(now) < this->mScanIntervalMs) { - // Rate limit if frontend changes scanner state within the interval - auto diff = static_cast(this->mScanIntervalMs - lastScanRequest.msecsTo(now)); - this->mScanTimer.start(diff); - } else { - this->wirelessProxy->RequestScan({}); - this->mLastScanRequest = now; - this->mScanTimer.start(this->mScanIntervalMs); - } -} - -void NMWirelessDevice::onScanningChanged(bool scanning) { - scanning ? this->onScanTimeout() : this->mScanTimer.stop(); -} - -void NMWirelessDevice::registerAccessPoints() { - auto pending = this->wirelessProxy->GetAllAccessPoints(); - auto* call = new QDBusPendingCallWatcher(pending, this); - - auto responseCallback = [this](QDBusPendingCallWatcher* call) { - const QDBusPendingReply> reply = *call; - - if (reply.isError()) { - qCWarning(logNetworkManager) - << "Failed to get all access points: " << reply.error().message(); - } else { - for (const QDBusObjectPath& devicePath: reply.value()) { - this->registerAccessPoint(devicePath.path()); - } - } - - delete call; - }; - - QObject::connect(call, &QDBusPendingCallWatcher::finished, this, responseCallback); -} - -void NMWirelessDevice::registerAccessPoint(const QString& path) { - if (this->mAccessPoints.contains(path)) { - qCDebug(logNetworkManager) << "Skipping duplicate registration of access point" << path; - return; - } - - auto* ap = new NMAccessPoint(path, this); - - if (!ap->isValid()) { - qCWarning(logNetworkManager) << "Ignoring invalid registration of" << path; - delete ap; - return; - } - - this->mAccessPoints.insert(path, ap); - QObject::connect( - ap, - &NMAccessPoint::loaded, - this, - [this, ap]() { emit this->accessPointLoaded(ap); }, - Qt::SingleShotConnection - ); - ap->bindableSecurity().setBinding([this, ap]() { - return findBestWirelessSecurity( - this->bCapabilities, - ap->mode() == NM80211Mode::Adhoc, - ap->flags(), - ap->wpaFlags(), - ap->rsnFlags() - ); - }); -} - -NMWirelessNetwork* NMWirelessDevice::registerNetwork(const QString& ssid) { - auto* net = new NMWirelessNetwork(ssid, this); - - // To avoid exposing outdated state to the frontend, filter the backend networks to only show - // the known or currently connected networks when the scanner is off. - auto visible = [this, net]() { - return this->bScanning || net->state() == NMConnectionState::Activated || net->known(); - }; - auto onVisibilityChanged = [this, net](bool visible) { - visible ? this->registerFrontendNetwork(net) : this->removeFrontendNetwork(net); - }; - - net->bindableVisible().setBinding(visible); - net->bindableActiveApPath().setBinding([this]() { return this->activeApPath().path(); }); - QObject::connect(net, &NMWirelessNetwork::disappeared, this, &NMWirelessDevice::removeNetwork); - QObject::connect(net, &NMWirelessNetwork::visibilityChanged, this, onVisibilityChanged); - - this->mNetworks.insert(ssid, net); - if (net->visible()) this->registerFrontendNetwork(net); - return net; -} - -void NMWirelessDevice::registerFrontendNetwork(NMWirelessNetwork* net) { - auto ssid = net->ssid(); - auto* frontendNet = new WifiNetwork(ssid, net); - - // Bind WifiNetwork to NMWirelessNetwork - auto translateSignal = [net]() { return net->signalStrength() / 100.0; }; - auto translateState = [net]() { return net->state() == NMConnectionState::Activated; }; - frontendNet->bindableSignalStrength().setBinding(translateSignal); - frontendNet->bindableConnected().setBinding(translateState); - frontendNet->bindableKnown().setBinding([net]() { return net->known(); }); - frontendNet->bindableNmReason().setBinding([net]() { return net->reason(); }); - frontendNet->bindableSecurity().setBinding([net]() { return net->security(); }); - frontendNet->bindableState().setBinding([net]() { - return static_cast(net->state()); - }); - - QObject::connect(frontendNet, &WifiNetwork::requestConnect, this, [this, net]() { - if (net->referenceConnection()) { - emit this->activateConnection( - QDBusObjectPath(net->referenceConnection()->path()), - QDBusObjectPath(this->path()) - ); - return; - } - if (net->referenceAp()) { - emit this->addAndActivateConnection( - ConnectionSettingsMap(), - QDBusObjectPath(this->path()), - QDBusObjectPath(net->referenceAp()->path()) - ); - } - }); - - QObject::connect( - frontendNet, - &WifiNetwork::requestDisconnect, - this, - &NMWirelessDevice::disconnect - ); - - QObject::connect(frontendNet, &WifiNetwork::requestForget, net, &NMWirelessNetwork::forget); - - this->mFrontendNetworks.insert(ssid, frontendNet); - emit this->networkAdded(frontendNet); -} - -void NMWirelessDevice::removeFrontendNetwork(NMWirelessNetwork* net) { - auto* frontendNet = this->mFrontendNetworks.take(net->ssid()); - if (frontendNet) { - emit this->networkRemoved(frontendNet); - frontendNet->deleteLater(); - } -} - -void NMWirelessDevice::removeNetwork() { - auto* net = qobject_cast(this->sender()); - if (this->mNetworks.take(net->ssid())) { - this->removeFrontendNetwork(net); - delete net; - }; -} - -bool NMWirelessDevice::isValid() const { - return this->NMDevice::isValid() && (this->wirelessProxy && this->wirelessProxy->isValid()); -} - -} // namespace qs::network - -namespace qs::dbus { - -DBusResult -DBusDataTransform::fromWire(quint32 wire) { - return DBusResult(static_cast(wire)); -} - -DBusResult DBusDataTransform::fromWire(qint64 wire) { - return DBusResult(qs::network::clockBootTimeToDateTime(wire)); -} - -} // namespace qs::dbus diff --git a/src/network/nm/wireless.hpp b/src/network/nm/wireless.hpp deleted file mode 100644 index fe4010e..0000000 --- a/src/network/nm/wireless.hpp +++ /dev/null @@ -1,166 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "../wifi.hpp" -#include "accesspoint.hpp" -#include "connection.hpp" -#include "dbus_nm_wireless.h" -#include "device.hpp" -#include "enums.hpp" - -namespace qs::dbus { -template <> -struct DBusDataTransform { - using Wire = quint32; - using Data = qs::network::NMWirelessCapabilities::Enum; - static DBusResult fromWire(Wire wire); -}; - -template <> -struct DBusDataTransform { - using Wire = qint64; - using Data = QDateTime; - static DBusResult fromWire(Wire wire); -}; - -} // namespace qs::dbus -namespace qs::network { - -// NMWirelessNetwork aggregates all related NMActiveConnection, NMAccessPoint, and NMConnectionSetting objects. -class NMWirelessNetwork: public QObject { - Q_OBJECT; - -public: - explicit NMWirelessNetwork(QString ssid, QObject* parent = nullptr); - - void addAccessPoint(NMAccessPoint* ap); - void addConnection(NMConnectionSettings* conn); - void addActiveConnection(NMActiveConnection* active); - void forget(); - - [[nodiscard]] QString ssid() const { return this->mSsid; }; - [[nodiscard]] quint8 signalStrength() const { return this->bSignalStrength; }; - [[nodiscard]] WifiSecurityType::Enum security() const { return this->bSecurity; }; - [[nodiscard]] NMConnectionState::Enum state() const { return this->bState; }; - [[nodiscard]] bool known() const { return this->bKnown; }; - [[nodiscard]] NMConnectionStateReason::Enum reason() const { return this->bReason; }; - [[nodiscard]] NMAccessPoint* referenceAp() const { return this->mReferenceAp; }; - [[nodiscard]] NMConnectionSettings* referenceConnection() const { return this->mReferenceConn; }; - [[nodiscard]] QList accessPoints() const { return this->mAccessPoints.values(); }; - [[nodiscard]] QList connections() const { - return this->mConnections.values(); - } - [[nodiscard]] QBindable bindableActiveApPath() { return &this->bActiveApPath; }; - [[nodiscard]] QBindable bindableVisible() { return &this->bVisible; }; - [[nodiscard]] bool visible() const { return this->bVisible; }; - -signals: - void disappeared(); - void visibilityChanged(bool visible); - void signalStrengthChanged(quint8 signal); - void stateChanged(NMConnectionState::Enum state); - void knownChanged(bool known); - void securityChanged(WifiSecurityType::Enum security); - void reasonChanged(NMConnectionStateReason::Enum reason); - void capabilitiesChanged(NMWirelessCapabilities::Enum caps); - void activeApPathChanged(QString path); - -private: - void updateReferenceAp(); - void updateReferenceConnection(); - - QString mSsid; - QHash mAccessPoints; - QHash mConnections; - NMAccessPoint* mReferenceAp = nullptr; - NMConnectionSettings* mReferenceConn = nullptr; - NMActiveConnection* mActiveConnection = nullptr; - - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, bool, bVisible, &NMWirelessNetwork::visibilityChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, bool, bKnown, &NMWirelessNetwork::knownChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, WifiSecurityType::Enum, bSecurity, &NMWirelessNetwork::securityChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, NMConnectionStateReason::Enum, bReason, &NMWirelessNetwork::reasonChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, NMConnectionState::Enum, bState, &NMWirelessNetwork::stateChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, quint8, bSignalStrength, &NMWirelessNetwork::signalStrengthChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessNetwork, QString, bActiveApPath, &NMWirelessNetwork::activeApPathChanged); - // clang-format on -}; - -// Proxy of a /org/freedesktop/NetworkManager/Device/* object. -// Extends NMDevice to also include members from the org.freedesktop.NetworkManager.Device.Wireless interface -// Owns the lifetime of NMAccessPoints(s), NMWirelessNetwork(s), frontend WifiNetwork(s). -class NMWirelessDevice: public NMDevice { - Q_OBJECT; - -public: - explicit NMWirelessDevice(const QString& path, QObject* parent = nullptr); - - [[nodiscard]] bool isValid() const override; - [[nodiscard]] NMWirelessCapabilities::Enum capabilities() { return this->bCapabilities; }; - [[nodiscard]] const QDBusObjectPath& activeApPath() { return this->bActiveAccessPoint; }; - [[nodiscard]] NM80211Mode::Enum mode() { return this->bMode; }; - [[nodiscard]] QBindable bindableScanning() { return &this->bScanning; }; - -signals: - void accessPointLoaded(NMAccessPoint* ap); - void accessPointRemoved(NMAccessPoint* ap); - void networkAdded(WifiNetwork* net); - void networkRemoved(WifiNetwork* net); - void lastScanChanged(QDateTime lastScan); - void scanningChanged(bool scanning); - void capabilitiesChanged(NMWirelessCapabilities::Enum caps); - void activeAccessPointChanged(const QDBusObjectPath& path); - void modeChanged(NM80211Mode::Enum mode); - -private slots: - void onAccessPointAdded(const QDBusObjectPath& path); - void onAccessPointRemoved(const QDBusObjectPath& path); - void onAccessPointLoaded(NMAccessPoint* ap); - void onConnectionLoaded(NMConnectionSettings* conn); - void onActiveConnectionLoaded(NMActiveConnection* active); - void onScanTimeout(); - void onScanningChanged(bool scanning); - -private: - void registerAccessPoint(const QString& path); - void registerFrontendNetwork(NMWirelessNetwork* net); - void removeFrontendNetwork(NMWirelessNetwork* net); - void removeNetwork(); - bool checkVisibility(WifiNetwork* net); - void registerAccessPoints(); - void initWireless(); - NMWirelessNetwork* registerNetwork(const QString& ssid); - - QHash mAccessPoints; - QHash mNetworks; - QHash mFrontendNetworks; - - QDateTime mLastScanRequest; - QTimer mScanTimer; - qint32 mScanIntervalMs = 10001; - - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessDevice, bool, bScanning, &NMWirelessDevice::scanningChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessDevice, QDateTime, bLastScan, &NMWirelessDevice::lastScanChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessDevice, NMWirelessCapabilities::Enum, bCapabilities, &NMWirelessDevice::capabilitiesChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessDevice, QDBusObjectPath, bActiveAccessPoint, &NMWirelessDevice::activeAccessPointChanged); - Q_OBJECT_BINDABLE_PROPERTY(NMWirelessDevice, NM80211Mode::Enum, bMode, &NMWirelessDevice::modeChanged); - - QS_DBUS_BINDABLE_PROPERTY_GROUP(NMWireless, wirelessProperties); - QS_DBUS_PROPERTY_BINDING(NMWirelessDevice, pLastScan, bLastScan, wirelessProperties, "LastScan"); - QS_DBUS_PROPERTY_BINDING(NMWirelessDevice, pCapabilities, bCapabilities, wirelessProperties, "WirelessCapabilities"); - QS_DBUS_PROPERTY_BINDING(NMWirelessDevice, pActiveAccessPoint, bActiveAccessPoint, wirelessProperties, "ActiveAccessPoint"); - QS_DBUS_PROPERTY_BINDING(NMWirelessDevice, pMode, bMode, wirelessProperties, "Mode"); - // clang-format on - - DBusNMWirelessProxy* wirelessProxy = nullptr; -}; - -} // namespace qs::network diff --git a/src/network/test/manual/network.qml b/src/network/test/manual/network.qml deleted file mode 100644 index 0fd0f72..0000000 --- a/src/network/test/manual/network.qml +++ /dev/null @@ -1,155 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import Quickshell -import Quickshell.Widgets -import Quickshell.Networking - -FloatingWindow { - color: contentItem.palette.window - - ColumnLayout { - anchors.fill: parent - anchors.margins: 5 - - Column { - Layout.fillWidth: true - RowLayout { - Label { - text: "WiFi" - font.bold: true - font.pointSize: 12 - } - CheckBox { - text: "Software" - checked: Networking.wifiEnabled - onClicked: Networking.wifiEnabled = !Networking.wifiEnabled - } - CheckBox { - enabled: false - text: "Hardware" - checked: Networking.wifiHardwareEnabled - } - } - } - - ListView { - clip: true - Layout.fillWidth: true - Layout.fillHeight: true - model: Networking.devices - - delegate: WrapperRectangle { - width: parent.width - color: "transparent" - border.color: palette.button - border.width: 1 - margin: 5 - - ColumnLayout { - RowLayout { - Label { text: modelData.name; font.bold: true } - Label { text: modelData.address } - Label { text: `(Type: ${DeviceType.toString(modelData.type)})` } - } - RowLayout { - Label { - text: DeviceConnectionState.toString(modelData.state) - color: modelData.connected ? palette.link : palette.placeholderText - } - Label { - visible: Networking.backend == NetworkBackendType.NetworkManager && (modelData.state == DeviceConnectionState.Connecting || modelData.state == DeviceConnectionState.Disconnecting) - text: `(${NMDeviceState.toString(modelData.nmState)})` - } - Button { - visible: modelData.state == DeviceConnectionState.Connected - text: "Disconnect" - onClicked: modelData.disconnect() - } - CheckBox { - text: "Autoconnect" - checked: modelData.autoconnect - onClicked: modelData.autoconnect = !modelData.autoconnect - } - Label { - text: `Mode: ${WifiDeviceMode.toString(modelData.mode)}` - visible: modelData.type == DeviceType.Wifi - } - CheckBox { - text: "Scanner" - checked: modelData.scannerEnabled - onClicked: modelData.scannerEnabled = !modelData.scannerEnabled - visible: modelData.type === DeviceType.Wifi - } - } - - Repeater { - Layout.fillWidth: true - model: { - if (modelData.type !== DeviceType.Wifi) return [] - return [...modelData.networks.values].sort((a, b) => { - if (a.connected !== b.connected) { - return b.connected - a.connected - } - return b.signalStrength - a.signalStrength - }) - } - - WrapperRectangle { - Layout.fillWidth: true - color: modelData.connected ? palette.highlight : palette.button - border.color: palette.mid - border.width: 1 - margin: 5 - - RowLayout { - ColumnLayout { - Layout.fillWidth: true - RowLayout { - Label { text: modelData.name; font.bold: true } - Label { - text: modelData.known ? "Known" : "" - color: palette.placeholderText - } - } - RowLayout { - Label { - text: `Security: ${WifiSecurityType.toString(modelData.security)}` - color: palette.placeholderText - } - Label { - text: `| Signal strength: ${Math.round(modelData.signalStrength*100)}%` - color: palette.placeholderText - } - } - Label { - visible: Networking.backend == NetworkBackendType.NetworkManager && (modelData.nmReason != NMConnectionStateReason.Unknown && modelData.nmReason != NMConnectionStateReason.None) - text: `Connection change reason: ${NMConnectionStateReason.toString(modelData.nmReason)}` - } - } - RowLayout { - Layout.alignment: Qt.AlignRight - Button { - text: "Connect" - onClicked: modelData.connect() - visible: !modelData.connected - } - Button { - text: "Disconnect" - onClicked: modelData.disconnect() - visible: modelData.connected - } - Button { - text: "Forget" - onClicked: modelData.forget() - visible: modelData.known - } - } - } - } - } - } - } - } - } -} diff --git a/src/network/wifi.cpp b/src/network/wifi.cpp deleted file mode 100644 index 57fb8ea..0000000 --- a/src/network/wifi.cpp +++ /dev/null @@ -1,138 +0,0 @@ -#include "wifi.hpp" -#include - -#include -#include -#include -#include -#include - -#include "../core/logcat.hpp" -#include "device.hpp" -#include "network.hpp" - -namespace qs::network { - -namespace { -QS_LOGGING_CATEGORY(logWifi, "quickshell.network.wifi", QtWarningMsg); -} // namespace - -QString WifiSecurityType::toString(WifiSecurityType::Enum type) { - switch (type) { - case Unknown: return QStringLiteral("Unknown"); - case Wpa3SuiteB192: return QStringLiteral("WPA3 Suite B 192-bit"); - case Sae: return QStringLiteral("WPA3"); - case Wpa2Eap: return QStringLiteral("WPA2 Enterprise"); - case Wpa2Psk: return QStringLiteral("WPA2"); - case WpaEap: return QStringLiteral("WPA Enterprise"); - case WpaPsk: return QStringLiteral("WPA"); - case StaticWep: return QStringLiteral("WEP"); - case DynamicWep: return QStringLiteral("Dynamic WEP"); - case Leap: return QStringLiteral("LEAP"); - case Owe: return QStringLiteral("OWE"); - case Open: return QStringLiteral("Open"); - default: return QStringLiteral("Unknown"); - } -} - -QString WifiDeviceMode::toString(WifiDeviceMode::Enum mode) { - switch (mode) { - case Unknown: return QStringLiteral("Unknown"); - case AdHoc: return QStringLiteral("Ad-Hoc"); - case Station: return QStringLiteral("Station"); - case AccessPoint: return QStringLiteral("Access Point"); - case Mesh: return QStringLiteral("Mesh"); - default: return QStringLiteral("Unknown"); - }; -} - -QString NMConnectionStateReason::toString(NMConnectionStateReason::Enum reason) { - switch (reason) { - case Unknown: return QStringLiteral("Unknown"); - case None: return QStringLiteral("No reason"); - case UserDisconnected: return QStringLiteral("User disconnection"); - case DeviceDisconnected: - return QStringLiteral("The device the connection was using was disconnected."); - case ServiceStopped: - return QStringLiteral("The service providing the VPN connection was stopped."); - case IpConfigInvalid: - return QStringLiteral("The IP config of the active connection was invalid."); - case ConnectTimeout: - return QStringLiteral("The connection attempt to the VPN service timed out."); - case ServiceStartTimeout: - return QStringLiteral( - "A timeout occurred while starting the service providing the VPN connection." - ); - case ServiceStartFailed: - return QStringLiteral("Starting the service providing the VPN connection failed."); - case NoSecrets: return QStringLiteral("Necessary secrets for the connection were not provided."); - case LoginFailed: return QStringLiteral("Authentication to the server failed."); - case ConnectionRemoved: - return QStringLiteral("Necessary secrets for the connection were not provided."); - case DependencyFailed: - return QStringLiteral("Master connection of this connection failed to activate."); - case DeviceRealizeFailed: return QStringLiteral("Could not create the software device link."); - case DeviceRemoved: return QStringLiteral("The device this connection depended on disappeared."); - default: return QStringLiteral("Unknown"); - }; -}; - -WifiNetwork::WifiNetwork(QString ssid, QObject* parent): Network(std::move(ssid), parent) {}; - -void WifiNetwork::connect() { - if (this->bConnected) { - qCCritical(logWifi) << this << "is already connected."; - return; - } - - this->requestConnect(); -} - -void WifiNetwork::disconnect() { - if (!this->bConnected) { - qCCritical(logWifi) << this << "is not currently connected"; - return; - } - - this->requestDisconnect(); -} - -void WifiNetwork::forget() { this->requestForget(); } - -WifiDevice::WifiDevice(QObject* parent): NetworkDevice(DeviceType::Wifi, parent) {}; - -void WifiDevice::setScannerEnabled(bool enabled) { - if (this->bScannerEnabled == enabled) return; - this->bScannerEnabled = enabled; -} - -void WifiDevice::networkAdded(WifiNetwork* net) { this->mNetworks.insertObject(net); } -void WifiDevice::networkRemoved(WifiNetwork* net) { this->mNetworks.removeObject(net); } - -} // namespace qs::network - -QDebug operator<<(QDebug debug, const qs::network::WifiNetwork* network) { - auto saver = QDebugStateSaver(debug); - - if (network) { - debug.nospace() << "WifiNetwork(" << static_cast(network) - << ", name=" << network->name() << ")"; - } else { - debug << "WifiNetwork(nullptr)"; - } - - return debug; -} - -QDebug operator<<(QDebug debug, const qs::network::WifiDevice* device) { - auto saver = QDebugStateSaver(debug); - - if (device) { - debug.nospace() << "WifiDevice(" << static_cast(device) - << ", name=" << device->name() << ")"; - } else { - debug << "WifiDevice(nullptr)"; - } - - return debug; -} diff --git a/src/network/wifi.hpp b/src/network/wifi.hpp deleted file mode 100644 index 15b093d..0000000 --- a/src/network/wifi.hpp +++ /dev/null @@ -1,186 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "../core/model.hpp" -#include "device.hpp" -#include "network.hpp" - -namespace qs::network { - -///! The security type of a wifi network. -class WifiSecurityType: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - Wpa3SuiteB192 = 0, - Sae = 1, - Wpa2Eap = 2, - Wpa2Psk = 3, - WpaEap = 4, - WpaPsk = 5, - StaticWep = 6, - DynamicWep = 7, - Leap = 8, - Owe = 9, - Open = 10, - Unknown = 11, - }; - Q_ENUM(Enum); - Q_INVOKABLE static QString toString(WifiSecurityType::Enum type); -}; - -///! The 802.11 mode of a wifi device. -class WifiDeviceMode: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - /// The device is part of an Ad-Hoc network without a central access point. - AdHoc = 0, - /// The device is a station that can connect to networks. - Station = 1, - /// The device is a local hotspot/access point. - AccessPoint = 2, - /// The device is an 802.11s mesh point. - Mesh = 3, - /// The device mode is unknown. - Unknown = 4, - }; - Q_ENUM(Enum); - Q_INVOKABLE static QString toString(WifiDeviceMode::Enum mode); -}; - -///! NetworkManager-specific reason for a WifiNetworks connection state. -/// In sync with https://networkmanager.dev/docs/api/latest/nm-dbus-types.html#NMActiveConnectionStateReason. -class NMConnectionStateReason: public QObject { - Q_OBJECT; - QML_ELEMENT; - QML_SINGLETON; - -public: - enum Enum : quint8 { - Unknown = 0, - None = 1, - UserDisconnected = 2, - DeviceDisconnected = 3, - ServiceStopped = 4, - IpConfigInvalid = 5, - ConnectTimeout = 6, - ServiceStartTimeout = 7, - ServiceStartFailed = 8, - NoSecrets = 9, - LoginFailed = 10, - ConnectionRemoved = 11, - DependencyFailed = 12, - DeviceRealizeFailed = 13, - DeviceRemoved = 14 - }; - Q_ENUM(Enum); - Q_INVOKABLE static QString toString(NMConnectionStateReason::Enum reason); -}; - -///! An available wifi network. -class WifiNetwork: public Network { - Q_OBJECT; - QML_ELEMENT; - QML_UNCREATABLE("WifiNetwork can only be acquired through WifiDevice"); - // clang-format off - /// The current signal strength of the network, from 0.0 to 1.0. - Q_PROPERTY(qreal signalStrength READ default NOTIFY signalStrengthChanged BINDABLE bindableSignalStrength); - /// True if the wifi network has known connection settings saved. - Q_PROPERTY(bool known READ default NOTIFY knownChanged BINDABLE bindableKnown); - /// The security type of the wifi network. - Q_PROPERTY(WifiSecurityType::Enum security READ default NOTIFY securityChanged BINDABLE bindableSecurity); - /// A specific reason for the connection state when the backend is NetworkManager. - Q_PROPERTY(NMConnectionStateReason::Enum nmReason READ default NOTIFY nmReasonChanged BINDABLE bindableNmReason); - // clang-format on - -public: - explicit WifiNetwork(QString ssid, QObject* parent = nullptr); - - /// Attempt to connect to the wifi network. - /// - /// > [!WARNING] Quickshell does not yet provide a NetworkManager authentication agent, - /// > meaning another agent will need to be active to enter passwords for unsaved networks. - Q_INVOKABLE void connect(); - /// Disconnect from the wifi network. - Q_INVOKABLE void disconnect(); - /// Forget all connection settings for this wifi network. - Q_INVOKABLE void forget(); - - QBindable bindableSignalStrength() { return &this->bSignalStrength; } - QBindable bindableKnown() { return &this->bKnown; } - QBindable bindableNmReason() { return &this->bNmReason; } - QBindable bindableSecurity() { return &this->bSecurity; } - -signals: - void requestConnect(); - void requestDisconnect(); - void requestForget(); - void signalStrengthChanged(); - void knownChanged(); - void securityChanged(); - void nmReasonChanged(); - -private: - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(WifiNetwork, qreal, bSignalStrength, &WifiNetwork::signalStrengthChanged); - Q_OBJECT_BINDABLE_PROPERTY(WifiNetwork, bool, bKnown, &WifiNetwork::knownChanged); - Q_OBJECT_BINDABLE_PROPERTY(WifiNetwork, NMConnectionStateReason::Enum, bNmReason, &WifiNetwork::nmReasonChanged); - Q_OBJECT_BINDABLE_PROPERTY(WifiNetwork, WifiSecurityType::Enum, bSecurity, &WifiNetwork::securityChanged); - // clang-format on -}; - -///! Wireless variant of a NetworkDevice. -class WifiDevice: public NetworkDevice { - Q_OBJECT; - QML_ELEMENT; - QML_UNCREATABLE(""); - - // clang-format off - /// A list of this available and connected wifi networks. - QSDOC_TYPE_OVERRIDE(ObjectModel*); - Q_PROPERTY(UntypedObjectModel* networks READ networks CONSTANT); - /// True when currently scanning for networks. - /// When enabled, the scanner populates the device with an active list of available wifi networks. - Q_PROPERTY(bool scannerEnabled READ scannerEnabled WRITE setScannerEnabled NOTIFY scannerEnabledChanged BINDABLE bindableScannerEnabled); - /// The 802.11 mode the device is in. - Q_PROPERTY(WifiDeviceMode::Enum mode READ default NOTIFY modeChanged BINDABLE bindableMode); - // clang-format on - -public: - explicit WifiDevice(QObject* parent = nullptr); - - void networkAdded(WifiNetwork* net); - void networkRemoved(WifiNetwork* net); - - [[nodiscard]] ObjectModel* networks() { return &this->mNetworks; }; - QBindable bindableScannerEnabled() { return &this->bScannerEnabled; }; - [[nodiscard]] bool scannerEnabled() const { return this->bScannerEnabled; }; - void setScannerEnabled(bool enabled); - QBindable bindableMode() { return &this->bMode; } - -signals: - void modeChanged(); - void scannerEnabledChanged(bool enabled); - -private: - ObjectModel mNetworks {this}; - Q_OBJECT_BINDABLE_PROPERTY(WifiDevice, bool, bScannerEnabled, &WifiDevice::scannerEnabledChanged); - Q_OBJECT_BINDABLE_PROPERTY(WifiDevice, WifiDeviceMode::Enum, bMode, &WifiDevice::modeChanged); -}; - -}; // namespace qs::network - -QDebug operator<<(QDebug debug, const qs::network::WifiNetwork* network); -QDebug operator<<(QDebug debug, const qs::network::WifiDevice* device); diff --git a/src/services/CMakeLists.txt b/src/services/CMakeLists.txt index f3912a9..5ab5c55 100644 --- a/src/services/CMakeLists.txt +++ b/src/services/CMakeLists.txt @@ -14,10 +14,6 @@ if (SERVICE_PAM) add_subdirectory(pam) endif() -if (SERVICE_POLKIT) - add_subdirectory(polkit) -endif() - if (SERVICE_GREETD) add_subdirectory(greetd) endif() diff --git a/src/services/greetd/connection.cpp b/src/services/greetd/connection.cpp index 7130870..bf0d1fd 100644 --- a/src/services/greetd/connection.cpp +++ b/src/services/greetd/connection.cpp @@ -199,8 +199,7 @@ void GreetdConnection::onSocketReady() { // Special case this error in case a session was already running. // This cancels and restarts the session. if (errorType == "error" && desc == "a session is already being configured") { - qCDebug( - logGreetd + qCDebug(logGreetd ) << "A session was already in progress, cancelling it and starting a new one."; this->setActive(false); this->setActive(true); @@ -226,10 +225,6 @@ void GreetdConnection::onSocketReady() { this->mResponseRequired = responseRequired; emit this->authMessage(message, error, responseRequired, echoResponse); - - if (!responseRequired) { - this->sendRequest({{"type", "post_auth_message_response"}}); - } } else goto unexpected; return; diff --git a/src/services/mpris/player.cpp b/src/services/mpris/player.cpp index fe8b349..45d5cd4 100644 --- a/src/services/mpris/player.cpp +++ b/src/services/mpris/player.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -100,12 +99,43 @@ MprisPlayer::MprisPlayer(const QString& address, QObject* parent): QObject(paren } else return static_cast(-1); }); - this->bLengthSupported.setBinding([this]() { return this->bInternalLength.value() != -1; }); + this->bLengthSupported.setBinding([this]() { return this->bInternalLength != -1; }); + + this->bPlaybackState.setBinding([this]() { + const auto& status = this->bpPlaybackStatus.value(); + + if (status == "Playing") { + return MprisPlaybackState::Playing; + } else if (status == "Paused") { + this->pausedTime = QDateTime::currentDateTimeUtc(); + return MprisPlaybackState::Paused; + } else if (status == "Stopped") { + return MprisPlaybackState::Stopped; + } else { + qWarning() << "Received unexpected PlaybackStatus for" << this << status; + return MprisPlaybackState::Stopped; + } + }); this->bIsPlaying.setBinding([this]() { return this->bPlaybackState == MprisPlaybackState::Playing; }); + this->bLoopState.setBinding([this]() { + const auto& status = this->bpLoopStatus.value(); + + if (status == "None") { + return MprisLoopState::None; + } else if (status == "Track") { + return MprisLoopState::Track; + } else if (status == "Playlist") { + return MprisLoopState::Playlist; + } else { + qWarning() << "Received unexpected LoopStatus for" << this << status; + return MprisLoopState::None; + } + }); + // clang-format off QObject::connect(this->player, &DBusMprisPlayer::Seeked, this, &MprisPlayer::onSeek); QObject::connect(&this->playerProperties, &DBusPropertyGroup::getAllFinished, this, &MprisPlayer::onGetAllFinished); @@ -378,7 +408,7 @@ void MprisPlayer::onPlaybackStatusUpdated() { // For exceptionally bad players that update playback timestamps at an indeterminate time AFTER // updating playback state. (Youtube) - QTimer::singleShot(100, this, [this]() { this->pPosition.requestUpdate(); }); + QTimer::singleShot(100, this, [&]() { this->pPosition.requestUpdate(); }); // For exceptionally bad players that don't update length (or other metadata) until a new track actually // starts playing, and then don't trigger a metadata update when they do. (Jellyfin) @@ -402,11 +432,18 @@ void MprisPlayer::setLoopState(MprisLoopState::Enum loopState) { } if (loopState == this->bLoopState) return; - if (loopState < MprisLoopState::None || loopState > MprisLoopState::Playlist) { + + QString loopStatusStr; + switch (loopState) { + case MprisLoopState::None: loopStatusStr = "None"; break; + case MprisLoopState::Track: loopStatusStr = "Track"; break; + case MprisLoopState::Playlist: loopStatusStr = "Playlist"; break; + default: qWarning() << "Cannot set loopState of" << this << "to unknown value" << loopState; + return; } - this->bLoopState = loopState; + this->bpLoopStatus = loopStatusStr; this->pLoopStatus.write(); } @@ -459,43 +496,3 @@ void MprisPlayer::onGetAllFinished() { } } // namespace qs::service::mpris - -namespace qs::dbus { - -using namespace qs::service::mpris; - -DBusResult -DBusDataTransform::fromWire(const QString& wire) { - if (wire == "Playing") return MprisPlaybackState::Playing; - if (wire == "Paused") return MprisPlaybackState::Paused; - if (wire == "Stopped") return MprisPlaybackState::Stopped; - return QDBusError(QDBusError::InvalidArgs, QString("Invalid MprisPlaybackState: %1").arg(wire)); -} - -QString DBusDataTransform::toWire(MprisPlaybackState::Enum data) { - switch (data) { - case MprisPlaybackState::Playing: return "Playing"; - case MprisPlaybackState::Paused: return "Paused"; - case MprisPlaybackState::Stopped: return "Stopped"; - default: qFatal() << "Tried to convert an invalid MprisPlaybackState to String"; return QString(); - } -} - -DBusResult -DBusDataTransform::fromWire(const QString& wire) { - if (wire == "None") return MprisLoopState::None; - if (wire == "Track") return MprisLoopState::Track; - if (wire == "Playlist") return MprisLoopState::Playlist; - return QDBusError(QDBusError::InvalidArgs, QString("Invalid MprisLoopState: %1").arg(wire)); -} - -QString DBusDataTransform::toWire(MprisLoopState::Enum data) { - switch (data) { - case MprisLoopState::None: return "None"; - case MprisLoopState::Track: return "Track"; - case MprisLoopState::Playlist: return "Playlist"; - default: qFatal() << "Tried to convert an invalid MprisLoopState to String"; return QString(); - } -} - -} // namespace qs::dbus diff --git a/src/services/mpris/player.hpp b/src/services/mpris/player.hpp index 423453d..89bc27a 100644 --- a/src/services/mpris/player.hpp +++ b/src/services/mpris/player.hpp @@ -51,30 +51,6 @@ public: Q_INVOKABLE static QString toString(qs::service::mpris::MprisLoopState::Enum status); }; -}; // namespace qs::service::mpris - -namespace qs::dbus { - -template <> -struct DBusDataTransform { - using Wire = QString; - using Data = qs::service::mpris::MprisPlaybackState::Enum; - static DBusResult fromWire(const QString& wire); - static QString toWire(Data data); -}; - -template <> -struct DBusDataTransform { - using Wire = QString; - using Data = qs::service::mpris::MprisLoopState::Enum; - static DBusResult fromWire(const QString& wire); - static QString toWire(Data data); -}; - -}; // namespace qs::dbus - -namespace qs::service::mpris { - ///! A media player exposed over MPRIS. /// A media player exposed over MPRIS. /// @@ -274,23 +250,23 @@ public: [[nodiscard]] bool isValid() const; [[nodiscard]] QString address() const; - [[nodiscard]] QBindable bindableCanControl() const { return &this->bCanControl; } - [[nodiscard]] QBindable bindableCanSeek() const { return &this->bCanSeek; } - [[nodiscard]] QBindable bindableCanGoNext() const { return &this->bCanGoNext; } - [[nodiscard]] QBindable bindableCanGoPrevious() const { return &this->bCanGoPrevious; } - [[nodiscard]] QBindable bindableCanPlay() const { return &this->bCanPlay; } - [[nodiscard]] QBindable bindableCanPause() const { return &this->bCanPause; } + [[nodiscard]] QBindable bindableCanControl() const { return &this->bCanControl; }; + [[nodiscard]] QBindable bindableCanSeek() const { return &this->bCanSeek; }; + [[nodiscard]] QBindable bindableCanGoNext() const { return &this->bCanGoNext; }; + [[nodiscard]] QBindable bindableCanGoPrevious() const { return &this->bCanGoPrevious; }; + [[nodiscard]] QBindable bindableCanPlay() const { return &this->bCanPlay; }; + [[nodiscard]] QBindable bindableCanPause() const { return &this->bCanPause; }; [[nodiscard]] QBindable bindableCanTogglePlaying() const { return &this->bCanTogglePlaying; - } - [[nodiscard]] QBindable bindableCanQuit() const { return &this->bCanQuit; } - [[nodiscard]] QBindable bindableCanRaise() const { return &this->bCanRaise; } + }; + [[nodiscard]] QBindable bindableCanQuit() const { return &this->bCanQuit; }; + [[nodiscard]] QBindable bindableCanRaise() const { return &this->bCanRaise; }; [[nodiscard]] QBindable bindableCanSetFullscreen() const { return &this->bCanSetFullscreen; - } + }; - [[nodiscard]] QBindable bindableIdentity() const { return &this->bIdentity; } - [[nodiscard]] QBindable bindableDesktopEntry() const { return &this->bDesktopEntry; } + [[nodiscard]] QBindable bindableIdentity() const { return &this->bIdentity; }; + [[nodiscard]] QBindable bindableDesktopEntry() const { return &this->bDesktopEntry; }; [[nodiscard]] qlonglong positionMs() const; [[nodiscard]] qreal position() const; @@ -300,49 +276,49 @@ public: [[nodiscard]] qreal length() const; [[nodiscard]] QBindable bindableLengthSupported() const { return &this->bLengthSupported; } - [[nodiscard]] qreal volume() const { return this->bVolume; } + [[nodiscard]] qreal volume() const { return this->bVolume; }; [[nodiscard]] bool volumeSupported() const; void setVolume(qreal volume); - [[nodiscard]] QBindable bindableUniqueId() const { return &this->bUniqueId; } - [[nodiscard]] QBindable bindableMetadata() const { return &this->bMetadata; } - [[nodiscard]] QBindable bindableTrackTitle() const { return &this->bTrackTitle; } - [[nodiscard]] QBindable bindableTrackAlbum() const { return &this->bTrackAlbum; } + [[nodiscard]] QBindable bindableUniqueId() const { return &this->bUniqueId; }; + [[nodiscard]] QBindable bindableMetadata() const { return &this->bMetadata; }; + [[nodiscard]] QBindable bindableTrackTitle() const { return &this->bTrackTitle; }; + [[nodiscard]] QBindable bindableTrackAlbum() const { return &this->bTrackAlbum; }; [[nodiscard]] QBindable bindableTrackAlbumArtist() const { return &this->bTrackAlbumArtist; - } - [[nodiscard]] QBindable bindableTrackArtist() const { return &this->bTrackArtist; } - [[nodiscard]] QBindable bindableTrackArtUrl() const { return &this->bTrackArtUrl; } + }; + [[nodiscard]] QBindable bindableTrackArtist() const { return &this->bTrackArtist; }; + [[nodiscard]] QBindable bindableTrackArtUrl() const { return &this->bTrackArtUrl; }; - [[nodiscard]] MprisPlaybackState::Enum playbackState() const { return this->bPlaybackState; } + [[nodiscard]] MprisPlaybackState::Enum playbackState() const { return this->bPlaybackState; }; void setPlaybackState(MprisPlaybackState::Enum playbackState); - [[nodiscard]] bool isPlaying() const { return this->bIsPlaying; } + [[nodiscard]] bool isPlaying() const { return this->bIsPlaying; }; void setPlaying(bool playing); - [[nodiscard]] MprisLoopState::Enum loopState() const { return this->bLoopState; } + [[nodiscard]] MprisLoopState::Enum loopState() const { return this->bLoopState; }; [[nodiscard]] bool loopSupported() const; void setLoopState(MprisLoopState::Enum loopState); - [[nodiscard]] qreal rate() const { return this->bRate; } - [[nodiscard]] QBindable bindableMinRate() const { return &this->bRate; } - [[nodiscard]] QBindable bindableMaxRate() const { return &this->bRate; } + [[nodiscard]] qreal rate() const { return this->bRate; }; + [[nodiscard]] QBindable bindableMinRate() const { return &this->bRate; }; + [[nodiscard]] QBindable bindableMaxRate() const { return &this->bRate; }; void setRate(qreal rate); - [[nodiscard]] bool shuffle() const { return this->bShuffle; } + [[nodiscard]] bool shuffle() const { return this->bShuffle; }; [[nodiscard]] bool shuffleSupported() const; void setShuffle(bool shuffle); - [[nodiscard]] bool fullscreen() const { return this->bFullscreen; } + [[nodiscard]] bool fullscreen() const { return this->bFullscreen; }; void setFullscreen(bool fullscreen); [[nodiscard]] QBindable> bindableSupportedUriSchemes() const { return &this->bSupportedUriSchemes; - } + }; [[nodiscard]] QBindable> bindableSupportedMimeTypes() const { return &this->bSupportedMimeTypes; - } + }; signals: /// The track has changed. @@ -414,7 +390,7 @@ private: void onPlaybackStatusUpdated(); // call instead of setting bpPosition void setPosition(qlonglong position); - void requestPositionUpdate() { this->pPosition.requestUpdate(); } + void requestPositionUpdate() { this->pPosition.requestUpdate(); }; // clang-format off Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, QString, bIdentity, &MprisPlayer::identityChanged); @@ -428,13 +404,13 @@ private: QS_DBUS_BINDABLE_PROPERTY_GROUP(MprisPlayer, appProperties); QS_DBUS_PROPERTY_BINDING(MprisPlayer, pIdentity, bIdentity, appProperties, "Identity"); - QS_DBUS_PROPERTY_BINDING(MprisPlayer, pDesktopEntry, bDesktopEntry, appProperties, "DesktopEntry", false); - QS_DBUS_PROPERTY_BINDING(MprisPlayer, pCanQuit, bCanQuit, appProperties, "CanQuit", false); - QS_DBUS_PROPERTY_BINDING(MprisPlayer, pCanRaise, bCanRaise, appProperties, "CanRaise", false); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pDesktopEntry, bDesktopEntry, appProperties, "DesktopEntry"); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pCanQuit, bCanQuit, appProperties, "CanQuit"); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pCanRaise, bCanRaise, appProperties, "CanRaise"); QS_DBUS_PROPERTY_BINDING(MprisPlayer, pFullscreen, bFullscreen, appProperties, "Fullscreen", false); QS_DBUS_PROPERTY_BINDING(MprisPlayer, pCanSetFullscreen, bCanSetFullscreen, appProperties, "CanSetFullscreen", false); - QS_DBUS_PROPERTY_BINDING(MprisPlayer, pSupportedUriSchemes, bSupportedUriSchemes, appProperties, "SupportedUriSchemes", false); - QS_DBUS_PROPERTY_BINDING(MprisPlayer, pSupportedMimeTypes, bSupportedMimeTypes, appProperties, "SupportedMimeTypes", false); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pSupportedUriSchemes, bSupportedUriSchemes, appProperties, "SupportedUriSchemes"); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pSupportedMimeTypes, bSupportedMimeTypes, appProperties, "SupportedMimeTypes"); Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, bool, bpCanPlay); Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, bool, bpCanPause); @@ -444,6 +420,8 @@ private: Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, QVariantMap, bpMetadata); QS_BINDING_SUBSCRIBE_METHOD(MprisPlayer, bpMetadata, onMetadataChanged, onValueChanged); Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(MprisPlayer, qlonglong, bpPosition, -1, &MprisPlayer::positionChanged); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, QString, bpPlaybackStatus); + Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, QString, bpLoopStatus); Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, bool, bCanControl, &MprisPlayer::canControlChanged); Q_OBJECT_BINDABLE_PROPERTY(MprisPlayer, bool, bCanPlay, &MprisPlayer::canPlayChanged); @@ -482,8 +460,8 @@ private: QS_DBUS_PROPERTY_BINDING(MprisPlayer, qlonglong, pPosition, bpPosition, onPositionUpdated, playerProperties, "Position", false); QS_DBUS_PROPERTY_BINDING(MprisPlayer, pVolume, bVolume, playerProperties, "Volume", false); QS_DBUS_PROPERTY_BINDING(MprisPlayer, pMetadata, bpMetadata, playerProperties, "Metadata"); - QS_DBUS_PROPERTY_BINDING(MprisPlayer, void, pPlaybackStatus, bPlaybackState, onPlaybackStatusUpdated, playerProperties, "PlaybackStatus", true); - QS_DBUS_PROPERTY_BINDING(MprisPlayer, pLoopStatus, bLoopState, playerProperties, "LoopStatus", false); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, void, pPlaybackStatus, bpPlaybackStatus, onPlaybackStatusUpdated, playerProperties, "PlaybackStatus", true); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, pLoopStatus, bpLoopStatus, playerProperties, "LoopStatus", false); QS_DBUS_PROPERTY_BINDING(MprisPlayer, pRate, bRate, playerProperties, "Rate", false); QS_DBUS_PROPERTY_BINDING(MprisPlayer, pMinRate, bMinRate, playerProperties, "MinimumRate", false); QS_DBUS_PROPERTY_BINDING(MprisPlayer, pMaxRate, bMaxRate, playerProperties, "MaximumRate", false); diff --git a/src/services/mpris/watcher.hpp b/src/services/mpris/watcher.hpp index cbe9669..bd922cd 100644 --- a/src/services/mpris/watcher.hpp +++ b/src/services/mpris/watcher.hpp @@ -51,7 +51,7 @@ class MprisQml: public QObject { Q_PROPERTY(UntypedObjectModel* players READ players CONSTANT); public: - explicit MprisQml(QObject* parent = nullptr): QObject(parent) {} + explicit MprisQml(QObject* parent = nullptr): QObject(parent) {}; [[nodiscard]] ObjectModel* players(); }; diff --git a/src/services/notifications/CMakeLists.txt b/src/services/notifications/CMakeLists.txt index 58b6648..0cbb42e 100644 --- a/src/services/notifications/CMakeLists.txt +++ b/src/services/notifications/CMakeLists.txt @@ -23,6 +23,7 @@ qt_add_qml_module(quickshell-service-notifications ) qs_add_module_deps_light(quickshell-service-notifications Quickshell) + install_qml_module(quickshell-service-notifications) target_link_libraries(quickshell-service-notifications PRIVATE Qt::Quick Qt::DBus) diff --git a/src/services/notifications/dbusimage.cpp b/src/services/notifications/dbusimage.cpp index 469d08c..e6c091b 100644 --- a/src/services/notifications/dbusimage.cpp +++ b/src/services/notifications/dbusimage.cpp @@ -42,9 +42,10 @@ const QDBusArgument& operator>>(const QDBusArgument& argument, DBusNotificationI } else if (channels != (pixmap.hasAlpha ? 4 : 3)) { qCWarning(logNotifications) << "Unable to parse pixmap as channel count is incorrect." << "Got " << channels << "expected" << (pixmap.hasAlpha ? 4 : 3); - } else if (rowstride != pixmap.width * channels) { + } else if (rowstride != pixmap.width * sampleBits * channels) { qCWarning(logNotifications) << "Unable to parse pixmap as rowstride is incorrect. Got" - << rowstride << "expected" << (pixmap.width * channels); + << rowstride << "expected" + << (pixmap.width * sampleBits * channels); } return argument; @@ -54,7 +55,7 @@ const QDBusArgument& operator<<(QDBusArgument& argument, const DBusNotificationI argument.beginStructure(); argument << pixmap.width; argument << pixmap.height; - argument << pixmap.width * (pixmap.hasAlpha ? 4 : 3); + argument << pixmap.width * (pixmap.hasAlpha ? 4 : 3) * 8; argument << pixmap.hasAlpha; argument << 8; argument << (pixmap.hasAlpha ? 4 : 3); diff --git a/src/services/notifications/notification.cpp b/src/services/notifications/notification.cpp index d048bde..96a2ff0 100644 --- a/src/services/notifications/notification.cpp +++ b/src/services/notifications/notification.cpp @@ -78,29 +78,6 @@ void Notification::close(NotificationCloseReason::Enum reason) { } } -void Notification::sendInlineReply(const QString& replyText) { - if (!NotificationServer::instance()->support.inlineReply) { - qCritical() << "Inline reply support disabled on server"; - return; - } - - if (!this->bHasInlineReply) { - qCritical() << "Cannot send reply to notification without inline-reply action"; - return; - } - - if (this->isRetained()) { - qCritical() << "Cannot send reply to destroyed notification" << this; - return; - } - - NotificationServer::instance()->NotificationReplied(this->id(), replyText); - - if (!this->bindableResident().value()) { - this->close(NotificationCloseReason::Dismissed); - } -} - void Notification::updateProperties( const QString& appName, QString appIcon, @@ -127,7 +104,7 @@ void Notification::updateProperties( if (appIcon.isEmpty() && !this->bDesktopEntry.value().isEmpty()) { if (auto* entry = DesktopEntryManager::instance()->byId(this->bDesktopEntry.value())) { - appIcon = entry->bIcon.value(); + appIcon = entry->mIcon; } } @@ -170,27 +147,17 @@ void Notification::updateProperties( this->bImage = imagePath; this->bHints = hints; + Qt::endPropertyUpdateGroup(); + bool actionsChanged = false; auto deletedActions = QVector(); if (actions.length() % 2 == 0) { int ai = 0; for (auto i = 0; i != actions.length(); i += 2) { + ai = i / 2; const auto& identifier = actions.at(i); const auto& text = actions.at(i + 1); - - if (identifier == "inline-reply" && NotificationServer::instance()->support.inlineReply) { - if (this->bHasInlineReply) { - qCWarning(logNotifications) << this << '(' << appName << ')' - << "sent an action set with duplicate inline-reply actions."; - } else { - this->bHasInlineReply = true; - this->bInlineReplyPlaceholder = text; - } - // skip inserting this action into action list - continue; - } - auto* action = ai < this->mActions.length() ? this->mActions.at(ai) : nullptr; if (action && identifier == action->identifier()) { @@ -221,8 +188,6 @@ void Notification::updateProperties( << "sent an action set of an invalid length."; } - Qt::endPropertyUpdateGroup(); - if (actionsChanged) emit this->actionsChanged(); for (auto* action: deletedActions) { diff --git a/src/services/notifications/notification.hpp b/src/services/notifications/notification.hpp index 7f5246c..f0c65bb 100644 --- a/src/services/notifications/notification.hpp +++ b/src/services/notifications/notification.hpp @@ -107,12 +107,6 @@ class Notification /// /// This image is often something like a profile picture in instant messaging applications. Q_PROPERTY(QString image READ default NOTIFY imageChanged BINDABLE bindableImage); - /// If true, the notification has an inline reply action. - /// - /// A quick reply text field should be displayed and the reply can be sent using @@sendInlineReply(). - Q_PROPERTY(bool hasInlineReply READ default NOTIFY hasInlineReplyChanged BINDABLE bindableHasInlineReply); - /// The placeholder text/button caption for the inline reply. - Q_PROPERTY(QString inlineReplyPlaceholder READ default NOTIFY inlineReplyPlaceholderChanged BINDABLE bindableInlineReplyPlaceholder); /// All hints sent by the client application as a javascript object. /// Many common hints are exposed via other properties. Q_PROPERTY(QVariantMap hints READ default NOTIFY hintsChanged BINDABLE bindableHints); @@ -130,12 +124,6 @@ public: /// explicitly closed by the user. Q_INVOKABLE void dismiss(); - /// Send an inline reply to the notification with an inline reply action. - /// > [!WARNING] This method can only be called if - /// > @@hasInlineReply is true - /// > and the server has @@NotificationServer.inlineReplySupported set to true. - Q_INVOKABLE void sendInlineReply(const QString& replyText); - void updateProperties( const QString& appName, QString appIcon, @@ -154,29 +142,23 @@ public: [[nodiscard]] bool isLastGeneration() const; void setLastGeneration(); - [[nodiscard]] QBindable bindableExpireTimeout() const { return &this->bExpireTimeout; } - [[nodiscard]] QBindable bindableAppName() const { return &this->bAppName; } - [[nodiscard]] QBindable bindableAppIcon() const { return &this->bAppIcon; } - [[nodiscard]] QBindable bindableSummary() const { return &this->bSummary; } - [[nodiscard]] QBindable bindableBody() const { return &this->bBody; } + [[nodiscard]] QBindable bindableExpireTimeout() const { return &this->bExpireTimeout; }; + [[nodiscard]] QBindable bindableAppName() const { return &this->bAppName; }; + [[nodiscard]] QBindable bindableAppIcon() const { return &this->bAppIcon; }; + [[nodiscard]] QBindable bindableSummary() const { return &this->bSummary; }; + [[nodiscard]] QBindable bindableBody() const { return &this->bBody; }; [[nodiscard]] QBindable bindableUrgency() const { return &this->bUrgency; - } + }; [[nodiscard]] QList actions() const; - [[nodiscard]] QBindable bindableHasActionIcons() const { return &this->bHasActionIcons; } - [[nodiscard]] QBindable bindableResident() const { return &this->bResident; } - [[nodiscard]] QBindable bindableTransient() const { return &this->bTransient; } - [[nodiscard]] QBindable bindableDesktopEntry() const { return &this->bDesktopEntry; } - [[nodiscard]] QBindable bindableImage() const { return &this->bImage; } - [[nodiscard]] QBindable bindableHasInlineReply() const { return &this->bHasInlineReply; } - - [[nodiscard]] QBindable bindableInlineReplyPlaceholder() const { - return &this->bInlineReplyPlaceholder; - } - - [[nodiscard]] QBindable bindableHints() const { return &this->bHints; } + [[nodiscard]] QBindable bindableHasActionIcons() const { return &this->bHasActionIcons; }; + [[nodiscard]] QBindable bindableResident() const { return &this->bResident; }; + [[nodiscard]] QBindable bindableTransient() const { return &this->bTransient; }; + [[nodiscard]] QBindable bindableDesktopEntry() const { return &this->bDesktopEntry; }; + [[nodiscard]] QBindable bindableImage() const { return &this->bImage; }; + [[nodiscard]] QBindable bindableHints() const { return &this->bHints; }; [[nodiscard]] NotificationCloseReason::Enum closeReason() const; void setTracked(bool tracked); @@ -200,8 +182,6 @@ signals: void transientChanged(); void desktopEntryChanged(); void imageChanged(); - void hasInlineReplyChanged(); - void inlineReplyPlaceholderChanged(); void hintsChanged(); private: @@ -222,8 +202,6 @@ private: Q_OBJECT_BINDABLE_PROPERTY(Notification, bool, bTransient, &Notification::transientChanged); Q_OBJECT_BINDABLE_PROPERTY(Notification, QString, bDesktopEntry, &Notification::desktopEntryChanged); Q_OBJECT_BINDABLE_PROPERTY(Notification, QString, bImage, &Notification::imageChanged); - Q_OBJECT_BINDABLE_PROPERTY(Notification, bool, bHasInlineReply, &Notification::hasInlineReplyChanged); - Q_OBJECT_BINDABLE_PROPERTY(Notification, QString, bInlineReplyPlaceholder, &Notification::inlineReplyPlaceholderChanged); Q_OBJECT_BINDABLE_PROPERTY(Notification, QVariantMap, bHints, &Notification::hintsChanged); // clang-format on diff --git a/src/services/notifications/org.freedesktop.Notifications.xml b/src/services/notifications/org.freedesktop.Notifications.xml index 3d99db0..1a2001f 100644 --- a/src/services/notifications/org.freedesktop.Notifications.xml +++ b/src/services/notifications/org.freedesktop.Notifications.xml @@ -38,11 +38,6 @@ - - - - - diff --git a/src/services/notifications/qml.cpp b/src/services/notifications/qml.cpp index 42bb23a..9981821 100644 --- a/src/services/notifications/qml.cpp +++ b/src/services/notifications/qml.cpp @@ -115,15 +115,6 @@ void NotificationServerQml::setImageSupported(bool imageSupported) { emit this->imageSupportedChanged(); } -bool NotificationServerQml::inlineReplySupported() const { return this->support.inlineReply; } - -void NotificationServerQml::setInlineReplySupported(bool inlineReplySupported) { - if (inlineReplySupported == this->support.inlineReply) return; - this->support.inlineReply = inlineReplySupported; - this->updateSupported(); - emit this->inlineReplySupportedChanged(); -} - QVector NotificationServerQml::extraHints() const { return this->support.extraHints; } void NotificationServerQml::setExtraHints(QVector extraHints) { diff --git a/src/services/notifications/qml.hpp b/src/services/notifications/qml.hpp index 88132c7..feb33db 100644 --- a/src/services/notifications/qml.hpp +++ b/src/services/notifications/qml.hpp @@ -65,8 +65,6 @@ class NotificationServerQml: public PostReloadHook { Q_PROPERTY(bool actionIconsSupported READ actionIconsSupported WRITE setActionIconsSupported NOTIFY actionIconsSupportedChanged); /// If the notification server should advertise that it supports images. Defaults to false. Q_PROPERTY(bool imageSupported READ imageSupported WRITE setImageSupported NOTIFY imageSupportedChanged); - /// If the notification server should advertise that it supports inline replies. Defaults to false. - Q_PROPERTY(bool inlineReplySupported READ inlineReplySupported WRITE setInlineReplySupported NOTIFY inlineReplySupportedChanged); /// All notifications currently tracked by the server. QSDOC_TYPE_OVERRIDE(ObjectModel*); Q_PROPERTY(UntypedObjectModel* trackedNotifications READ trackedNotifications NOTIFY trackedNotificationsChanged); @@ -105,9 +103,6 @@ public: [[nodiscard]] bool imageSupported() const; void setImageSupported(bool imageSupported); - [[nodiscard]] bool inlineReplySupported() const; - void setInlineReplySupported(bool inlineReplySupported); - [[nodiscard]] QVector extraHints() const; void setExtraHints(QVector extraHints); @@ -128,7 +123,6 @@ signals: void actionsSupportedChanged(); void actionIconsSupportedChanged(); void imageSupportedChanged(); - void inlineReplySupportedChanged(); void extraHintsChanged(); void trackedNotificationsChanged(); diff --git a/src/services/notifications/server.cpp b/src/services/notifications/server.cpp index d2b55d0..18a898a 100644 --- a/src/services/notifications/server.cpp +++ b/src/services/notifications/server.cpp @@ -21,7 +21,7 @@ namespace qs::service::notifications { // NOLINTNEXTLINE(misc-use-internal-linkage) -QS_LOGGING_CATEGORY(logNotifications, "quickshell.service.notifications", QtWarningMsg); +QS_LOGGING_CATEGORY(logNotifications, "quickshell.service.notifications"); NotificationServer::NotificationServer() { qDBusRegisterMetaType(); @@ -117,12 +117,10 @@ void NotificationServer::tryRegister() { if (success) { qCInfo(logNotifications) << "Registered notification server with dbus."; } else { - qCWarning( - logNotifications + qCWarning(logNotifications ) << "Could not register notification server at org.freedesktop.Notifications, presumably " "because one is already registered."; - qCWarning( - logNotifications + qCWarning(logNotifications ) << "Registration will be attempted again if the active service is unregistered."; } } @@ -157,7 +155,6 @@ QStringList NotificationServer::GetCapabilities() const { } if (this->support.image) capabilities += "icon-static"; - if (this->support.inlineReply) capabilities += "inline-reply"; capabilities += this->support.extraHints; diff --git a/src/services/notifications/server.hpp b/src/services/notifications/server.hpp index 8bd92a3..8c20943 100644 --- a/src/services/notifications/server.hpp +++ b/src/services/notifications/server.hpp @@ -23,7 +23,6 @@ struct NotificationServerSupport { bool actions = false; bool actionIcons = false; bool image = false; - bool inlineReply = false; QVector extraHints; }; @@ -61,7 +60,6 @@ signals: // NOLINTBEGIN void NotificationClosed(quint32 id, quint32 reason); void ActionInvoked(quint32 id, QString action); - void NotificationReplied(quint32 id, QString replyText); // NOLINTEND private slots: diff --git a/src/services/pam/conversation.cpp b/src/services/pam/conversation.cpp index f8f5a09..6d27978 100644 --- a/src/services/pam/conversation.cpp +++ b/src/services/pam/conversation.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include "../../core/logcat.hpp" diff --git a/src/services/pam/qml.hpp b/src/services/pam/qml.hpp index a36184e..805e04c 100644 --- a/src/services/pam/qml.hpp +++ b/src/services/pam/qml.hpp @@ -6,11 +6,7 @@ #include #include #include -#ifdef __FreeBSD__ -#include -#else #include -#endif #include #include "conversation.hpp" @@ -21,9 +17,6 @@ class PamContext : public QObject , public QQmlParserStatus { Q_OBJECT; - QML_ELEMENT; - Q_INTERFACES(QQmlParserStatus); - // clang-format off /// If the pam context is actively performing an authentication. /// @@ -39,8 +32,6 @@ class PamContext /// /// The configuration directory is resolved relative to the current file if not an absolute path. /// - /// On FreeBSD this property is ignored as the pam configuration directory cannot be changed. - /// /// This property may not be set while @@active is true. Q_PROPERTY(QString configDirectory READ configDirectory WRITE setConfigDirectory NOTIFY configDirectoryChanged); /// The user to authenticate as. If unset the current user will be used. @@ -58,6 +49,7 @@ class PamContext /// If the user's response should be visible. Only valid when @@responseRequired is true. Q_PROPERTY(bool responseVisible READ isResponseVisible NOTIFY responseVisibleChanged); // clang-format on + QML_ELEMENT; public: explicit PamContext(QObject* parent = nullptr): QObject(parent) {} diff --git a/src/services/pam/subprocess.cpp b/src/services/pam/subprocess.cpp index dc36228..f99b279 100644 --- a/src/services/pam/subprocess.cpp +++ b/src/services/pam/subprocess.cpp @@ -7,11 +7,7 @@ #include #include #include -#ifdef __FreeBSD__ -#include -#else #include -#endif #include #include @@ -87,11 +83,7 @@ PamIpcExitCode PamSubprocess::exec(const char* configDir, const char* config, co logIf(this->log) << "Starting pam session for user \"" << user << "\" with config \"" << config << "\" in dir \"" << configDir << "\"" << std::endl; -#ifdef __FreeBSD__ - auto result = pam_start(config, user, &conv, &handle); -#else auto result = pam_start_confdir(config, user, &conv, configDir, &handle); -#endif if (result != PAM_SUCCESS) { logIf(true) << "Unable to start pam conversation with error \"" << pam_strerror(handle, result) diff --git a/src/services/pipewire/CMakeLists.txt b/src/services/pipewire/CMakeLists.txt index fe894c9..fddca6f 100644 --- a/src/services/pipewire/CMakeLists.txt +++ b/src/services/pipewire/CMakeLists.txt @@ -3,7 +3,6 @@ pkg_check_modules(pipewire REQUIRED IMPORTED_TARGET libpipewire-0.3) qt_add_library(quickshell-service-pipewire STATIC qml.cpp - peak.cpp core.cpp connection.cpp registry.cpp diff --git a/src/services/pipewire/connection.cpp b/src/services/pipewire/connection.cpp index c2f505f..ac4c5e6 100644 --- a/src/services/pipewire/connection.cpp +++ b/src/services/pipewire/connection.cpp @@ -1,135 +1,13 @@ #include "connection.hpp" -#include -#include -#include -#include -#include #include -#include -#include - -#include "../../core/logcat.hpp" -#include "core.hpp" namespace qs::service::pipewire { -namespace { -QS_LOGGING_CATEGORY(logConnection, "quickshell.service.pipewire.connection", QtWarningMsg); -} - PwConnection::PwConnection(QObject* parent): QObject(parent) { - this->runtimeDir = PwConnection::resolveRuntimeDir(); - - QObject::connect(&this->core, &PwCore::fatalError, this, &PwConnection::queueFatalError); - - if (!this->tryConnect(false) - && qEnvironmentVariableIntValue("QS_PIPEWIRE_IMMEDIATE_RECONNECT") == 1) - { - this->beginReconnect(); - } -} - -QString PwConnection::resolveRuntimeDir() { - auto runtimeDir = qEnvironmentVariable("PIPEWIRE_RUNTIME_DIR"); - if (runtimeDir.isEmpty()) { - runtimeDir = qEnvironmentVariable("XDG_RUNTIME_DIR"); - } - - if (runtimeDir.isEmpty()) { - runtimeDir = QString("/run/user/%1").arg(getuid()); - } - - return runtimeDir; -} - -void PwConnection::beginReconnect() { if (this->core.isValid()) { - this->stopSocketWatcher(); - return; + this->registry.init(this->core); } - - if (!qEnvironmentVariableIsEmpty("PIPEWIRE_REMOTE")) return; - - if (this->runtimeDir.isEmpty()) { - qCWarning( - logConnection - ) << "Cannot watch runtime dir for pipewire reconnects: runtime dir is empty."; - return; - } - - this->startSocketWatcher(); - this->tryConnect(true); -} - -bool PwConnection::tryConnect(bool retry) { - if (this->core.isValid()) return true; - - qCDebug(logConnection) << "Attempting reconnect..."; - if (!this->core.start(retry)) { - return false; - } - - qCInfo(logConnection) << "Connection established"; - this->stopSocketWatcher(); - - this->registry.init(this->core); - return true; -} - -void PwConnection::startSocketWatcher() { - if (this->socketWatcher != nullptr) return; - if (!qEnvironmentVariableIsEmpty("PIPEWIRE_REMOTE")) return; - - auto dir = QDir(this->runtimeDir); - if (!dir.exists()) { - qCWarning(logConnection) << "Cannot wait for a new pipewire socket, runtime dir does not exist:" - << this->runtimeDir; - return; - } - - this->socketWatcher = new QFileSystemWatcher(this); - this->socketWatcher->addPath(this->runtimeDir); - - QObject::connect( - this->socketWatcher, - &QFileSystemWatcher::directoryChanged, - this, - &PwConnection::onRuntimeDirChanged - ); -} - -void PwConnection::stopSocketWatcher() { - if (this->socketWatcher == nullptr) return; - - this->socketWatcher->deleteLater(); - this->socketWatcher = nullptr; -} - -void PwConnection::queueFatalError() { - if (this->fatalErrorQueued) return; - - this->fatalErrorQueued = true; - QMetaObject::invokeMethod(this, &PwConnection::onFatalError, Qt::QueuedConnection); -} - -void PwConnection::onFatalError() { - this->fatalErrorQueued = false; - - this->defaults.reset(); - this->registry.reset(); - this->core.shutdown(); - - this->beginReconnect(); -} - -void PwConnection::onRuntimeDirChanged(const QString& /*path*/) { - if (this->core.isValid()) { - this->stopSocketWatcher(); - return; - } - - this->tryConnect(true); } PwConnection* PwConnection::instance() { diff --git a/src/services/pipewire/connection.hpp b/src/services/pipewire/connection.hpp index d0374f8..2b3e860 100644 --- a/src/services/pipewire/connection.hpp +++ b/src/services/pipewire/connection.hpp @@ -1,13 +1,9 @@ #pragma once -#include - #include "core.hpp" #include "defaults.hpp" #include "registry.hpp" -class QFileSystemWatcher; - namespace qs::service::pipewire { class PwConnection: public QObject { @@ -22,23 +18,6 @@ public: static PwConnection* instance(); private: - static QString resolveRuntimeDir(); - - void beginReconnect(); - bool tryConnect(bool retry); - void startSocketWatcher(); - void stopSocketWatcher(); - -private slots: - void queueFatalError(); - void onFatalError(); - void onRuntimeDirChanged(const QString& path); - -private: - QString runtimeDir; - QFileSystemWatcher* socketWatcher = nullptr; - bool fatalErrorQueued = false; - // init/destroy order is important. do not rearrange. PwCore core; }; diff --git a/src/services/pipewire/core.cpp b/src/services/pipewire/core.cpp index 5077abe..22445aa 100644 --- a/src/services/pipewire/core.cpp +++ b/src/services/pipewire/core.cpp @@ -27,7 +27,7 @@ const pw_core_events PwCore::EVENTS = { .info = nullptr, .done = &PwCore::onSync, .ping = nullptr, - .error = &PwCore::onError, + .error = nullptr, .remove_id = nullptr, .bound_id = nullptr, .add_mem = nullptr, @@ -36,46 +36,26 @@ const pw_core_events PwCore::EVENTS = { }; PwCore::PwCore(QObject* parent): QObject(parent), notifier(QSocketNotifier::Read) { - pw_init(nullptr, nullptr); -} - -bool PwCore::start(bool retry) { - if (this->core != nullptr) return true; - qCInfo(logLoop) << "Creating pipewire event loop."; + pw_init(nullptr, nullptr); this->loop = pw_loop_new(nullptr); if (this->loop == nullptr) { - if (retry) { - qCInfo(logLoop) << "Failed to create pipewire event loop."; - } else { - qCCritical(logLoop) << "Failed to create pipewire event loop."; - } - this->shutdown(); - return false; + qCCritical(logLoop) << "Failed to create pipewire event loop."; + return; } this->context = pw_context_new(this->loop, nullptr, 0); if (this->context == nullptr) { - if (retry) { - qCInfo(logLoop) << "Failed to create pipewire context."; - } else { - qCCritical(logLoop) << "Failed to create pipewire context."; - } - this->shutdown(); - return false; + qCCritical(logLoop) << "Failed to create pipewire context."; + return; } qCInfo(logLoop) << "Connecting to pipewire server."; this->core = pw_context_connect(this->context, nullptr, 0); if (this->core == nullptr) { - if (retry) { - qCInfo(logLoop) << "Failed to connect pipewire context. Errno:" << errno; - } else { - qCCritical(logLoop) << "Failed to connect pipewire context. Errno:" << errno; - } - this->shutdown(); - return false; + qCCritical(logLoop) << "Failed to connect pipewire context. Errno:" << errno; + return; } pw_core_add_listener(this->core, &this->listener.hook, &PwCore::EVENTS, this); @@ -86,34 +66,22 @@ bool PwCore::start(bool retry) { this->notifier.setSocket(fd); QObject::connect(&this->notifier, &QSocketNotifier::activated, this, &PwCore::poll); this->notifier.setEnabled(true); - - return true; -} - -void PwCore::shutdown() { - if (this->core != nullptr) { - this->listener.remove(); - pw_core_disconnect(this->core); - this->core = nullptr; - } - - if (this->context != nullptr) { - pw_context_destroy(this->context); - this->context = nullptr; - } - - if (this->loop != nullptr) { - pw_loop_destroy(this->loop); - this->loop = nullptr; - } - - this->notifier.setEnabled(false); - QObject::disconnect(&this->notifier, nullptr, this, nullptr); } PwCore::~PwCore() { qCInfo(logLoop) << "Destroying PwCore."; - this->shutdown(); + + if (this->loop != nullptr) { + if (this->context != nullptr) { + if (this->core != nullptr) { + pw_core_disconnect(this->core); + } + + pw_context_destroy(this->context); + } + + pw_loop_destroy(this->loop); + } } bool PwCore::isValid() const { @@ -122,7 +90,6 @@ bool PwCore::isValid() const { } void PwCore::poll() { - if (this->loop == nullptr) return; qCDebug(logLoop) << "Pipewire event loop received new events, iterating."; // Spin pw event loop. pw_loop_iterate(this->loop, 0); @@ -140,23 +107,6 @@ void PwCore::onSync(void* data, quint32 id, qint32 seq) { emit self->synced(id, seq); } -void PwCore::onError(void* data, quint32 id, qint32 /*seq*/, qint32 res, const char* message) { - auto* self = static_cast(data); - - // Pipewire's documentation describes the error event as being fatal, however it isn't. - // We're not sure what causes these ENOENTs on device removal, presumably something in - // the teardown sequence, but they're harmless. Attempting to handle them as a fatal - // error causes unnecessary triggers for shells. - if (res == -ENOENT) { - qCDebug(logLoop) << "Pipewire ENOENT on object" << id << "with code" << res << message; - return; - } - - qCWarning(logLoop) << "Pipewire error on object" << id << "with code" << res << message; - - emit self->fatalError(); -} - SpaHook::SpaHook() { // NOLINT spa_zero(this->hook); } diff --git a/src/services/pipewire/core.hpp b/src/services/pipewire/core.hpp index 967efaf..262e2d3 100644 --- a/src/services/pipewire/core.hpp +++ b/src/services/pipewire/core.hpp @@ -30,9 +30,6 @@ public: ~PwCore() override; Q_DISABLE_COPY_MOVE(PwCore); - bool start(bool retry); - void shutdown(); - [[nodiscard]] bool isValid() const; [[nodiscard]] qint32 sync(quint32 id) const; @@ -43,7 +40,6 @@ public: signals: void polled(); void synced(quint32 id, qint32 seq); - void fatalError(); private slots: void poll(); @@ -52,7 +48,6 @@ private: static const pw_core_events EVENTS; static void onSync(void* data, quint32 id, qint32 seq); - static void onError(void* data, quint32 id, qint32 seq, qint32 res, const char* message); QSocketNotifier notifier; SpaHook listener; diff --git a/src/services/pipewire/defaults.cpp b/src/services/pipewire/defaults.cpp index 7a24a65..b3d8bfc 100644 --- a/src/services/pipewire/defaults.cpp +++ b/src/services/pipewire/defaults.cpp @@ -12,6 +12,7 @@ #include #include "../../core/logcat.hpp" +#include "../../core/util.hpp" #include "metadata.hpp" #include "node.hpp" #include "registry.hpp" @@ -30,22 +31,6 @@ PwDefaultTracker::PwDefaultTracker(PwRegistry* registry): registry(registry) { QObject::connect(registry, &PwRegistry::nodeAdded, this, &PwDefaultTracker::onNodeAdded); } -void PwDefaultTracker::reset() { - if (auto* meta = this->defaultsMetadata.object()) { - QObject::disconnect(meta, nullptr, this, nullptr); - } - - this->defaultsMetadata.setObject(nullptr); - this->setDefaultSink(nullptr); - this->setDefaultSinkName(QString()); - this->setDefaultSource(nullptr); - this->setDefaultSourceName(QString()); - this->setDefaultConfiguredSink(nullptr); - this->setDefaultConfiguredSinkName(QString()); - this->setDefaultConfiguredSource(nullptr); - this->setDefaultConfiguredSourceName(QString()); -} - void PwDefaultTracker::onMetadataAdded(PwMetadata* metadata) { if (metadata->name() == "default") { qCDebug(logDefaults) << "Got new defaults metadata object" << metadata; @@ -137,6 +122,32 @@ void PwDefaultTracker::onNodeAdded(PwNode* node) { } } +void PwDefaultTracker::onNodeDestroyed(QObject* node) { + if (node == this->mDefaultSink) { + qCInfo(logDefaults) << "Default sink destroyed."; + this->mDefaultSink = nullptr; + emit this->defaultSinkChanged(); + } + + if (node == this->mDefaultSource) { + qCInfo(logDefaults) << "Default source destroyed."; + this->mDefaultSource = nullptr; + emit this->defaultSourceChanged(); + } + + if (node == this->mDefaultConfiguredSink) { + qCInfo(logDefaults) << "Default configured sink destroyed."; + this->mDefaultConfiguredSink = nullptr; + emit this->defaultConfiguredSinkChanged(); + } + + if (node == this->mDefaultConfiguredSource) { + qCInfo(logDefaults) << "Default configured source destroyed."; + this->mDefaultConfiguredSource = nullptr; + emit this->defaultConfiguredSourceChanged(); + } +} + void PwDefaultTracker::changeConfiguredSink(PwNode* node) { if (node != nullptr) { if (!node->type.testFlags(PwNodeType::AudioSink)) { @@ -190,8 +201,7 @@ bool PwDefaultTracker::setConfiguredDefault(const char* key, const QString& valu } if (!meta->hasSetPermission()) { - qCCritical( - logDefaults + qCCritical(logDefaults ) << "Cannot set default node as write+execute permissions are missing for" << meta; return false; @@ -213,23 +223,10 @@ void PwDefaultTracker::setDefaultSink(PwNode* node) { if (node == this->mDefaultSink) return; qCInfo(logDefaults) << "Default sink changed to" << node; - if (this->mDefaultSink != nullptr) { - QObject::disconnect(this->mDefaultSink, nullptr, this, nullptr); - } - - this->mDefaultSink = node; - - if (node != nullptr) { - QObject::connect(node, &QObject::destroyed, this, &PwDefaultTracker::onDefaultSinkDestroyed); - } - - emit this->defaultSinkChanged(); -} - -void PwDefaultTracker::onDefaultSinkDestroyed() { - qCInfo(logDefaults) << "Default sink destroyed."; - this->mDefaultSink = nullptr; - emit this->defaultSinkChanged(); + setSimpleObjectHandle< + &PwDefaultTracker::mDefaultSink, + &PwDefaultTracker::onNodeDestroyed, + &PwDefaultTracker::defaultSinkChanged>(this, node); } void PwDefaultTracker::setDefaultSinkName(const QString& name) { @@ -243,23 +240,10 @@ void PwDefaultTracker::setDefaultSource(PwNode* node) { if (node == this->mDefaultSource) return; qCInfo(logDefaults) << "Default source changed to" << node; - if (this->mDefaultSource != nullptr) { - QObject::disconnect(this->mDefaultSource, nullptr, this, nullptr); - } - - this->mDefaultSource = node; - - if (node != nullptr) { - QObject::connect(node, &QObject::destroyed, this, &PwDefaultTracker::onDefaultSourceDestroyed); - } - - emit this->defaultSourceChanged(); -} - -void PwDefaultTracker::onDefaultSourceDestroyed() { - qCInfo(logDefaults) << "Default source destroyed."; - this->mDefaultSource = nullptr; - emit this->defaultSourceChanged(); + setSimpleObjectHandle< + &PwDefaultTracker::mDefaultSource, + &PwDefaultTracker::onNodeDestroyed, + &PwDefaultTracker::defaultSourceChanged>(this, node); } void PwDefaultTracker::setDefaultSourceName(const QString& name) { @@ -273,28 +257,10 @@ void PwDefaultTracker::setDefaultConfiguredSink(PwNode* node) { if (node == this->mDefaultConfiguredSink) return; qCInfo(logDefaults) << "Default configured sink changed to" << node; - if (this->mDefaultConfiguredSink != nullptr) { - QObject::disconnect(this->mDefaultConfiguredSink, nullptr, this, nullptr); - } - - this->mDefaultConfiguredSink = node; - - if (node != nullptr) { - QObject::connect( - node, - &QObject::destroyed, - this, - &PwDefaultTracker::onDefaultConfiguredSinkDestroyed - ); - } - - emit this->defaultConfiguredSinkChanged(); -} - -void PwDefaultTracker::onDefaultConfiguredSinkDestroyed() { - qCInfo(logDefaults) << "Default configured sink destroyed."; - this->mDefaultConfiguredSink = nullptr; - emit this->defaultConfiguredSinkChanged(); + setSimpleObjectHandle< + &PwDefaultTracker::mDefaultConfiguredSink, + &PwDefaultTracker::onNodeDestroyed, + &PwDefaultTracker::defaultConfiguredSinkChanged>(this, node); } void PwDefaultTracker::setDefaultConfiguredSinkName(const QString& name) { @@ -308,28 +274,10 @@ void PwDefaultTracker::setDefaultConfiguredSource(PwNode* node) { if (node == this->mDefaultConfiguredSource) return; qCInfo(logDefaults) << "Default configured source changed to" << node; - if (this->mDefaultConfiguredSource != nullptr) { - QObject::disconnect(this->mDefaultConfiguredSource, nullptr, this, nullptr); - } - - this->mDefaultConfiguredSource = node; - - if (node != nullptr) { - QObject::connect( - node, - &QObject::destroyed, - this, - &PwDefaultTracker::onDefaultConfiguredSourceDestroyed - ); - } - - emit this->defaultConfiguredSourceChanged(); -} - -void PwDefaultTracker::onDefaultConfiguredSourceDestroyed() { - qCInfo(logDefaults) << "Default configured source destroyed."; - this->mDefaultConfiguredSource = nullptr; - emit this->defaultConfiguredSourceChanged(); + setSimpleObjectHandle< + &PwDefaultTracker::mDefaultConfiguredSource, + &PwDefaultTracker::onNodeDestroyed, + &PwDefaultTracker::defaultConfiguredSourceChanged>(this, node); } void PwDefaultTracker::setDefaultConfiguredSourceName(const QString& name) { diff --git a/src/services/pipewire/defaults.hpp b/src/services/pipewire/defaults.hpp index f31669e..f3a8e3f 100644 --- a/src/services/pipewire/defaults.hpp +++ b/src/services/pipewire/defaults.hpp @@ -12,7 +12,6 @@ class PwDefaultTracker: public QObject { public: explicit PwDefaultTracker(PwRegistry* registry); - void reset(); [[nodiscard]] PwNode* defaultSink() const; [[nodiscard]] PwNode* defaultSource() const; @@ -44,10 +43,7 @@ private slots: void onMetadataAdded(PwMetadata* metadata); void onMetadataProperty(const char* key, const char* type, const char* value); void onNodeAdded(PwNode* node); - void onDefaultSinkDestroyed(); - void onDefaultSourceDestroyed(); - void onDefaultConfiguredSinkDestroyed(); - void onDefaultConfiguredSourceDestroyed(); + void onNodeDestroyed(QObject* node); private: void setDefaultSink(PwNode* node); diff --git a/src/services/pipewire/device.cpp b/src/services/pipewire/device.cpp index 61079a1..616e7d0 100644 --- a/src/services/pipewire/device.cpp +++ b/src/services/pipewire/device.cpp @@ -107,7 +107,7 @@ void PwDevice::addDeviceIndexPairs(const spa_pod* param) { qint32 device = 0; qint32 index = 0; - const spa_pod* props = nullptr; + spa_pod* props = nullptr; // clang-format off quint32 id = SPA_PARAM_Route; @@ -125,32 +125,18 @@ void PwDevice::addDeviceIndexPairs(const spa_pod* param) { // Insert into the main map as well, staging's purpose is to remove old entries. this->routeDeviceIndexes.insert(device, index); - // Used for initial node volume if the device is bound before the node - // (e.g. multiple nodes pointing to the same device) - this->routeDeviceVolumes.insert(device, volumeProps); - qCDebug(logDevice).nospace() << "Registered device/index pair for " << this << ": [device: " << device << ", index: " << index << ']'; emit this->routeVolumesChanged(device, volumeProps); } -bool PwDevice::tryLoadVolumeProps(qint32 routeDevice, PwVolumeProps& volumeProps) { - if (!this->routeDeviceVolumes.contains(routeDevice)) return false; - volumeProps = this->routeDeviceVolumes.value(routeDevice); - return true; -} - -bool PwDevice::hasRouteDevice(qint32 routeDevice) const { - return this->routeDeviceIndexes.contains(routeDevice); -} - void PwDevice::polled() { // It is far more likely that the list content has not come in yet than it having no entries, // and there isn't a way to check in the case that there *aren't* actually any entries. if (!this->stagingIndexes.isEmpty()) { - this->routeDeviceIndexes.removeIf([&, this](const std::pair& entry) { - if (!this->stagingIndexes.contains(entry.first)) { + this->routeDeviceIndexes.removeIf([&](const std::pair& entry) { + if (!stagingIndexes.contains(entry.first)) { qCDebug(logDevice).nospace() << "Removed device/index pair [device: " << entry.first << ", index: " << entry.second << "] for" << this; return true; diff --git a/src/services/pipewire/device.hpp b/src/services/pipewire/device.hpp index cd61709..1a1f705 100644 --- a/src/services/pipewire/device.hpp +++ b/src/services/pipewire/device.hpp @@ -12,15 +12,13 @@ #include #include "core.hpp" +#include "node.hpp" #include "registry.hpp" namespace qs::service::pipewire { class PwDevice; -// Forward declare to avoid circular dependency with node.hpp -struct PwVolumeProps; - class PwDevice: public PwBindable { Q_OBJECT; @@ -34,9 +32,6 @@ public: void waitForDevice(); [[nodiscard]] bool waitingForDevice() const; - [[nodiscard]] bool tryLoadVolumeProps(qint32 routeDevice, PwVolumeProps& volumeProps); - [[nodiscard]] bool hasRouteDevice(qint32 routeDevice) const; - signals: void deviceReady(); void routeVolumesChanged(qint32 routeDevice, const PwVolumeProps& volumeProps); @@ -51,7 +46,6 @@ private: onParam(void* data, qint32 seq, quint32 id, quint32 index, quint32 next, const spa_pod* param); QHash routeDeviceIndexes; - QHash routeDeviceVolumes; QList stagingIndexes; void addDeviceIndexPairs(const spa_pod* param); diff --git a/src/services/pipewire/module.md b/src/services/pipewire/module.md index e34f77d..d109f05 100644 --- a/src/services/pipewire/module.md +++ b/src/services/pipewire/module.md @@ -2,7 +2,6 @@ name = "Quickshell.Services.Pipewire" description = "Pipewire API" headers = [ "qml.hpp", - "peak.hpp", "link.hpp", "node.hpp", ] diff --git a/src/services/pipewire/node.cpp b/src/services/pipewire/node.cpp index 075a7ec..3e68149 100644 --- a/src/services/pipewire/node.cpp +++ b/src/services/pipewire/node.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include #include @@ -23,7 +23,6 @@ #include #include #include -#include #include #include "../../core/logcat.hpp" @@ -161,24 +160,6 @@ void PwNode::initProps(const spa_dict* props) { this->nick = nodeNick; } - if (const auto* nodeCategory = spa_dict_lookup(props, PW_KEY_MEDIA_CATEGORY)) { - if (strcmp(nodeCategory, "Monitor") == 0 || strcmp(nodeCategory, "Manager") == 0) { - this->isMonitor = true; - } - } - - if (const auto* serial = spa_dict_lookup(props, PW_KEY_OBJECT_SERIAL)) { - auto ok = false; - auto value = QString::fromUtf8(serial).toULongLong(&ok); - if (!ok) { - qCWarning(logNode) << this - << "has an object.serial property but the value is not valid. Value:" - << serial; - } else { - this->objectSerial = value; - } - } - if (const auto* deviceId = spa_dict_lookup(props, PW_KEY_DEVICE_ID)) { auto ok = false; auto id = QString::fromUtf8(deviceId).toInt(&ok); @@ -190,8 +171,7 @@ void PwNode::initProps(const spa_dict* props) { this->device = this->registry->devices.value(id); if (this->device == nullptr) { - qCCritical( - logNode + qCCritical(logNode ) << this << "has a device.id property that does not corrospond to a device object. Id:" << id; } @@ -215,36 +195,21 @@ void PwNode::onInfo(void* data, const pw_node_info* info) { if ((info->change_mask & PW_NODE_CHANGE_MASK_PROPS) != 0) { auto properties = QMap(); - bool proAudio = false; - if (const auto* proAudioStr = spa_dict_lookup(info->props, "device.profile.pro")) { - proAudio = spa_atob(proAudioStr); - } - - if (proAudio != self->proAudio) { - qCDebug(logNode) << self << "pro audio state changed:" << proAudio; - self->proAudio = proAudio; - } - if (self->device) { if (const auto* routeDevice = spa_dict_lookup(info->props, "card.profile.device")) { auto ok = false; auto id = QString::fromUtf8(routeDevice).toInt(&ok); if (!ok) { - qCCritical( - logNode + qCCritical(logNode ) << self << "has a card.profile.device property but the value is not an integer. Value:" << id; } self->routeDevice = id; - if (self->boundData) self->boundData->onDeviceChanged(); } else { - qCDebug( - logNode - ) << self - << "has attached device" << self->device - << "but no card.profile.device property. Node volume control will be used."; + qCCritical(logNode) << self << "has attached device" << self->device + << "but no card.profile.device property."; } } @@ -301,15 +266,6 @@ PwNodeBoundAudio::PwNodeBoundAudio(PwNode* node): QObject(node), node(node) { } } -void PwNodeBoundAudio::onDeviceChanged() { - PwVolumeProps volumeProps; - if (this->node->device->tryLoadVolumeProps(this->node->routeDevice, volumeProps)) { - qCDebug(logNode) << "Initializing volume props for" << this->node - << "with known values from backing device."; - this->updateVolumeProps(volumeProps); - } -} - void PwNodeBoundAudio::onInfo(const pw_node_info* info) { if ((info->change_mask & PW_NODE_CHANGE_MASK_PARAMS) != 0) { for (quint32 i = 0; i < info->n_params; i++) { @@ -330,10 +286,9 @@ void PwNodeBoundAudio::onInfo(const pw_node_info* info) { void PwNodeBoundAudio::onSpaParam(quint32 id, quint32 index, const spa_pod* param) { if (id == SPA_PARAM_Props && index == 0) { - if (this->node->shouldUseDevice()) { + if (this->node->device) { qCDebug(logNode) << "Skipping node volume props update for" << this->node - << "in favor of device updates from routeDevice" << this->node->routeDevice - << "of" << this->node->device; + << "in favor of device updates."; return; } @@ -349,8 +304,6 @@ void PwNodeBoundAudio::updateVolumeProps(const PwVolumeProps& volumeProps) { return; } - this->volumeStep = volumeProps.volumeStep; - // It is important that the lengths of channels and volumes stay in sync whenever you read them. auto channelsChanged = false; auto volumesChanged = false; @@ -403,7 +356,7 @@ void PwNodeBoundAudio::setMuted(bool muted) { if (muted == this->mMuted) return; - if (this->node->shouldUseDevice()) { + if (this->node->device) { qCInfo(logNode) << "Changing muted state of" << this->node << "to" << muted << "via device"; if (!this->node->device->setMuted(this->node->routeDevice, muted)) { return; @@ -429,10 +382,6 @@ void PwNodeBoundAudio::setMuted(bool muted) { } float PwNodeBoundAudio::averageVolume() const { - if (this->mVolumes.isEmpty()) { - return 0.0f; - } - float total = 0; for (auto volume: this->mVolumes) { @@ -480,41 +429,37 @@ void PwNodeBoundAudio::setVolumes(const QVector& volumes) { return; } - if (this->node->shouldUseDevice()) { + if (this->node->device) { if (this->node->device->waitingForDevice()) { qCInfo(logNode) << "Waiting to change volumes of" << this->node << "to" << realVolumes << "via device"; this->waitingVolumes = realVolumes; } else { - if (this->volumeStep != -1) { - auto significantChange = this->mServerVolumes.isEmpty(); - for (auto i = 0; i < this->mServerVolumes.length(); i++) { - auto serverVolume = this->mServerVolumes.value(i); - auto targetVolume = realVolumes.value(i); - if (targetVolume == 0 || abs(targetVolume - serverVolume) >= this->volumeStep) { - significantChange = true; - break; - } + auto significantChange = this->mServerVolumes.isEmpty(); + for (auto i = 0; i < this->mServerVolumes.length(); i++) { + auto serverVolume = this->mServerVolumes.value(i); + auto targetVolume = realVolumes.value(i); + if (targetVolume == 0 || abs(targetVolume - serverVolume) >= 0.0001) { + significantChange = true; + break; + } + } + + if (significantChange) { + qCInfo(logNode) << "Changing volumes of" << this->node << "to" << realVolumes + << "via device"; + if (!this->node->device->setVolumes(this->node->routeDevice, realVolumes)) { + return; } - if (significantChange) { - qCInfo(logNode) << "Changing volumes of" << this->node << "to" << realVolumes - << "via device"; - if (!this->node->device->setVolumes(this->node->routeDevice, realVolumes)) { - return; - } - - this->mDeviceVolumes = realVolumes; - this->node->device->waitForDevice(); - } else { - // Insignificant changes won't cause an info event on the device, leaving qs hung in the - // "waiting for acknowledgement" state forever. - qCInfo(logNode).nospace() - << "Ignoring volume change for " << this->node << " to " << realVolumes << " from " - << this->mServerVolumes - << " as it is a device node and the change is too small (min step: " - << this->volumeStep << ")."; - } + this->mDeviceVolumes = realVolumes; + this->node->device->waitForDevice(); + } else { + // Insignificant changes won't cause an info event on the device, leaving qs hung in the + // "waiting for acknowledgement" state forever. + qCInfo(logNode) << "Ignoring volume change for" << this->node << "to" << realVolumes + << "from" << this->mServerVolumes + << "as it is a device node and the change is too small."; } } } else { @@ -560,7 +505,7 @@ void PwNodeBoundAudio::onDeviceVolumesChanged( qint32 routeDevice, const PwVolumeProps& volumeProps ) { - if (this->node->shouldUseDevice() && this->node->routeDevice == routeDevice) { + if (this->node->device && this->node->routeDevice == routeDevice) { qCDebug(logNode) << "Got updated device volume props for" << this->node << "via" << this->node->device; @@ -574,36 +519,23 @@ PwVolumeProps PwVolumeProps::parseSpaPod(const spa_pod* param) { const auto* volumesProp = spa_pod_find_prop(param, nullptr, SPA_PROP_channelVolumes); const auto* channelsProp = spa_pod_find_prop(param, nullptr, SPA_PROP_channelMap); const auto* muteProp = spa_pod_find_prop(param, nullptr, SPA_PROP_mute); - const auto* volumeStepProp = spa_pod_find_prop(param, nullptr, SPA_PROP_volumeStep); - if (volumesProp) { - const auto* volumes = reinterpret_cast(&volumesProp->value); - spa_pod* iter = nullptr; - SPA_POD_ARRAY_FOREACH(volumes, iter) { - // Cubing behavior found in MPD source, and appears to corrospond to everyone else's measurements correctly. - auto linear = *reinterpret_cast(iter); - auto visual = std::cbrt(linear); - props.volumes.push_back(visual); - } + const auto* volumes = reinterpret_cast(&volumesProp->value); + const auto* channels = reinterpret_cast(&channelsProp->value); + + spa_pod* iter = nullptr; + SPA_POD_ARRAY_FOREACH(volumes, iter) { + // Cubing behavior found in MPD source, and appears to corrospond to everyone else's measurements correctly. + auto linear = *reinterpret_cast(iter); + auto visual = std::cbrt(linear); + props.volumes.push_back(visual); } - if (channelsProp) { - const auto* channels = reinterpret_cast(&channelsProp->value); - spa_pod* iter = nullptr; - SPA_POD_ARRAY_FOREACH(channels, iter) { - props.channels.push_back(*reinterpret_cast(iter)); - } + SPA_POD_ARRAY_FOREACH(channels, iter) { + props.channels.push_back(*reinterpret_cast(iter)); } - if (muteProp) { - spa_pod_get_bool(&muteProp->value, &props.mute); - } - - if (volumeStepProp) { - spa_pod_get_float(&volumeStepProp->value, &props.volumeStep); - } else { - props.volumeStep = -1; - } + spa_pod_get_bool(&muteProp->value, &props.mute); return props; } diff --git a/src/services/pipewire/node.hpp b/src/services/pipewire/node.hpp index efc819c..0d4c92e 100644 --- a/src/services/pipewire/node.hpp +++ b/src/services/pipewire/node.hpp @@ -15,7 +15,6 @@ #include #include "core.hpp" -#include "device.hpp" #include "registry.hpp" namespace qs::service::pipewire { @@ -159,7 +158,6 @@ struct PwVolumeProps { QVector channels; QVector volumes; bool mute = false; - float volumeStep = -1; static PwVolumeProps parseSpaPod(const spa_pod* param); }; @@ -170,7 +168,6 @@ public: virtual ~PwNodeBoundData() = default; Q_DISABLE_COPY_MOVE(PwNodeBoundData); - virtual void onDeviceChanged() {}; virtual void onInfo(const pw_node_info* /*info*/) {} virtual void onSpaParam(quint32 /*id*/, quint32 /*index*/, const spa_pod* /*param*/) {} virtual void onUnbind() {} @@ -184,7 +181,6 @@ class PwNodeBoundAudio public: explicit PwNodeBoundAudio(PwNode* node); - void onDeviceChanged() override; void onInfo(const pw_node_info* info) override; void onSpaParam(quint32 id, quint32 index, const spa_pod* param) override; void onUnbind() override; @@ -200,8 +196,6 @@ public: [[nodiscard]] QVector volumes() const; void setVolumes(const QVector& volumes); - [[nodiscard]] QVector server() const; - signals: void volumesChanged(); void channelsChanged(); @@ -220,7 +214,6 @@ private: QVector mServerVolumes; QVector mDeviceVolumes; QVector waitingVolumes; - float volumeStep = -1; PwNode* node; }; @@ -236,8 +229,6 @@ public: QString description; QString nick; QMap properties; - quint64 objectSerial = 0; - bool isMonitor = false; PwNodeType::Flags type = PwNodeType::Untracked; @@ -247,13 +238,6 @@ public: PwDevice* device = nullptr; qint32 routeDevice = -1; - bool proAudio = false; - - [[nodiscard]] bool shouldUseDevice() const { - if (!this->device || this->proAudio || this->routeDevice == -1) return false; - // Only use device control if the device actually has route indexes for this routeDevice - return this->device->hasRouteDevice(this->routeDevice); - } signals: void propertiesChanged(); diff --git a/src/services/pipewire/peak.cpp b/src/services/pipewire/peak.cpp deleted file mode 100644 index 64b5c42..0000000 --- a/src/services/pipewire/peak.cpp +++ /dev/null @@ -1,404 +0,0 @@ -#include "peak.hpp" -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../core/logcat.hpp" -#include "connection.hpp" -#include "core.hpp" -#include "node.hpp" -#include "qml.hpp" - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmissing-designated-field-initializers" - -namespace qs::service::pipewire { - -namespace { -QS_LOGGING_CATEGORY(logPeak, "quickshell.service.pipewire.peak", QtWarningMsg); -} - -class PwPeakStream { -public: - PwPeakStream(PwNodePeakMonitor* monitor, PwNode* node): monitor(monitor), node(node) {} - ~PwPeakStream() { this->destroy(); } - Q_DISABLE_COPY_MOVE(PwPeakStream); - - bool start(); - void destroy(); - -private: - static const pw_stream_events EVENTS; - static void onProcess(void* data); - static void onParamChanged(void* data, uint32_t id, const spa_pod* param); - static void - onStateChanged(void* data, pw_stream_state oldState, pw_stream_state state, const char* error); - static void onDestroy(void* data); - - void handleProcess(); - void handleParamChanged(uint32_t id, const spa_pod* param); - void handleStateChanged(pw_stream_state oldState, pw_stream_state state, const char* error); - void resetFormat(); - - PwNodePeakMonitor* monitor = nullptr; - PwNode* node = nullptr; - pw_stream* stream = nullptr; - SpaHook listener; - spa_audio_info_raw format = SPA_AUDIO_INFO_RAW_INIT(.format = SPA_AUDIO_FORMAT_UNKNOWN); - bool formatReady = false; - QVector channelPeaks; -}; - -const pw_stream_events PwPeakStream::EVENTS = { - .version = PW_VERSION_STREAM_EVENTS, - .destroy = &PwPeakStream::onDestroy, - .state_changed = &PwPeakStream::onStateChanged, - .param_changed = &PwPeakStream::onParamChanged, - .process = &PwPeakStream::onProcess, -}; - -bool PwPeakStream::start() { - auto* core = PwConnection::instance()->registry.core; - if (core == nullptr || !core->isValid()) { - qCWarning(logPeak) << "Cannot start peak monitor stream: pipewire core is not ready."; - return false; - } - - auto target = - QByteArray::number(this->node->objectSerial ? this->node->objectSerial : this->node->id); - - // clang-format off - auto* props = pw_properties_new( - PW_KEY_MEDIA_TYPE, "Audio", - PW_KEY_MEDIA_CATEGORY, "Monitor", - PW_KEY_MEDIA_NAME, "Peak detect", - PW_KEY_APP_NAME, "Quickshell Peak Detect", - PW_KEY_STREAM_MONITOR, "true", - PW_KEY_STREAM_CAPTURE_SINK, this->node->type.testFlags(PwNodeType::Sink) ? "true" : "false", - PW_KEY_TARGET_OBJECT, target.constData(), - nullptr - ); - // clang-format on - - if (props == nullptr) { - qCWarning(logPeak) << "Failed to create properties for peak monitor stream."; - return false; - } - - this->stream = pw_stream_new(core->core, "quickshell-peak-monitor", props); - if (this->stream == nullptr) { - qCWarning(logPeak) << "Failed to create peak monitor stream."; - return false; - } - - pw_stream_add_listener(this->stream, &this->listener.hook, &PwPeakStream::EVENTS, this); - - auto buffer = std::array {}; - auto builder = SPA_POD_BUILDER_INIT(buffer.data(), buffer.size()); // NOLINT - - auto params = std::array {}; - auto raw = SPA_AUDIO_INFO_RAW_INIT(.format = SPA_AUDIO_FORMAT_F32); - params[0] = spa_format_audio_raw_build(&builder, SPA_PARAM_EnumFormat, &raw); - - auto flags = - static_cast(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS); - auto res = - pw_stream_connect(this->stream, PW_DIRECTION_INPUT, PW_ID_ANY, flags, params.data(), 1); - - if (res < 0) { - qCWarning(logPeak) << "Failed to connect peak monitor stream:" << res; - this->destroy(); - return false; - } - - return true; -} - -void PwPeakStream::destroy() { - if (this->stream == nullptr) return; - this->listener.remove(); - pw_stream_destroy(this->stream); - this->stream = nullptr; - this->resetFormat(); -} - -void PwPeakStream::onProcess(void* data) { - static_cast(data)->handleProcess(); // NOLINT -} - -void PwPeakStream::onParamChanged(void* data, uint32_t id, const spa_pod* param) { - static_cast(data)->handleParamChanged(id, param); // NOLINT -} - -void PwPeakStream::onStateChanged( - void* data, - pw_stream_state oldState, - pw_stream_state state, - const char* error -) { - static_cast(data)->handleStateChanged(oldState, state, error); // NOLINT -} - -void PwPeakStream::onDestroy(void* data) { - auto* self = static_cast(data); // NOLINT - self->stream = nullptr; - self->listener.remove(); - self->resetFormat(); -} - -void PwPeakStream::handleStateChanged( - pw_stream_state oldState, - pw_stream_state state, - const char* error -) { - if (state == PW_STREAM_STATE_ERROR) { - if (error != nullptr) { - qCWarning(logPeak) << "Peak monitor stream error:" << error; - } else { - qCWarning(logPeak) << "Peak monitor stream error."; - } - } - - if (state == PW_STREAM_STATE_PAUSED && oldState != PW_STREAM_STATE_PAUSED) { - auto peakCount = this->monitor->mChannels.length(); - if (peakCount == 0) { - peakCount = this->monitor->mPeaks.length(); - } - if (peakCount == 0 && this->formatReady) { - peakCount = static_cast(this->format.channels); - } - - if (peakCount > 0) { - auto zeros = QVector(peakCount, 0.0f); - this->monitor->updatePeaks(zeros, 0.0f); - } - } -} - -void PwPeakStream::handleParamChanged(uint32_t id, const spa_pod* param) { - if (param == nullptr || id != SPA_PARAM_Format) return; - - auto info = spa_audio_info {}; - if (spa_format_parse(param, &info.media_type, &info.media_subtype) < 0) return; - - if (info.media_type != SPA_MEDIA_TYPE_audio || info.media_subtype != SPA_MEDIA_SUBTYPE_raw) - return; - - auto raw = SPA_AUDIO_INFO_RAW_INIT(.format = SPA_AUDIO_FORMAT_UNKNOWN); // NOLINT - if (spa_format_audio_raw_parse(param, &raw) < 0) return; - - if (raw.format != SPA_AUDIO_FORMAT_F32) { - qCWarning(logPeak) << "Unsupported peak monitor format for" << this->node << ":" << raw.format; - this->resetFormat(); - return; - } - - this->format = raw; - this->formatReady = raw.channels > 0; - - auto channels = QVector(); - channels.reserve(static_cast(raw.channels)); - - for (quint32 i = 0; i < raw.channels; i++) { - if ((raw.flags & SPA_AUDIO_FLAG_UNPOSITIONED) != 0) { - channels.push_back(PwAudioChannel::Unknown); - } else { - channels.push_back(static_cast(raw.position[i])); - } - } - - this->channelPeaks.fill(0.0f, channels.size()); - this->monitor->updateChannels(channels); - this->monitor->updatePeaks(this->channelPeaks, 0.0f); -} - -void PwPeakStream::resetFormat() { - this->format = SPA_AUDIO_INFO_RAW_INIT(.format = SPA_AUDIO_FORMAT_UNKNOWN); - this->formatReady = false; - this->channelPeaks.clear(); - this->monitor->clearPeaks(); -} - -void PwPeakStream::handleProcess() { - if (!this->formatReady || this->stream == nullptr) return; - - auto* buffer = pw_stream_dequeue_buffer(this->stream); - auto requeue = qScopeGuard([&, this] { pw_stream_queue_buffer(this->stream, buffer); }); - - if (buffer == nullptr) { - qCWarning(logPeak) << "Peak monitor ran out of buffers."; - return; - } - - auto* spaBuffer = buffer->buffer; - if (spaBuffer == nullptr || spaBuffer->n_datas < 1) { - return; - } - - auto* data = &spaBuffer->datas[0]; // NOLINT - if (data->data == nullptr || data->chunk == nullptr) { - return; - } - - auto channelCount = static_cast(this->format.channels); - if (channelCount <= 0) { - return; - } - - const auto* base = static_cast(data->data) + data->chunk->offset; // NOLINT - const auto* samples = reinterpret_cast(base); - auto sampleCount = static_cast(data->chunk->size / sizeof(float)); - - if (sampleCount < channelCount) { - return; - } - - QVector volumes; - if (auto* audioData = dynamic_cast(this->node->boundData)) { - if (!this->node->shouldUseDevice()) volumes = audioData->volumes(); - } - - this->channelPeaks.fill(0.0f, channelCount); - - auto maxPeak = 0.0f; - for (auto channel = 0; channel < channelCount; channel++) { - auto peak = 0.0f; - for (auto sample = channel; sample < sampleCount; sample += channelCount) { - peak = std::max(peak, std::abs(samples[sample])); // NOLINT - } - - auto visualPeak = std::cbrt(peak); - if (!volumes.isEmpty() && volumes[channel] != 0.0f) visualPeak *= 1.0f / volumes[channel]; - - this->channelPeaks[channel] = visualPeak; - maxPeak = std::max(maxPeak, visualPeak); - } - - this->monitor->updatePeaks(this->channelPeaks, maxPeak); -} - -PwNodePeakMonitor::PwNodePeakMonitor(QObject* parent): QObject(parent) {} - -PwNodePeakMonitor::~PwNodePeakMonitor() { - delete this->mStream; - this->mStream = nullptr; -} - -PwNodeIface* PwNodePeakMonitor::node() const { return this->mNode; } - -void PwNodePeakMonitor::setNode(PwNodeIface* node) { - if (node == this->mNode) return; - - if (this->mNode != nullptr) { - QObject::disconnect(this->mNode, nullptr, this, nullptr); - } - - if (node != nullptr) { - QObject::connect(node, &QObject::destroyed, this, &PwNodePeakMonitor::onNodeDestroyed); - } - - this->mNode = node; - this->mNodeRef.setObject(node != nullptr ? node->node() : nullptr); - this->rebuildStream(); - emit this->nodeChanged(); -} - -bool PwNodePeakMonitor::isEnabled() const { return this->mEnabled; } - -void PwNodePeakMonitor::setEnabled(bool enabled) { - if (enabled == this->mEnabled) return; - this->mEnabled = enabled; - this->rebuildStream(); - emit this->enabledChanged(); -} - -void PwNodePeakMonitor::onNodeDestroyed() { - this->mNode = nullptr; - this->mNodeRef.setObject(nullptr); - this->rebuildStream(); - emit this->nodeChanged(); -} - -void PwNodePeakMonitor::updatePeaks(const QVector& peaks, float peak) { - if (this->mPeaks != peaks) { - this->mPeaks = peaks; - emit this->peaksChanged(); - } - - if (this->mPeak != peak) { - this->mPeak = peak; - emit this->peakChanged(); - } -} - -void PwNodePeakMonitor::updateChannels(const QVector& channels) { - if (this->mChannels == channels) return; - this->mChannels = channels; - emit this->channelsChanged(); -} - -void PwNodePeakMonitor::clearPeaks() { - if (!this->mPeaks.isEmpty()) { - this->mPeaks.clear(); - emit this->peaksChanged(); - } - - if (!this->mChannels.isEmpty()) { - this->mChannels.clear(); - emit this->channelsChanged(); - } - - if (this->mPeak != 0.0f) { - this->mPeak = 0.0f; - emit this->peakChanged(); - } -} - -void PwNodePeakMonitor::rebuildStream() { - delete this->mStream; - this->mStream = nullptr; - - auto* node = this->mNodeRef.object(); - if (!this->mEnabled || node == nullptr) { - this->clearPeaks(); - return; - } - - if (node == nullptr || !node->type.testFlags(PwNodeType::Audio)) { - this->clearPeaks(); - return; - } - - this->mStream = new PwPeakStream(this, node); - if (!this->mStream->start()) { - delete this->mStream; - this->mStream = nullptr; - this->clearPeaks(); - } -} - -} // namespace qs::service::pipewire - -#pragma GCC diagnostic pop diff --git a/src/services/pipewire/peak.hpp b/src/services/pipewire/peak.hpp deleted file mode 100644 index c4af3c2..0000000 --- a/src/services/pipewire/peak.hpp +++ /dev/null @@ -1,87 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "node.hpp" - -namespace qs::service::pipewire { - -class PwNodeIface; -class PwPeakStream; - -} // namespace qs::service::pipewire - -Q_DECLARE_OPAQUE_POINTER(qs::service::pipewire::PwNodeIface*); - -namespace qs::service::pipewire { - -///! Monitors peak levels of an audio node. -/// Tracks volume peaks for a node across all its channels. -/// -/// The peak monitor binds nodes similarly to @@PwObjectTracker when enabled. -class PwNodePeakMonitor: public QObject { - Q_OBJECT; - // clang-format off - /// The node to monitor. Must be an audio node. - Q_PROPERTY(qs::service::pipewire::PwNodeIface* node READ node WRITE setNode NOTIFY nodeChanged); - /// If true, the monitor is actively capturing and computing peaks. Defaults to true. - Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged); - /// Per-channel peak noise levels (0.0-1.0). Length matches @@channels. - /// - /// The channel's volume does not affect this property. - Q_PROPERTY(QVector peaks READ peaks NOTIFY peaksChanged); - /// Maximum value of @@peaks. - Q_PROPERTY(float peak READ peak NOTIFY peakChanged); - /// Channel positions for the captured format. Length matches @@peaks. - Q_PROPERTY(QVector channels READ channels NOTIFY channelsChanged); - // clang-format on - QML_ELEMENT; - -public: - explicit PwNodePeakMonitor(QObject* parent = nullptr); - ~PwNodePeakMonitor() override; - Q_DISABLE_COPY_MOVE(PwNodePeakMonitor); - - [[nodiscard]] PwNodeIface* node() const; - void setNode(PwNodeIface* node); - - [[nodiscard]] bool isEnabled() const; - void setEnabled(bool enabled); - - [[nodiscard]] QVector peaks() const { return this->mPeaks; } - [[nodiscard]] float peak() const { return this->mPeak; } - [[nodiscard]] QVector channels() const { return this->mChannels; } - -signals: - void nodeChanged(); - void enabledChanged(); - void peaksChanged(); - void peakChanged(); - void channelsChanged(); - -private slots: - void onNodeDestroyed(); - -private: - friend class PwPeakStream; - - void updatePeaks(const QVector& peaks, float peak); - void updateChannels(const QVector& channels); - void clearPeaks(); - void rebuildStream(); - - PwNodeIface* mNode = nullptr; - PwBindableRef mNodeRef; - bool mEnabled = true; - QVector mPeaks; - float mPeak = 0.0f; - QVector mChannels; - PwPeakStream* mStream = nullptr; -}; - -} // namespace qs::service::pipewire diff --git a/src/services/pipewire/qml.cpp b/src/services/pipewire/qml.cpp index e4424c1..5d8c45e 100644 --- a/src/services/pipewire/qml.cpp +++ b/src/services/pipewire/qml.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -17,16 +18,6 @@ namespace qs::service::pipewire { -PwObjectIface::PwObjectIface(PwBindableObject* object): QObject(object), object(object) { - // We want to destroy the interface before QObject::destroyed is fired, as handlers - // connected before PwObjectIface will run first and emit signals that hit user code, - // which can then try to reference the iface again after ~PwNode() has been called but - // before ~QObject() has finished. - QObject::connect(object, &PwBindableObject::destroying, this, &PwObjectIface::onObjectDestroying); -} - -void PwObjectIface::onObjectDestroying() { delete this; } - void PwObjectIface::ref() { this->refcount++; @@ -98,8 +89,15 @@ Pipewire::Pipewire(QObject* parent): QObject(parent) { &Pipewire::defaultConfiguredAudioSourceChanged ); - QObject::connect(&connection->registry, &PwRegistry::initialized, this, &Pipewire::readyChanged); - QObject::connect(&connection->registry, &PwRegistry::cleared, this, &Pipewire::readyChanged); + if (!connection->registry.isInitialized()) { + QObject::connect( + &connection->registry, + &PwRegistry::initialized, + this, + &Pipewire::readyChanged, + Qt::SingleShotConnection + ); + } } ObjectModel* Pipewire::nodes() { return &this->mNodes; } @@ -213,7 +211,6 @@ void PwNodeLinkTracker::updateLinks() { || (this->mNode->isSink() && link->inputNode() == this->mNode->id())) { auto* iface = PwLinkGroupIface::instance(link); - if (iface->target()->node()->isMonitor) return; // do not connect twice if (!this->mLinkGroups.contains(iface)) { @@ -232,7 +229,7 @@ void PwNodeLinkTracker::updateLinks() { for (auto* iface: this->mLinkGroups) { // only disconnect no longer used nodes - if (!newLinks.contains(iface) || iface->target()->node()->isMonitor) { + if (!newLinks.contains(iface)) { QObject::disconnect(iface, nullptr, this, nullptr); } } @@ -272,8 +269,6 @@ void PwNodeLinkTracker::onLinkGroupCreated(PwLinkGroup* linkGroup) { || (this->mNode->isSink() && linkGroup->inputNode() == this->mNode->id())) { auto* iface = PwLinkGroupIface::instance(linkGroup); - if (iface->target()->node()->isMonitor) return; - QObject::connect(iface, &QObject::destroyed, this, &PwNodeLinkTracker::onLinkGroupDestroyed); this->mLinkGroups.push_back(iface); emit this->linkGroupsChanged(); diff --git a/src/services/pipewire/qml.hpp b/src/services/pipewire/qml.hpp index a43ce19..5bcc70d 100644 --- a/src/services/pipewire/qml.hpp +++ b/src/services/pipewire/qml.hpp @@ -36,7 +36,7 @@ class PwObjectIface Q_OBJECT; public: - explicit PwObjectIface(PwBindableObject* object); + explicit PwObjectIface(PwBindableObject* object): QObject(object), object(object) {}; // destructor should ONLY be called by the pw object destructor, making an unref unnecessary ~PwObjectIface() override = default; Q_DISABLE_COPY_MOVE(PwObjectIface); @@ -44,9 +44,6 @@ public: void ref() override; void unref() override; -private slots: - void onObjectDestroying(); - private: quint32 refcount = 0; PwBindableObject* object; @@ -171,13 +168,13 @@ private: ObjectModel mLinkGroups {this}; }; -///! Tracks non-monitor link connections to a given node. +///! Tracks all link connections to a given node. class PwNodeLinkTracker: public QObject { Q_OBJECT; // clang-format off /// The node to track connections to. Q_PROPERTY(qs::service::pipewire::PwNodeIface* node READ node WRITE setNode NOTIFY nodeChanged); - /// Link groups connected to the given node, excluding monitors. + /// Link groups connected to the given node. /// /// If the node is a sink, links which target the node will be tracked. /// If the node is a source, links which source the node will be tracked. diff --git a/src/services/pipewire/registry.cpp b/src/services/pipewire/registry.cpp index 4b670b1..c08fc1d 100644 --- a/src/services/pipewire/registry.cpp +++ b/src/services/pipewire/registry.cpp @@ -134,46 +134,6 @@ void PwRegistry::init(PwCore& core) { this->coreSyncSeq = this->core->sync(PW_ID_CORE); } -void PwRegistry::reset() { - if (this->core != nullptr) { - QObject::disconnect(this->core, nullptr, this, nullptr); - } - - this->listener.remove(); - - if (this->object != nullptr) { - pw_proxy_destroy(reinterpret_cast(this->object)); - this->object = nullptr; - } - - for (auto* meta: this->metadata.values()) { - meta->safeDestroy(); - } - this->metadata.clear(); - - for (auto* link: this->links.values()) { - link->safeDestroy(); - } - this->links.clear(); - - for (auto* node: this->nodes.values()) { - node->safeDestroy(); - } - this->nodes.clear(); - - for (auto* device: this->devices.values()) { - device->safeDestroy(); - } - this->devices.clear(); - - this->linkGroups.clear(); - this->initState = InitState::SendingObjects; - this->coreSyncSeq = 0; - this->core = nullptr; - - emit this->cleared(); -} - void PwRegistry::onCoreSync(quint32 id, qint32 seq) { if (id != PW_ID_CORE || seq != this->coreSyncSeq) return; diff --git a/src/services/pipewire/registry.hpp b/src/services/pipewire/registry.hpp index bb2db8c..14ea405 100644 --- a/src/services/pipewire/registry.hpp +++ b/src/services/pipewire/registry.hpp @@ -55,8 +55,8 @@ protected: void registryBind(const char* interface, quint32 version); virtual void bind(); void unbind(); - virtual void bindHooks() {} - virtual void unbindHooks() {} + virtual void bindHooks() {}; + virtual void unbindHooks() {}; quint32 refcount = 0; pw_proxy* object = nullptr; @@ -116,7 +116,6 @@ class PwRegistry public: void init(PwCore& core); - void reset(); [[nodiscard]] bool isInitialized() const { return this->initState == InitState::Done; } @@ -137,7 +136,6 @@ signals: void linkGroupAdded(PwLinkGroup* group); void metadataAdded(PwMetadata* metadata); void initialized(); - void cleared(); private slots: void onLinkGroupDestroyed(QObject* object); diff --git a/src/services/polkit/CMakeLists.txt b/src/services/polkit/CMakeLists.txt deleted file mode 100644 index 51791d8..0000000 --- a/src/services/polkit/CMakeLists.txt +++ /dev/null @@ -1,35 +0,0 @@ -find_package(PkgConfig REQUIRED) -pkg_check_modules(glib REQUIRED IMPORTED_TARGET glib-2.0>=2.36) -pkg_check_modules(gobject REQUIRED IMPORTED_TARGET gobject-2.0) -pkg_check_modules(polkit_agent REQUIRED IMPORTED_TARGET polkit-agent-1) -pkg_check_modules(polkit REQUIRED IMPORTED_TARGET polkit-gobject-1) - -qt_add_library(quickshell-service-polkit STATIC - agentimpl.cpp - flow.cpp - identity.cpp - listener.cpp - session.cpp - qml.cpp -) - -qt_add_qml_module(quickshell-service-polkit - URI Quickshell.Services.Polkit - VERSION 0.1 - DEPENDENCIES QtQml -) - -install_qml_module(quickshell-service-polkit) - -target_link_libraries(quickshell-service-polkit PRIVATE - Qt::Qml - Qt::Quick - PkgConfig::glib - PkgConfig::gobject - PkgConfig::polkit_agent - PkgConfig::polkit -) - -qs_module_pch(quickshell-service-polkit) - -target_link_libraries(quickshell PRIVATE quickshell-service-polkitplugin) diff --git a/src/services/polkit/agentimpl.cpp b/src/services/polkit/agentimpl.cpp deleted file mode 100644 index 85c62b7..0000000 --- a/src/services/polkit/agentimpl.cpp +++ /dev/null @@ -1,180 +0,0 @@ -#include "agentimpl.hpp" -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "../../core/generation.hpp" -#include "../../core/logcat.hpp" -#include "gobjectref.hpp" -#include "listener.hpp" -#include "qml.hpp" - -namespace { -QS_LOGGING_CATEGORY(logPolkit, "quickshell.service.polkit", QtWarningMsg); -} - -namespace qs::service::polkit { -PolkitAgentImpl* PolkitAgentImpl::instance = nullptr; - -PolkitAgentImpl::PolkitAgentImpl(PolkitAgent* agent) - : QObject(nullptr) - , listener(qs_polkit_agent_new(this), G_OBJECT_NO_REF) - , qmlAgent(agent) - , path(this->qmlAgent->path()) { - auto utf8Path = this->path.toUtf8(); - qs_polkit_agent_register(this->listener.get(), utf8Path.constData()); -} - -PolkitAgentImpl::~PolkitAgentImpl() { this->cancelAllRequests("PolkitAgent is being destroyed"); } - -void PolkitAgentImpl::cancelAllRequests(const QString& reason) { - for (; !this->queuedRequests.empty(); this->queuedRequests.pop_back()) { - AuthRequest* req = this->queuedRequests.back(); - qCDebug(logPolkit) << "destroying queued authentication request for action" << req->actionId; - req->cancel(reason); - delete req; - } - - auto* flow = this->bActiveFlow.value(); - if (flow) { - flow->cancelAuthenticationRequest(); - flow->deleteLater(); - } - - if (this->bIsRegistered.value()) qs_polkit_agent_unregister(this->listener.get()); -} - -PolkitAgentImpl* PolkitAgentImpl::tryGetOrCreate(PolkitAgent* agent) { - if (instance == nullptr) instance = new PolkitAgentImpl(agent); - if (instance->qmlAgent == agent) return instance; - return nullptr; -} - -PolkitAgentImpl* PolkitAgentImpl::tryGet(const PolkitAgent* agent) { - if (instance == nullptr) return nullptr; - if (instance->qmlAgent == agent) return instance; - return nullptr; -} - -PolkitAgentImpl* PolkitAgentImpl::tryTakeoverOrCreate(PolkitAgent* agent) { - if (auto* impl = tryGetOrCreate(agent); impl != nullptr) return impl; - - auto* prevGen = EngineGeneration::findObjectGeneration(instance->qmlAgent); - auto* myGen = EngineGeneration::findObjectGeneration(agent); - if (prevGen == myGen) return nullptr; - - qCDebug(logPolkit) << "taking over listener from previous generation"; - instance->qmlAgent = agent; - instance->setPath(agent->path()); - - return instance; -} - -void PolkitAgentImpl::onEndOfQmlAgent(PolkitAgent* agent) { - if (instance != nullptr && instance->qmlAgent == agent) { - delete instance; - instance = nullptr; - } -} - -void PolkitAgentImpl::setPath(const QString& path) { - if (this->path == path) return; - - this->path = path; - auto utf8Path = path.toUtf8(); - - this->cancelAllRequests("PolkitAgent path changed"); - qs_polkit_agent_unregister(this->listener.get()); - this->bIsRegistered = false; - - qs_polkit_agent_register(this->listener.get(), utf8Path.constData()); -} - -void PolkitAgentImpl::registerComplete(bool success) { - if (success) this->bIsRegistered = true; - else qCWarning(logPolkit) << "failed to register listener on path" << this->qmlAgent->path(); -} - -void PolkitAgentImpl::initiateAuthentication(AuthRequest* request) { - qCDebug(logPolkit) << "incoming authentication request for action" << request->actionId; - - this->queuedRequests.emplace_back(request); - - if (this->queuedRequests.size() == 1) { - this->activateAuthenticationRequest(); - } -} - -void PolkitAgentImpl::cancelAuthentication(AuthRequest* request) { - qCDebug(logPolkit) << "cancelling authentication request from agent"; - - auto* flow = this->bActiveFlow.value(); - if (flow && flow->authRequest() == request) { - flow->cancelFromAgent(); - } else if (auto it = std::ranges::find(this->queuedRequests, request); - it != this->queuedRequests.end()) - { - qCDebug(logPolkit) << "removing queued authentication request for action" << (*it)->actionId; - (*it)->cancel("Authentication request was cancelled"); - delete (*it); - this->queuedRequests.erase(it); - } else { - qCWarning(logPolkit) << "the cancelled request was not found in the queue."; - } -} - -void PolkitAgentImpl::activateAuthenticationRequest() { - if (this->queuedRequests.empty()) return; - - AuthRequest* req = this->queuedRequests.front(); - this->queuedRequests.pop_front(); - qCDebug(logPolkit) << "activating authentication request for action" << req->actionId - << ", cookie: " << req->cookie; - - QList identities; - for (auto& identity: req->identities) { - auto* obj = Identity::fromPolkitIdentity(identity); - if (obj) identities.append(obj); - } - if (identities.isEmpty()) { - qCWarning( - logPolkit - ) << "no supported identities available for authentication request, cancelling."; - req->cancel("Error requesting authentication: no supported identities available."); - delete req; - return; - } - - this->bActiveFlow = new AuthFlow(req, std::move(identities)); - - QObject::connect( - this->bActiveFlow.value(), - &AuthFlow::isCompletedChanged, - this, - &PolkitAgentImpl::finishAuthenticationRequest - ); - - emit this->qmlAgent->authenticationRequestStarted(); -} - -void PolkitAgentImpl::finishAuthenticationRequest() { - if (!this->bActiveFlow.value()) return; - - qCDebug(logPolkit) << "finishing authentication request for action" - << this->bActiveFlow.value()->actionId(); - - this->bActiveFlow.value()->deleteLater(); - - if (!this->queuedRequests.empty()) { - this->activateAuthenticationRequest(); - } else { - this->bActiveFlow = nullptr; - } -} -} // namespace qs::service::polkit diff --git a/src/services/polkit/agentimpl.hpp b/src/services/polkit/agentimpl.hpp deleted file mode 100644 index 65ae11a..0000000 --- a/src/services/polkit/agentimpl.hpp +++ /dev/null @@ -1,66 +0,0 @@ -#pragma once - -#include - -#include -#include - -#include "flow.hpp" -#include "gobjectref.hpp" -#include "listener.hpp" - -namespace qs::service::polkit { -class PolkitAgent; - -class PolkitAgentImpl - : public QObject - , public ListenerCb { - Q_OBJECT; - Q_DISABLE_COPY_MOVE(PolkitAgentImpl); - -public: - ~PolkitAgentImpl() override; - - static PolkitAgentImpl* tryGetOrCreate(PolkitAgent* agent); - static PolkitAgentImpl* tryGet(const PolkitAgent* agent); - static PolkitAgentImpl* tryTakeoverOrCreate(PolkitAgent* agent); - static void onEndOfQmlAgent(PolkitAgent* agent); - - [[nodiscard]] QBindable activeFlow() { return &this->bActiveFlow; }; - [[nodiscard]] QBindable isRegistered() { return &this->bIsRegistered; }; - - [[nodiscard]] const QString& getPath() const { return this->path; } - void setPath(const QString& path); - - void initiateAuthentication(AuthRequest* request) override; - void cancelAuthentication(AuthRequest* request) override; - void registerComplete(bool success) override; - - void cancelAllRequests(const QString& reason); - -signals: - void activeFlowChanged(); - void isRegisteredChanged(); - -private: - PolkitAgentImpl(PolkitAgent* agent); - - static PolkitAgentImpl* instance; - - /// Start handling of the next authentication request in the queue. - void activateAuthenticationRequest(); - /// Finalize and remove the current authentication request. - void finishAuthenticationRequest(); - - GObjectRef listener; - PolkitAgent* qmlAgent = nullptr; - QString path; - - std::deque queuedRequests; - - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(PolkitAgentImpl, AuthFlow*, bActiveFlow, &PolkitAgentImpl::activeFlowChanged); - Q_OBJECT_BINDABLE_PROPERTY(PolkitAgentImpl, bool, bIsRegistered, &PolkitAgentImpl::isRegisteredChanged); - // clang-format on -}; -} // namespace qs::service::polkit diff --git a/src/services/polkit/flow.cpp b/src/services/polkit/flow.cpp deleted file mode 100644 index 2a709eb..0000000 --- a/src/services/polkit/flow.cpp +++ /dev/null @@ -1,163 +0,0 @@ -#include "flow.hpp" -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "../../core/logcat.hpp" -#include "identity.hpp" -#include "qml.hpp" -#include "session.hpp" - -namespace { -QS_LOGGING_CATEGORY(logPolkitState, "quickshell.service.polkit.state", QtWarningMsg); -} - -namespace qs::service::polkit { -AuthFlow::AuthFlow(AuthRequest* request, QList&& identities, QObject* parent) - : QObject(parent) - , mRequest(request) - , mIdentities(std::move(identities)) - , bSelectedIdentity(this->mIdentities.isEmpty() ? nullptr : this->mIdentities.first()) { - // We reject auth requests with no identities before a flow is created. - // This should never happen. - if (!this->bSelectedIdentity.value()) - qCFatal(logPolkitState) << "AuthFlow created with no valid identities!"; - - for (auto* identity: this->mIdentities) { - identity->setParent(this); - } - - this->setupSession(); -} - -AuthFlow::~AuthFlow() { delete this->mRequest; }; - -void AuthFlow::setSelectedIdentity(Identity* identity) { - if (this->bSelectedIdentity.value() == identity) return; - if (!identity) { - qmlWarning(this) << "Cannot set selected identity to null."; - return; - } - this->bSelectedIdentity = identity; - this->currentSession->cancel(); - this->setupSession(); -} - -void AuthFlow::cancelFromAgent() { - if (!this->currentSession) return; - - qCDebug(logPolkitState) << "cancelling authentication request from agent"; - - // Session cancel can immediately call the cancel handler, which also - // performs property updates. - Qt::beginPropertyUpdateGroup(); - this->bIsCancelled = true; - this->currentSession->cancel(); - Qt::endPropertyUpdateGroup(); - - emit this->authenticationRequestCancelled(); - - this->mRequest->cancel("Authentication request cancelled by agent."); -} - -void AuthFlow::submit(const QString& value) { - if (!this->currentSession) return; - - qCDebug(logPolkitState) << "submitting response to authentication request"; - - this->currentSession->respond(value); - - Qt::beginPropertyUpdateGroup(); - this->bIsResponseRequired = false; - this->bInputPrompt = QString(); - this->bResponseVisible = false; - Qt::endPropertyUpdateGroup(); -} - -void AuthFlow::cancelAuthenticationRequest() { - if (!this->currentSession) return; - - qCDebug(logPolkitState) << "cancelling authentication request by user request"; - - // Session cancel can immediately call the cancel handler, which also - // performs property updates. - Qt::beginPropertyUpdateGroup(); - this->bIsCancelled = true; - this->currentSession->cancel(); - Qt::endPropertyUpdateGroup(); - - this->mRequest->cancel("Authentication request cancelled by user."); -} - -void AuthFlow::setupSession() { - delete this->currentSession; - - qCDebug(logPolkitState) << "setting up session for identity" - << this->bSelectedIdentity.value()->name(); - - this->currentSession = new Session( - this->bSelectedIdentity.value()->polkitIdentity.get(), - this->mRequest->cookie, - this - ); - QObject::connect(this->currentSession, &Session::request, this, &AuthFlow::request); - QObject::connect(this->currentSession, &Session::completed, this, &AuthFlow::completed); - QObject::connect(this->currentSession, &Session::showError, this, &AuthFlow::showError); - QObject::connect(this->currentSession, &Session::showInfo, this, &AuthFlow::showInfo); - this->currentSession->initiate(); -} - -void AuthFlow::request(const QString& message, bool echo) { - Qt::beginPropertyUpdateGroup(); - this->bIsResponseRequired = true; - this->bInputPrompt = message; - this->bResponseVisible = echo; - Qt::endPropertyUpdateGroup(); -} - -void AuthFlow::completed(bool gainedAuthorization) { - qCDebug(logPolkitState) << "authentication session completed, gainedAuthorization =" - << gainedAuthorization << ", isCancelled =" << this->bIsCancelled.value(); - - if (gainedAuthorization) { - Qt::beginPropertyUpdateGroup(); - this->bIsCompleted = true; - this->bIsSuccessful = true; - Qt::endPropertyUpdateGroup(); - - this->mRequest->complete(); - - emit this->authenticationSucceeded(); - } else if (this->bIsCancelled.value()) { - Qt::beginPropertyUpdateGroup(); - this->bIsCompleted = true; - this->bIsSuccessful = false; - Qt::endPropertyUpdateGroup(); - } else { - this->bFailed = true; - emit this->authenticationFailed(); - - this->setupSession(); - } -} - -void AuthFlow::showError(const QString& message) { - Qt::beginPropertyUpdateGroup(); - this->bSupplementaryMessage = message; - this->bSupplementaryIsError = true; - Qt::endPropertyUpdateGroup(); -} - -void AuthFlow::showInfo(const QString& message) { - Qt::beginPropertyUpdateGroup(); - this->bSupplementaryMessage = message; - this->bSupplementaryIsError = false; - Qt::endPropertyUpdateGroup(); -} -} // namespace qs::service::polkit diff --git a/src/services/polkit/flow.hpp b/src/services/polkit/flow.hpp deleted file mode 100644 index 0b7e845..0000000 --- a/src/services/polkit/flow.hpp +++ /dev/null @@ -1,179 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "../../core/retainable.hpp" -#include "identity.hpp" -#include "listener.hpp" - -namespace qs::service::polkit { -class Session; - -class AuthFlow - : public QObject - , public Retainable { - Q_OBJECT; - QML_ELEMENT; - Q_DISABLE_COPY_MOVE(AuthFlow); - QML_UNCREATABLE("AuthFlow can only be obtained from PolkitAgent."); - - // clang-format off - /// The main message to present to the user. - Q_PROPERTY(QString message READ message CONSTANT); - - /// The icon to present to the user in association with the message. - /// - /// The icon name follows the [FreeDesktop icon naming specification](https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html). - /// Use @@Quickshell.Quickshell.iconPath() to resolve the icon name to an - /// actual file path for display. - Q_PROPERTY(QString iconName READ iconName CONSTANT); - - /// The action ID represents the action that is being authorized. - /// - /// This is a machine-readable identifier. - Q_PROPERTY(QString actionId READ actionId CONSTANT); - - /// A cookie that identifies this authentication request. - /// - /// This is an internal identifier and not recommended to show to users. - Q_PROPERTY(QString cookie READ cookie CONSTANT); - - /// The list of identities that may be used to authenticate. - /// - /// Each identity may be a user or a group. You may select any of them to - /// authenticate by setting @@selectedIdentity. By default, the first identity - /// in the list is selected. - Q_PROPERTY(QList identities READ identities CONSTANT); - - /// The identity that will be used to authenticate. - /// - /// Changing this will abort any ongoing authentication conversations and start a new one. - Q_PROPERTY(Identity* selectedIdentity READ default WRITE setSelectedIdentity NOTIFY selectedIdentityChanged BINDABLE selectedIdentity); - - /// Indicates that a response from the user is required from the user, - /// typically a password. - Q_PROPERTY(bool isResponseRequired READ default NOTIFY isResponseRequiredChanged BINDABLE isResponseRequired); - - /// This message is used to prompt the user for required input. - Q_PROPERTY(QString inputPrompt READ default NOTIFY inputPromptChanged BINDABLE inputPrompt); - - /// Indicates whether the user's response should be visible. (e.g. for passwords this should be false) - Q_PROPERTY(bool responseVisible READ default NOTIFY responseVisibleChanged BINDABLE responseVisible); - - /// An additional message to present to the user. - /// - /// This may be used to show errors or supplementary information. - /// See @@supplementaryIsError to determine if this is an error message. - Q_PROPERTY(QString supplementaryMessage READ default NOTIFY supplementaryMessageChanged BINDABLE supplementaryMessage); - - /// Indicates whether the supplementary message is an error. - Q_PROPERTY(bool supplementaryIsError READ default NOTIFY supplementaryIsErrorChanged BINDABLE supplementaryIsError); - - /// Has the authentication request been completed. - Q_PROPERTY(bool isCompleted READ default NOTIFY isCompletedChanged BINDABLE isCompleted); - - /// Indicates whether the authentication request was successful. - Q_PROPERTY(bool isSuccessful READ default NOTIFY isSuccessfulChanged BINDABLE isSuccessful); - - /// Indicates whether the current authentication request was cancelled. - Q_PROPERTY(bool isCancelled READ default NOTIFY isCancelledChanged BINDABLE isCancelled); - - /// Indicates whether an authentication attempt has failed at least once during this authentication flow. - Q_PROPERTY(bool failed READ default NOTIFY failedChanged BINDABLE failed); - // clang-format on - -public: - explicit AuthFlow(AuthRequest* request, QList&& identities, QObject* parent = nullptr); - ~AuthFlow() override; - - /// Cancel the ongoing authentication request from the agent side. - void cancelFromAgent(); - - /// Submit a response to a request that was previously emitted. Typically the password. - Q_INVOKABLE void submit(const QString& value); - /// Cancel the ongoing authentication request from the user side. - Q_INVOKABLE void cancelAuthenticationRequest(); - - [[nodiscard]] const QString& message() const { return this->mRequest->message; }; - [[nodiscard]] const QString& iconName() const { return this->mRequest->iconName; }; - [[nodiscard]] const QString& actionId() const { return this->mRequest->actionId; }; - [[nodiscard]] const QString& cookie() const { return this->mRequest->cookie; }; - [[nodiscard]] const QList& identities() const { return this->mIdentities; }; - - [[nodiscard]] QBindable selectedIdentity() { return &this->bSelectedIdentity; }; - void setSelectedIdentity(Identity* identity); - - [[nodiscard]] QBindable isResponseRequired() { return &this->bIsResponseRequired; }; - [[nodiscard]] QBindable inputPrompt() { return &this->bInputPrompt; }; - [[nodiscard]] QBindable responseVisible() { return &this->bResponseVisible; }; - - [[nodiscard]] QBindable supplementaryMessage() { return &this->bSupplementaryMessage; }; - [[nodiscard]] QBindable supplementaryIsError() { return &this->bSupplementaryIsError; }; - - [[nodiscard]] QBindable isCompleted() { return &this->bIsCompleted; }; - [[nodiscard]] QBindable isSuccessful() { return &this->bIsSuccessful; }; - [[nodiscard]] QBindable isCancelled() { return &this->bIsCancelled; }; - [[nodiscard]] QBindable failed() { return &this->bFailed; }; - - [[nodiscard]] AuthRequest* authRequest() const { return this->mRequest; }; - -signals: - /// Emitted whenever an authentication request completes successfully. - void authenticationSucceeded(); - - /// Emitted whenever an authentication request completes unsuccessfully. - /// - /// This may be because the user entered the wrong password or otherwise - /// failed to authenticate. - /// This signal is not emmitted when the user canceled the request or it - /// was cancelled by the PolKit daemon. - /// - /// After this signal, a new session is automatically started for the same - /// identity. - void authenticationFailed(); - - /// Emmitted when on ongoing authentication request is cancelled by the PolKit daemon. - void authenticationRequestCancelled(); - - void selectedIdentityChanged(); - void isResponseRequiredChanged(); - void inputPromptChanged(); - void responseVisibleChanged(); - void supplementaryMessageChanged(); - void supplementaryIsErrorChanged(); - void isCompletedChanged(); - void isSuccessfulChanged(); - void isCancelledChanged(); - void failedChanged(); - -private slots: - // Signals received from session objects. - void request(const QString& message, bool echo); - void completed(bool gainedAuthorization); - void showError(const QString& message); - void showInfo(const QString& message); - -private: - /// Start a session for the currently selected identity and the current request. - void setupSession(); - - Session* currentSession = nullptr; - AuthRequest* mRequest = nullptr; - QList mIdentities; - - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(AuthFlow, Identity*, bSelectedIdentity, &AuthFlow::selectedIdentityChanged); - Q_OBJECT_BINDABLE_PROPERTY(AuthFlow, bool, bIsResponseRequired, &AuthFlow::isResponseRequiredChanged); - Q_OBJECT_BINDABLE_PROPERTY(AuthFlow, QString, bInputPrompt, &AuthFlow::inputPromptChanged); - Q_OBJECT_BINDABLE_PROPERTY(AuthFlow, bool, bResponseVisible, &AuthFlow::responseVisibleChanged); - Q_OBJECT_BINDABLE_PROPERTY(AuthFlow, QString, bSupplementaryMessage, &AuthFlow::supplementaryMessageChanged); - Q_OBJECT_BINDABLE_PROPERTY(AuthFlow, bool, bSupplementaryIsError, &AuthFlow::supplementaryIsErrorChanged); - Q_OBJECT_BINDABLE_PROPERTY(AuthFlow, bool, bIsCompleted, &AuthFlow::isCompletedChanged); - Q_OBJECT_BINDABLE_PROPERTY(AuthFlow, bool, bIsSuccessful, &AuthFlow::isSuccessfulChanged); - Q_OBJECT_BINDABLE_PROPERTY(AuthFlow, bool, bIsCancelled, &AuthFlow::isCancelledChanged); - Q_OBJECT_BINDABLE_PROPERTY(AuthFlow, bool, bFailed, &AuthFlow::failedChanged); - // clang-format on -}; -} // namespace qs::service::polkit diff --git a/src/services/polkit/gobjectref.hpp b/src/services/polkit/gobjectref.hpp deleted file mode 100644 index cd29a9d..0000000 --- a/src/services/polkit/gobjectref.hpp +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#include - -namespace qs::service::polkit { - -struct GObjectNoRefTag {}; -constexpr GObjectNoRefTag G_OBJECT_NO_REF; - -template -class GObjectRef { -public: - explicit GObjectRef(T* ptr = nullptr): ptr(ptr) { - if (this->ptr) { - g_object_ref(this->ptr); - } - } - - explicit GObjectRef(T* ptr, GObjectNoRefTag /*tag*/): ptr(ptr) {} - - ~GObjectRef() { - if (this->ptr) { - g_object_unref(this->ptr); - } - } - - // We do handle self-assignment in a more general case by checking the - // included pointers rather than the wrapper objects themselves. - // NOLINTBEGIN(bugprone-unhandled-self-assignment) - - GObjectRef(const GObjectRef& other): GObjectRef(other.ptr) {} - GObjectRef& operator=(const GObjectRef& other) { - if (*this == other) return *this; - if (this->ptr) { - g_object_unref(this->ptr); - } - this->ptr = other.ptr; - if (this->ptr) { - g_object_ref(this->ptr); - } - return *this; - } - - GObjectRef(GObjectRef&& other) noexcept: ptr(other.ptr) { other.ptr = nullptr; } - GObjectRef& operator=(GObjectRef&& other) noexcept { - if (*this == other) return *this; - if (this->ptr) { - g_object_unref(this->ptr); - } - this->ptr = other.ptr; - other.ptr = nullptr; - return *this; - } - - // NOLINTEND(bugprone-unhandled-self-assignment) - - [[nodiscard]] T* get() const { return this->ptr; } - T* operator->() const { return this->ptr; } - - bool operator==(const GObjectRef& other) const { return this->ptr == other.ptr; } - -private: - T* ptr; -}; -} // namespace qs::service::polkit \ No newline at end of file diff --git a/src/services/polkit/identity.cpp b/src/services/polkit/identity.cpp deleted file mode 100644 index 7be5f39..0000000 --- a/src/services/polkit/identity.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include "identity.hpp" -#include -#include -#include - -#include -#include -#include - -#define POLKIT_AGENT_I_KNOW_API_IS_SUBJECT_TO_CHANGE -// Workaround macro collision with glib 'signals' struct member. -#undef signals -#include -#define signals Q_SIGNALS -#include -#include -#include - -#include "gobjectref.hpp" - -namespace qs::service::polkit { -Identity::Identity( - id_t id, - QString name, - QString displayName, - bool isGroup, - GObjectRef polkitIdentity, - QObject* parent -) - : QObject(parent) - , polkitIdentity(std::move(polkitIdentity)) - , mId(id) - , mName(std::move(name)) - , mDisplayName(std::move(displayName)) - , mIsGroup(isGroup) {} - -Identity* Identity::fromPolkitIdentity(GObjectRef identity) { - if (POLKIT_IS_UNIX_USER(identity.get())) { - auto uid = polkit_unix_user_get_uid(POLKIT_UNIX_USER(identity.get())); - - auto bufSize = sysconf(_SC_GETPW_R_SIZE_MAX); - // The call can fail with -1, in this case choose a default that is - // big enough. - if (bufSize == -1) bufSize = 16384; - auto buffer = std::vector(bufSize); - - std::aligned_storage_t pwBuf; - passwd* pw = nullptr; - getpwuid_r(uid, reinterpret_cast(&pwBuf), buffer.data(), bufSize, &pw); - - auto name = - (pw && pw->pw_name && *pw->pw_name) ? QString::fromUtf8(pw->pw_name) : QString::number(uid); - - return new Identity( - uid, - name, - (pw && pw->pw_gecos && *pw->pw_gecos) ? QString::fromUtf8(pw->pw_gecos) : name, - false, - std::move(identity) - ); - } - - if (POLKIT_IS_UNIX_GROUP(identity.get())) { - auto gid = polkit_unix_group_get_gid(POLKIT_UNIX_GROUP(identity.get())); - - auto bufSize = sysconf(_SC_GETGR_R_SIZE_MAX); - // The call can fail with -1, in this case choose a default that is - // big enough. - if (bufSize == -1) bufSize = 16384; - auto buffer = std::vector(bufSize); - - std::aligned_storage_t grBuf; - group* gr = nullptr; - getgrgid_r(gid, reinterpret_cast(&grBuf), buffer.data(), bufSize, &gr); - - auto name = - (gr && gr->gr_name && *gr->gr_name) ? QString::fromUtf8(gr->gr_name) : QString::number(gid); - return new Identity(gid, name, name, true, std::move(identity)); - } - - // A different type of identity is netgroup. - return nullptr; -} -} // namespace qs::service::polkit diff --git a/src/services/polkit/identity.hpp b/src/services/polkit/identity.hpp deleted file mode 100644 index 27f3c1c..0000000 --- a/src/services/polkit/identity.hpp +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -#include -#include - -#include "gobjectref.hpp" - -// _PolkitIdentity is considered a reserved identifier, but I am specifically -// forward declaring this reserved name. -using PolkitIdentity = struct _PolkitIdentity; // NOLINT(bugprone-reserved-identifier) - -namespace qs::service::polkit { -//! Represents a user or group that can be used to authenticate. -class Identity: public QObject { - Q_OBJECT; - Q_DISABLE_COPY_MOVE(Identity); - - // clang-format off - /// The Id of the identity. If the identity is a user, this is the user's uid. See @@isGroup. - Q_PROPERTY(quint32 id READ id CONSTANT); - - /// The name of the user or group. - /// - /// If available, this is the actual username or group name, but may fallback to the ID. - Q_PROPERTY(QString string READ name CONSTANT); - - /// The full name of the user or group, if available. Otherwise the same as @@name. - Q_PROPERTY(QString displayName READ displayName CONSTANT); - - /// Indicates if this identity is a group or a user. - /// - /// If true, @@id is a gid, otherwise it is a uid. - Q_PROPERTY(bool isGroup READ isGroup CONSTANT); - - QML_UNCREATABLE("Identities cannot be created directly."); - // clang-format on - -public: - explicit Identity( - id_t id, - QString name, - QString displayName, - bool isGroup, - GObjectRef polkitIdentity, - QObject* parent = nullptr - ); - ~Identity() override = default; - - static Identity* fromPolkitIdentity(GObjectRef identity); - - [[nodiscard]] quint32 id() const { return static_cast(this->mId); }; - [[nodiscard]] const QString& name() const { return this->mName; }; - [[nodiscard]] const QString& displayName() const { return this->mDisplayName; }; - [[nodiscard]] bool isGroup() const { return this->mIsGroup; }; - - GObjectRef polkitIdentity; - -private: - id_t mId; - QString mName; - QString mDisplayName; - bool mIsGroup; -}; -} // namespace qs::service::polkit diff --git a/src/services/polkit/listener.cpp b/src/services/polkit/listener.cpp deleted file mode 100644 index e4bca4c..0000000 --- a/src/services/polkit/listener.cpp +++ /dev/null @@ -1,232 +0,0 @@ -#include "listener.hpp" -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../core/logcat.hpp" -#include "gobjectref.hpp" -#include "qml.hpp" - -namespace { -QS_LOGGING_CATEGORY(logPolkitListener, "quickshell.service.polkit.listener", QtWarningMsg); -} - -using qs::service::polkit::GObjectRef; - -// This is mostly GObject code, we follow their naming conventions for improved -// clarity and to mark it as such. Additionally, many methods need to be static -// to conform with the expected declarations. -// NOLINTBEGIN(readability-identifier-naming,misc-use-anonymous-namespace) - -using QsPolkitAgent = struct _QsPolkitAgent { - PolkitAgentListener parent_instance; - - qs::service::polkit::ListenerCb* cb; - gpointer registration_handle; -}; - -G_DEFINE_TYPE(QsPolkitAgent, qs_polkit_agent, POLKIT_AGENT_TYPE_LISTENER) - -static void initiate_authentication( - PolkitAgentListener* listener, - const gchar* actionId, - const gchar* message, - const gchar* iconName, - PolkitDetails* details, - const gchar* cookie, - GList* identities, - GCancellable* cancellable, - GAsyncReadyCallback callback, - gpointer userData -); - -static gboolean -initiate_authentication_finish(PolkitAgentListener* listener, GAsyncResult* result, GError** error); - -static void qs_polkit_agent_init(QsPolkitAgent* self) { - self->cb = nullptr; - self->registration_handle = nullptr; -} - -static void qs_polkit_agent_finalize(GObject* object) { - if (G_OBJECT_CLASS(qs_polkit_agent_parent_class)) - G_OBJECT_CLASS(qs_polkit_agent_parent_class)->finalize(object); -} - -static void qs_polkit_agent_class_init(QsPolkitAgentClass* klass) { - GObjectClass* gobject_class = G_OBJECT_CLASS(klass); - gobject_class->finalize = qs_polkit_agent_finalize; - - PolkitAgentListenerClass* listener_class = POLKIT_AGENT_LISTENER_CLASS(klass); - listener_class->initiate_authentication = initiate_authentication; - listener_class->initiate_authentication_finish = initiate_authentication_finish; -} - -QsPolkitAgent* qs_polkit_agent_new(qs::service::polkit::ListenerCb* cb) { - QsPolkitAgent* self = QS_POLKIT_AGENT(g_object_new(QS_TYPE_POLKIT_AGENT, nullptr)); - self->cb = cb; - return self; -} - -struct RegisterCbData { - GObjectRef agent; - std::string path; -}; - -static void qs_polkit_agent_register_cb(GObject* /*unused*/, GAsyncResult* res, gpointer userData); -void qs_polkit_agent_register(QsPolkitAgent* agent, const char* path) { - if (path == nullptr || *path == '\0') { - qCWarning(logPolkitListener) << "cannot register listener without a path set."; - agent->cb->registerComplete(false); - return; - } - - auto* data = new RegisterCbData {.agent = GObjectRef(agent), .path = path}; - polkit_unix_session_new_for_process(getpid(), nullptr, &qs_polkit_agent_register_cb, data); -} - -static void qs_polkit_agent_register_cb(GObject* /*unused*/, GAsyncResult* res, gpointer userData) { - std::unique_ptr data(reinterpret_cast(userData)); - - GError* error = nullptr; - auto* subject = polkit_unix_session_new_for_process_finish(res, &error); - - if (subject == nullptr || error != nullptr) { - qCWarning(logPolkitListener) << "failed to create subject for listener:" - << (error ? error->message : ""); - g_clear_error(&error); - data->agent->cb->registerComplete(false); - return; - } - - data->agent->registration_handle = polkit_agent_listener_register( - POLKIT_AGENT_LISTENER(data->agent.get()), - POLKIT_AGENT_REGISTER_FLAGS_NONE, - subject, - data->path.c_str(), - nullptr, - &error - ); - - g_object_unref(subject); - - if (error != nullptr) { - qCWarning(logPolkitListener) << "failed to register listener:" << error->message; - g_clear_error(&error); - data->agent->cb->registerComplete(false); - return; - } - - data->agent->cb->registerComplete(true); -} - -void qs_polkit_agent_unregister(QsPolkitAgent* agent) { - if (agent->registration_handle != nullptr) { - polkit_agent_listener_unregister(agent->registration_handle); - agent->registration_handle = nullptr; - } -} - -static void authentication_cancelled_cb(GCancellable* /*unused*/, gpointer userData) { - auto* request = static_cast(userData); - request->cb->cancelAuthentication(request); -} - -static void initiate_authentication( - PolkitAgentListener* listener, - const gchar* actionId, - const gchar* message, - const gchar* iconName, - PolkitDetails* /*unused*/, - const gchar* cookie, - GList* identities, - GCancellable* cancellable, - GAsyncReadyCallback callback, - gpointer userData -) { - auto* self = QS_POLKIT_AGENT(listener); - - auto* asyncResult = g_task_new(reinterpret_cast(self), nullptr, callback, userData); - - // Identities may be duplicated, so we use the hash to filter them out. - std::unordered_set identitySet; - std::vector> identityVector; - for (auto* item = g_list_first(identities); item != nullptr; item = g_list_next(item)) { - auto* identity = static_cast(item->data); - if (identitySet.contains(polkit_identity_hash(identity))) continue; - - identitySet.insert(polkit_identity_hash(identity)); - // The caller unrefs all identities after we return, therefore we need to - // take our own reference for the identities we keep. Our wrapper does - // this automatically. - identityVector.emplace_back(identity); - } - - // The original strings are freed by the caller after we return, so we - // copy them into QStrings. - auto* request = new qs::service::polkit::AuthRequest { - .actionId = QString::fromUtf8(actionId), - .message = QString::fromUtf8(message), - .iconName = QString::fromUtf8(iconName), - .cookie = QString::fromUtf8(cookie), - .identities = std::move(identityVector), - - .task = asyncResult, - .cancellable = cancellable, - .handlerId = 0, - .cb = self->cb - }; - - if (cancellable != nullptr) { - request->handlerId = g_cancellable_connect( - cancellable, - reinterpret_cast(authentication_cancelled_cb), - request, - nullptr - ); - } - - self->cb->initiateAuthentication(request); -} - -static gboolean initiate_authentication_finish( - PolkitAgentListener* /*unused*/, - GAsyncResult* result, - GError** error -) { - return g_task_propagate_boolean(G_TASK(result), error); -} - -namespace qs::service::polkit { -// While these functions can be const since they do not modify member variables, -// they are logically non-const since they modify the state of the -// authentication request. Therefore, we do not mark them as const. -// NOLINTBEGIN(readability-make-member-function-const) -void AuthRequest::complete() { g_task_return_boolean(this->task, true); } - -void AuthRequest::cancel(const QString& reason) { - auto utf8Reason = reason.toUtf8(); - g_task_return_new_error( - this->task, - POLKIT_ERROR, - POLKIT_ERROR_CANCELLED, - "%s", - utf8Reason.constData() - ); -} -// NOLINTEND(readability-make-member-function-const) -} // namespace qs::service::polkit - -// NOLINTEND(readability-identifier-naming,misc-use-anonymous-namespace) diff --git a/src/services/polkit/listener.hpp b/src/services/polkit/listener.hpp deleted file mode 100644 index 996fa23..0000000 --- a/src/services/polkit/listener.hpp +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once - -#include - -#define POLKIT_AGENT_I_KNOW_API_IS_SUBJECT_TO_CHANGE -// This causes a problem with variables of the name. -#undef signals - -#include -#include - -#define signals Q_SIGNALS - -#include "gobjectref.hpp" - -namespace qs::service::polkit { -class ListenerCb; -//! All state that comes in from PolKit about an authentication request. -struct AuthRequest { - //! The action ID that this session is for. - QString actionId; - //! Message to present to the user. - QString message; - //! Icon name according to the FreeDesktop specification. May be empty. - QString iconName; - // Details intentionally omitted because nothing seems to use them. - QString cookie; - //! List of users/groups that can be used for authentication. - std::vector> identities; - - //! Implementation detail to mark authentication done. - GTask* task; - //! Implementation detail for requests cancelled by agent. - GCancellable* cancellable; - //! Callback handler ID for the cancellable. - gulong handlerId; - //! Callbacks for the listener - ListenerCb* cb; - - void complete(); - void cancel(const QString& reason); -}; - -//! Callback interface for PolkitAgent listener events. -class ListenerCb { -public: - ListenerCb() = default; - virtual ~ListenerCb() = default; - Q_DISABLE_COPY_MOVE(ListenerCb); - - //! Called when the agent registration is complete. - virtual void registerComplete(bool success) = 0; - //! Called when an authentication request is initiated by PolKit. - virtual void initiateAuthentication(AuthRequest* request) = 0; - //! Called when an authentication request is cancelled by PolKit before completion. - virtual void cancelAuthentication(AuthRequest* request) = 0; -}; -} // namespace qs::service::polkit - -G_BEGIN_DECLS - -// This is GObject code. By using their naming conventions, we clearly mark it -// as such for the rest of the project. -// NOLINTBEGIN(readability-identifier-naming) - -#define QS_TYPE_POLKIT_AGENT (qs_polkit_agent_get_type()) -G_DECLARE_FINAL_TYPE(QsPolkitAgent, qs_polkit_agent, QS, POLKIT_AGENT, PolkitAgentListener) - -QsPolkitAgent* qs_polkit_agent_new(qs::service::polkit::ListenerCb* cb); -void qs_polkit_agent_register(QsPolkitAgent* agent, const char* path); -void qs_polkit_agent_unregister(QsPolkitAgent* agent); - -// NOLINTEND(readability-identifier-naming) - -G_END_DECLS diff --git a/src/services/polkit/module.md b/src/services/polkit/module.md deleted file mode 100644 index b306ecb..0000000 --- a/src/services/polkit/module.md +++ /dev/null @@ -1,52 +0,0 @@ -name = "Quickshell.Services.Polkit" -description = "Polkit Agent" -headers = [ - "agentimpl.hpp", - "flow.hpp", - "identity.hpp", - "listener.hpp", - "qml.hpp", - "session.hpp", -] ------ -## Purpose of a Polkit Agent - -PolKit is a system for privileged applications to query if a user is permitted to execute an action. -You have probably seen it in the form of a "Please enter your password to continue with X" dialog box before. -This dialog box is presented by your *PolKit agent*, it is a process running as your user that accepts authentication requests from the *daemon* and presents them to you to accept or deny. - -This service enables writing a PolKit agent in Quickshell. - -## Implementing a Polkit Agent - -The backend logic of communicating with the daemon is handled by the @@Quickshell.Services.Polkit.PolkitAgent object. -It exposes incoming requests via @@Quickshell.Services.Polkit.PolkitAgent.flow and provides appropriate signals. - -### Flow of an authentication request - -Incoming authentication requests are queued in the order that they arrive. -If none is queued, a request starts processing right away. -Otherwise, it will wait until prior requests are done. - -A request starts by emitting the @@Quickshell.Services.Polkit.PolkitAgent.authenticationRequestStarted signal. -At this point, information like the action to be performed and permitted users that can authenticate is available. - -An authentication *session* for the request is immediately started, which internally starts a PAM conversation that is likely to prompt for user input. -* Additional prompts may be shared with the user by way of the @@Quickshell.Services.Polkit.AuthFlow.supplementaryMessageChanged / @@Quickshell.Services.Polkit.AuthFlow.supplementaryIsErrorChanged signals and the @@Quickshell.Services.Polkit.AuthFlow.supplementaryMessage and @@Quickshell.Services.Polkit.AuthFlow.supplementaryIsError properties. A common message might be 'Please input your password'. -* An input request is forwarded via the @@Quickshell.Services.Polkit.AuthFlow.isResponseRequiredChanged / @@Quickshell.Services.Polkit.AuthFlow.inputPromptChanged / @@Quickshell.Services.Polkit.AuthFlow.responseVisibleChanged signals and the corresponding properties. Note that the request specifies whether the text box should show the typed input on screen or replace it with placeholders. - -User replies can be submitted via the @@Quickshell.Services.Polkit.AuthFlow.submit method. -A conversation can take multiple turns, for example if second factors are involved. - -If authentication fails, we automatically create a fresh session so the user can try again. -The @@Quickshell.Services.Polkit.AuthFlow.authenticationFailed signal is emitted in this case. - -If authentication is successful, you receive the @@Quickshell.Services.Polkit.AuthFlow.authenticationSucceeeded signal. At this point, the dialog can be closed. -If additional requests are queued, you will receive the @@Quickshell.Services.Polkit.PolkitAgent.authenticationRequestStarted signal again. - -#### Cancelled requests - -Requests may either be canceled by the user or the PolKit daemon. -In this case, we clean up any state and proceed to the next request, if any. - -If the request was cancelled by the daemon and not the user, you also receive the @@Quickshell.Services.Polkit.AuthFlow.authenticationRequestCancelled signal. diff --git a/src/services/polkit/qml.cpp b/src/services/polkit/qml.cpp deleted file mode 100644 index 9a08e5d..0000000 --- a/src/services/polkit/qml.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "qml.hpp" - -#include -#include -#include - -#include "../../core/logcat.hpp" -#include "agentimpl.hpp" - -namespace { -QS_LOGGING_CATEGORY(logPolkit, "quickshell.service.polkit", QtWarningMsg); -} - -namespace qs::service::polkit { -PolkitAgent::~PolkitAgent() { PolkitAgentImpl::onEndOfQmlAgent(this); }; - -void PolkitAgent::componentComplete() { - if (this->mPath.isEmpty()) this->mPath = "/org/quickshell/PolkitAgent"; - - auto* impl = PolkitAgentImpl::tryTakeoverOrCreate(this); - if (impl == nullptr) return; - - this->bFlow.setBinding([impl]() { return impl->activeFlow().value(); }); - this->bIsActive.setBinding([impl]() { return impl->activeFlow().value() != nullptr; }); - this->bIsRegistered.setBinding([impl]() { return impl->isRegistered().value(); }); -} - -void PolkitAgent::setPath(const QString& path) { - if (this->mPath.isEmpty()) { - this->mPath = path; - } else if (this->mPath != path) { - qCWarning(logPolkit) << "cannot change path after it has been set."; - } -} -} // namespace qs::service::polkit diff --git a/src/services/polkit/qml.hpp b/src/services/polkit/qml.hpp deleted file mode 100644 index 5343bcd..0000000 --- a/src/services/polkit/qml.hpp +++ /dev/null @@ -1,84 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include -#include - -#include "flow.hpp" - -// The reserved identifier is exactly the struct I mean. -using PolkitIdentity = struct _PolkitIdentity; // NOLINT(bugprone-reserved-identifier) -using QsPolkitAgent = struct _QsPolkitAgent; - -namespace qs::service::polkit { - -struct AuthRequest; -class Session; -class Identity; -class AuthFlow; - -//! Contains interface to instantiate a PolKit agent listener. -class PolkitAgent - : public QObject - , public QQmlParserStatus { - Q_OBJECT; - QML_ELEMENT; - Q_INTERFACES(QQmlParserStatus); - Q_DISABLE_COPY_MOVE(PolkitAgent); - - /// The D-Bus path that this agent listener will use. - /// - /// If not set, a default of /org/quickshell/Polkit will be used. - Q_PROPERTY(QString path READ path WRITE setPath); - - /// Indicates whether the agent registered successfully and is in use. - Q_PROPERTY(bool isRegistered READ default NOTIFY isRegisteredChanged BINDABLE isRegistered); - - /// Indicates an ongoing authentication request. - /// - /// If this is true, other properties such as @@message and @@iconName will - /// also be populated with relevant information. - Q_PROPERTY(bool isActive READ default NOTIFY isActiveChanged BINDABLE isActive); - - /// The current authentication state if an authentication request is active. - /// - /// Null when no authentication request is active. - Q_PROPERTY(AuthFlow* flow READ default NOTIFY flowChanged BINDABLE flow); - -public: - explicit PolkitAgent(QObject* parent = nullptr): QObject(parent) {}; - ~PolkitAgent() override; - - void classBegin() override {}; - void componentComplete() override; - - [[nodiscard]] QString path() const { return this->mPath; }; - void setPath(const QString& path); - - [[nodiscard]] QBindable flow() { return &this->bFlow; }; - [[nodiscard]] QBindable isActive() { return &this->bIsActive; }; - [[nodiscard]] QBindable isRegistered() { return &this->bIsRegistered; }; - -signals: - /// Emitted when an application makes a request that requires authentication. - /// - /// At this point, @@state will be populated with relevant information. - /// Note that signals for conversation outcome are emitted from the @@AuthFlow instance. - void authenticationRequestStarted(); - - void isRegisteredChanged(); - void isActiveChanged(); - void flowChanged(); - -private: - QString mPath = ""; - - Q_OBJECT_BINDABLE_PROPERTY(PolkitAgent, AuthFlow*, bFlow, &PolkitAgent::flowChanged); - Q_OBJECT_BINDABLE_PROPERTY(PolkitAgent, bool, bIsActive, &PolkitAgent::isActiveChanged); - Q_OBJECT_BINDABLE_PROPERTY(PolkitAgent, bool, bIsRegistered, &PolkitAgent::isRegisteredChanged); -}; -} // namespace qs::service::polkit diff --git a/src/services/polkit/session.cpp b/src/services/polkit/session.cpp deleted file mode 100644 index 71def68..0000000 --- a/src/services/polkit/session.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include "session.hpp" - -#include -#include -#include -#include - -#define POLKIT_AGENT_I_KNOW_API_IS_SUBJECT_TO_CHANGE -// This causes a problem with variables of the name. -#undef signals -#include -#define signals Q_SIGNALS - -namespace qs::service::polkit { - -namespace { -void completedCb(PolkitAgentSession* /*session*/, gboolean gainedAuthorization, gpointer userData) { - auto* self = static_cast(userData); - emit self->completed(gainedAuthorization); -} - -void requestCb( - PolkitAgentSession* /*session*/, - const char* message, - gboolean echo, - gpointer userData -) { - auto* self = static_cast(userData); - emit self->request(QString::fromUtf8(message), echo); -} - -void showErrorCb(PolkitAgentSession* /*session*/, const char* message, gpointer userData) { - auto* self = static_cast(userData); - emit self->showError(QString::fromUtf8(message)); -} - -void showInfoCb(PolkitAgentSession* /*session*/, const char* message, gpointer userData) { - auto* self = static_cast(userData); - emit self->showInfo(QString::fromUtf8(message)); -} -} // namespace - -Session::Session(PolkitIdentity* identity, const QString& cookie, QObject* parent) - : QObject(parent) { - this->session = polkit_agent_session_new(identity, cookie.toUtf8().constData()); - - g_signal_connect(G_OBJECT(this->session), "completed", G_CALLBACK(completedCb), this); - g_signal_connect(G_OBJECT(this->session), "request", G_CALLBACK(requestCb), this); - g_signal_connect(G_OBJECT(this->session), "show-error", G_CALLBACK(showErrorCb), this); - g_signal_connect(G_OBJECT(this->session), "show-info", G_CALLBACK(showInfoCb), this); -} - -Session::~Session() { - // Signals do not need to be disconnected explicitly. This happens during - // destruction of the gobject. Since we own the session object, we can be - // sure it is being destroyed after the unref. - g_object_unref(this->session); -} - -void Session::initiate() { polkit_agent_session_initiate(this->session); } - -void Session::cancel() { polkit_agent_session_cancel(this->session); } - -void Session::respond(const QString& response) { - polkit_agent_session_response(this->session, response.toUtf8().constData()); -} - -} // namespace qs::service::polkit diff --git a/src/services/polkit/session.hpp b/src/services/polkit/session.hpp deleted file mode 100644 index 29331b1..0000000 --- a/src/services/polkit/session.hpp +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include - -// _PolkitIdentity and _PolkitAgentSession are considered reserved identifiers, -// but I am specifically forward declaring those reserved names. - -// NOLINTBEGIN(bugprone-reserved-identifier) -using PolkitIdentity = struct _PolkitIdentity; -using PolkitAgentSession = struct _PolkitAgentSession; -// NOLINTEND(bugprone-reserved-identifier) - -namespace qs::service::polkit { -//! Represents an authentication session for a specific identity. -class Session: public QObject { - Q_OBJECT; - Q_DISABLE_COPY_MOVE(Session); - -public: - explicit Session(PolkitIdentity* identity, const QString& cookie, QObject* parent = nullptr); - ~Session() override; - - /// Call this after connecting to the relevant signals. - void initiate(); - /// Call this to abort a running authentication session. - void cancel(); - /// Provide a response to an input request. - void respond(const QString& response); - -Q_SIGNALS: - /// Emitted when the session wants to request input from the user. - /// - /// The message is a prompt to present to the user. - /// If echo is false, the user's response should not be displayed (e.g. for passwords). - void request(const QString& message, bool echo); - - /// Emitted when the authentication session completes. - /// - /// If success is true, authentication was successful. - /// Otherwise it failed (e.g. wrong password). - void completed(bool success); - - /// Emitted when an error message should be shown to the user. - void showError(const QString& message); - - /// Emitted when an informational message should be shown to the user. - void showInfo(const QString& message); - -private: - PolkitAgentSession* session = nullptr; -}; -} // namespace qs::service::polkit diff --git a/src/services/polkit/test/manual/agent.qml b/src/services/polkit/test/manual/agent.qml deleted file mode 100644 index 4588e4b..0000000 --- a/src/services/polkit/test/manual/agent.qml +++ /dev/null @@ -1,97 +0,0 @@ -import Quickshell -import Quickshell.Services.Polkit -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls - -Scope { - id: root - - FloatingWindow { - title: "Authentication Required" - - visible: polkitAgent.isActive - color: contentItem.palette.window - - ColumnLayout { - id: contentColumn - anchors.fill: parent - anchors.margins: 18 - spacing: 12 - - Item { Layout.fillHeight: true } - - Label { - Layout.fillWidth: true - text: polkitAgent.flow?.message || "" - wrapMode: Text.Wrap - font.bold: true - } - - Label { - Layout.fillWidth: true - text: polkitAgent.flow?.supplementaryMessage || "" - wrapMode: Text.Wrap - opacity: 0.8 - } - - Label { - Layout.fillWidth: true - text: polkitAgent.flow?.inputPrompt || "" - wrapMode: Text.Wrap - } - - Label { - text: "Authentication failed, try again" - color: "red" - visible: polkitAgent.flow?.failed - } - - TextField { - id: passwordInput - echoMode: polkitAgent.flow?.responseVisible - ? TextInput.Normal : TextInput.Password - selectByMouse: true - Layout.fillWidth: true - onAccepted: okButton.clicked() - } - - RowLayout { - spacing: 8 - Button { - id: okButton - text: "OK" - enabled: passwordInput.text.length > 0 || !!polkitAgent.flow?.isResponseRequired - onClicked: { - polkitAgent.flow.submit(passwordInput.text) - passwordInput.text = "" - passwordInput.forceActiveFocus() - } - } - Button { - text: "Cancel" - visible: polkitAgent.isActive - onClicked: { - polkitAgent.flow.cancelAuthenticationRequest() - passwordInput.text = "" - } - } - } - - Item { Layout.fillHeight: true } - } - - Connections { - target: polkitAgent.flow - function onIsResponseRequiredChanged() { - passwordInput.text = "" - if (polkitAgent.flow.isResponseRequired) - passwordInput.forceActiveFocus() - } - } - } - - PolkitAgent { - id: polkitAgent - } -} diff --git a/src/services/status_notifier/item.cpp b/src/services/status_notifier/item.cpp index 17404e1..4632995 100644 --- a/src/services/status_notifier/item.cpp +++ b/src/services/status_notifier/item.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -162,10 +163,6 @@ QPixmap StatusNotifierItem::createPixmap(const QSize& size) const { } else { const auto* icon = closestPixmap(size, this->bAttentionIconPixmaps.value()); - if (icon == nullptr) { - icon = closestPixmap(size, this->bIconPixmaps.value()); - } - if (icon != nullptr) { const auto image = icon->createImage().scaled(size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); @@ -221,14 +218,9 @@ void StatusNotifierItem::activate() { const QDBusPendingReply<> reply = *call; if (reply.isError()) { - if (reply.error().type() == QDBusError::UnknownMethod) { - qCDebug(logStatusNotifierItem) << "Tried to call Activate method of StatusNotifierItem" - << this->properties.toString() << "but it does not exist."; - } else { - qCWarning(logStatusNotifierItem).noquote() - << "Error calling Activate method of StatusNotifierItem" << this->properties.toString(); - qCWarning(logStatusNotifierItem) << reply.error(); - } + qCWarning(logStatusNotifierItem).noquote() + << "Error calling Activate method of StatusNotifierItem" << this->properties.toString(); + qCWarning(logStatusNotifierItem) << reply.error(); } delete call; @@ -245,16 +237,10 @@ void StatusNotifierItem::secondaryActivate() { const QDBusPendingReply<> reply = *call; if (reply.isError()) { - if (reply.error().type() == QDBusError::UnknownMethod) { - qCDebug(logStatusNotifierItem) - << "Tried to call SecondaryActivate method of StatusNotifierItem" - << this->properties.toString() << "but it does not exist."; - } else { - qCWarning(logStatusNotifierItem).noquote() - << "Error calling SecondaryActivate method of StatusNotifierItem" - << this->properties.toString(); - qCWarning(logStatusNotifierItem) << reply.error(); - } + qCWarning(logStatusNotifierItem).noquote() + << "Error calling SecondaryActivate method of StatusNotifierItem" + << this->properties.toString(); + qCWarning(logStatusNotifierItem) << reply.error(); } delete call; diff --git a/src/services/status_notifier/item.hpp b/src/services/status_notifier/item.hpp index 2eff95d..60f3a98 100644 --- a/src/services/status_notifier/item.hpp +++ b/src/services/status_notifier/item.hpp @@ -126,6 +126,13 @@ class StatusNotifierItem: public QObject { public: explicit StatusNotifierItem(const QString& address, QObject* parent = nullptr); + [[nodiscard]] bool isValid() const; + [[nodiscard]] bool isReady() const; + [[nodiscard]] QBindable bindableIcon() const { return &this->bIcon; }; + [[nodiscard]] QPixmap createPixmap(const QSize& size) const; + + [[nodiscard]] dbus::dbusmenu::DBusMenuHandle* menuHandle(); + /// Primary activation action, generally triggered via a left click. Q_INVOKABLE void activate(); /// Secondary activation action, generally triggered via a middle click. @@ -135,21 +142,14 @@ public: /// Display a platform menu at the given location relative to the parent window. Q_INVOKABLE void display(QObject* parentWindow, qint32 relativeX, qint32 relativeY); - [[nodiscard]] bool isValid() const; - [[nodiscard]] bool isReady() const; - [[nodiscard]] QBindable bindableIcon() const { return &this->bIcon; } - [[nodiscard]] QPixmap createPixmap(const QSize& size) const; - - [[nodiscard]] dbus::dbusmenu::DBusMenuHandle* menuHandle(); - - [[nodiscard]] QBindable bindableId() const { return &this->bId; } - [[nodiscard]] QBindable bindableTitle() const { return &this->bTitle; } - [[nodiscard]] QBindable bindableStatus() const { return &this->bStatus; } - [[nodiscard]] QBindable bindableCategory() const { return &this->bCategory; } - [[nodiscard]] QString tooltipTitle() const { return this->bTooltip.value().title; } - [[nodiscard]] QString tooltipDescription() const { return this->bTooltip.value().description; } - [[nodiscard]] QBindable bindableHasMenu() const { return &this->bHasMenu; } - [[nodiscard]] QBindable bindableOnlyMenu() const { return &this->bIsMenu; } + [[nodiscard]] QBindable bindableId() const { return &this->bId; }; + [[nodiscard]] QBindable bindableTitle() const { return &this->bTitle; }; + [[nodiscard]] QBindable bindableStatus() const { return &this->bStatus; }; + [[nodiscard]] QBindable bindableCategory() const { return &this->bCategory; }; + [[nodiscard]] QString tooltipTitle() const { return this->bTooltip.value().title; }; + [[nodiscard]] QString tooltipDescription() const { return this->bTooltip.value().description; }; + [[nodiscard]] QBindable bindableHasMenu() const { return &this->bHasMenu; }; + [[nodiscard]] QBindable bindableOnlyMenu() const { return &this->bIsMenu; }; signals: void ready(); @@ -207,8 +207,6 @@ private: QS_BINDING_SUBSCRIBE_METHOD(StatusNotifierItem, bOverlayIconPixmaps, updatePixmapIndex, onValueChanged); QS_BINDING_SUBSCRIBE_METHOD(StatusNotifierItem, bAttentionIconPixmaps, updatePixmapIndex, onValueChanged); QS_BINDING_SUBSCRIBE_METHOD(StatusNotifierItem, bMenuPath, onMenuPathChanged, onValueChanged); - QS_BINDING_SUBSCRIBE_METHOD(StatusNotifierItem, bTooltip, tooltipTitleChanged, onValueChanged); - QS_BINDING_SUBSCRIBE_METHOD(StatusNotifierItem, bTooltip, tooltipDescriptionChanged, onValueChanged); Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, quint32, pixmapIndex); Q_OBJECT_BINDABLE_PROPERTY(StatusNotifierItem, QString, bIcon, &StatusNotifierItem::iconChanged); diff --git a/src/services/upower/core.hpp b/src/services/upower/core.hpp index 62fca1d..e2ed4f7 100644 --- a/src/services/upower/core.hpp +++ b/src/services/upower/core.hpp @@ -22,7 +22,7 @@ class UPower: public QObject { public: [[nodiscard]] UPowerDevice* displayDevice(); [[nodiscard]] ObjectModel* devices(); - [[nodiscard]] QBindable bindableOnBattery() const { return &this->bOnBattery; } + [[nodiscard]] QBindable bindableOnBattery() const { return &this->bOnBattery; }; static UPower* instance(); diff --git a/src/services/upower/device.cpp b/src/services/upower/device.cpp index 63382ad..2492b1f 100644 --- a/src/services/upower/device.cpp +++ b/src/services/upower/device.cpp @@ -73,7 +73,7 @@ UPowerDevice::UPowerDevice(QObject* parent): QObject(parent) { return this->bType == UPowerDeviceType::Battery && this->bPowerSupply; }); - this->bHealthSupported.setBinding([this]() { return this->bHealthPercentage.value() != 0; }); + this->bHealthSupported.setBinding([this]() { return this->bHealthPercentage != 0; }); } void UPowerDevice::init(const QString& path) { @@ -101,7 +101,7 @@ QString UPowerDevice::address() const { return this->device ? this->device->serv QString UPowerDevice::path() const { return this->device ? this->device->path() : QString(); } void UPowerDevice::onGetAllFinished() { - qCDebug(logUPowerDevice) << "UPowerDevice" << this->device->path() << "ready."; + qCDebug(logUPowerDevice) << "UPowerDevice" << device->path() << "ready."; this->bReady = true; } @@ -126,8 +126,8 @@ DBusDataTransform::fromWire(quint32 wire) { ); } -DBusResult -DBusDataTransform::fromWire(quint32 wire) { +DBusResult DBusDataTransform::fromWire(quint32 wire +) { if (wire >= UPowerDeviceType::Unknown && wire <= UPowerDeviceType::BluetoothGeneric) { return DBusResult(static_cast(wire)); } diff --git a/src/services/upower/device.hpp b/src/services/upower/device.hpp index a4fbe83..b2b5f02 100644 --- a/src/services/upower/device.hpp +++ b/src/services/upower/device.hpp @@ -173,25 +173,25 @@ public: [[nodiscard]] QString address() const; [[nodiscard]] QString path() const; - [[nodiscard]] QBindable bindableType() const { return &this->bType; } - [[nodiscard]] QBindable bindablePowerSupply() const { return &this->bPowerSupply; } - [[nodiscard]] QBindable bindableEnergy() const { return &this->bEnergy; } - [[nodiscard]] QBindable bindableEnergyCapacity() const { return &this->bEnergyCapacity; } - [[nodiscard]] QBindable bindableChangeRate() const { return &this->bChangeRate; } - [[nodiscard]] QBindable bindableTimeToEmpty() const { return &this->bTimeToEmpty; } - [[nodiscard]] QBindable bindableTimeToFull() const { return &this->bTimeToFull; } - [[nodiscard]] QBindable bindablePercentage() const { return &this->bPercentage; } - [[nodiscard]] QBindable bindableIsPresent() const { return &this->bIsPresent; } - [[nodiscard]] QBindable bindableState() const { return &this->bState; } + [[nodiscard]] QBindable bindableType() const { return &this->bType; }; + [[nodiscard]] QBindable bindablePowerSupply() const { return &this->bPowerSupply; }; + [[nodiscard]] QBindable bindableEnergy() const { return &this->bEnergy; }; + [[nodiscard]] QBindable bindableEnergyCapacity() const { return &this->bEnergyCapacity; }; + [[nodiscard]] QBindable bindableChangeRate() const { return &this->bChangeRate; }; + [[nodiscard]] QBindable bindableTimeToEmpty() const { return &this->bTimeToEmpty; }; + [[nodiscard]] QBindable bindableTimeToFull() const { return &this->bTimeToFull; }; + [[nodiscard]] QBindable bindablePercentage() const { return &this->bPercentage; }; + [[nodiscard]] QBindable bindableIsPresent() const { return &this->bIsPresent; }; + [[nodiscard]] QBindable bindableState() const { return &this->bState; }; [[nodiscard]] QBindable bindableHealthPercentage() const { return &this->bHealthPercentage; - } - [[nodiscard]] QBindable bindableHealthSupported() const { return &this->bHealthSupported; } - [[nodiscard]] QBindable bindableIconName() const { return &this->bIconName; } - [[nodiscard]] QBindable bindableIsLaptopBattery() const { return &this->bIsLaptopBattery; } - [[nodiscard]] QBindable bindableNativePath() const { return &this->bNativePath; } - [[nodiscard]] QBindable bindableModel() const { return &this->bModel; } - [[nodiscard]] QBindable bindableReady() const { return &this->bReady; } + }; + [[nodiscard]] QBindable bindableHealthSupported() const { return &this->bHealthSupported; }; + [[nodiscard]] QBindable bindableIconName() const { return &this->bIconName; }; + [[nodiscard]] QBindable bindableIsLaptopBattery() const { return &this->bIsLaptopBattery; }; + [[nodiscard]] QBindable bindableNativePath() const { return &this->bNativePath; }; + [[nodiscard]] QBindable bindableModel() const { return &this->bModel; }; + [[nodiscard]] QBindable bindableReady() const { return &this->bReady; }; signals: QSDOC_HIDE void readyChanged(); diff --git a/src/services/upower/powerprofiles.cpp b/src/services/upower/powerprofiles.cpp index f59b871..4c40798 100644 --- a/src/services/upower/powerprofiles.cpp +++ b/src/services/upower/powerprofiles.cpp @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include "../../core/logcat.hpp" #include "../../dbus/bus.hpp" @@ -66,8 +66,7 @@ PowerProfiles::PowerProfiles() { auto bus = QDBusConnection::systemBus(); if (!bus.isConnected()) { - qCWarning( - logPowerProfiles + qCWarning(logPowerProfiles ) << "Could not connect to DBus. PowerProfiles services will not work."; } @@ -80,8 +79,7 @@ PowerProfiles::PowerProfiles() { ); if (!this->service->isValid()) { - qCDebug( - logPowerProfiles + qCDebug(logPowerProfiles ) << "PowerProfilesDaemon is not currently running, attempting to start it."; dbus::tryLaunchService(this, bus, "org.freedesktop.UPower.PowerProfiles", [this](bool success) { @@ -105,15 +103,13 @@ void PowerProfiles::init() { void PowerProfiles::setProfile(PowerProfile::Enum profile) { if (!this->properties.isConnected()) { - qCCritical( - logPowerProfiles + qCCritical(logPowerProfiles ) << "Cannot set power profile: power-profiles-daemon not accessible or not running"; return; } if (profile == PowerProfile::Performance && !this->bHasPerformanceProfile) { - qCCritical( - logPowerProfiles + qCCritical(logPowerProfiles ) << "Cannot request performance profile as it is not present for this device."; return; } else if (profile < PowerProfile::PowerSaver || profile > PowerProfile::Performance) { @@ -139,9 +135,8 @@ PowerProfilesQml::PowerProfilesQml(QObject* parent): QObject(parent) { return instance->bHasPerformanceProfile.value(); }); - this->bDegradationReason.setBinding([instance]() { - return instance->bDegradationReason.value(); - }); + this->bDegradationReason.setBinding([instance]() { return instance->bDegradationReason.value(); } + ); this->bHolds.setBinding([instance]() { return instance->bHolds.value(); }); } @@ -169,7 +164,6 @@ QString DBusDataTransform::toWire(Data data) { case PowerProfile::PowerSaver: return QStringLiteral("power-saver"); case PowerProfile::Balanced: return QStringLiteral("balanced"); case PowerProfile::Performance: return QStringLiteral("performance"); - default: qFatal() << "Attempted to convert invalid power profile" << data << "to wire format."; } } diff --git a/src/ui/reload_popup.cpp b/src/ui/reload_popup.cpp index f000374..8e58dc9 100644 --- a/src/ui/reload_popup.cpp +++ b/src/ui/reload_popup.cpp @@ -25,7 +25,7 @@ ReloadPopup::ReloadPopup(QString instanceId, bool failed, QString errorString) this->popup = component.createWithInitialProperties({{"reloadInfo", QVariant::fromValue(this)}}); - if (!this->popup) { + if (!popup) { qCritical() << "Failed to open reload popup:" << component.errorString(); } diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt index db53f37..ede66c5 100644 --- a/src/wayland/CMakeLists.txt +++ b/src/wayland/CMakeLists.txt @@ -1,6 +1,6 @@ find_package(PkgConfig REQUIRED) find_package(WaylandScanner REQUIRED) -pkg_check_modules(wayland REQUIRED IMPORTED_TARGET wayland-client wayland-protocols>=1.41) +pkg_check_modules(wayland REQUIRED IMPORTED_TARGET wayland-client wayland-protocols) # wayland protocols @@ -12,13 +12,13 @@ if(NOT TARGET Qt6::qtwaylandscanner) message(FATAL_ERROR "qtwaylandscanner executable not found. Most likely there is an issue with your Qt installation.") endif() -pkg_get_variable(WAYLAND_PROTOCOLS wayland-protocols pkgdatadir) +execute_process( + COMMAND pkg-config --variable=pkgdatadir wayland-protocols + OUTPUT_VARIABLE WAYLAND_PROTOCOLS + OUTPUT_STRIP_TRAILING_WHITESPACE +) -if(WAYLAND_PROTOCOLS) - message(STATUS "Found wayland protocols at ${WAYLAND_PROTOCOLS}") -else() - message(FATAL_ERROR "Could not find wayland protocols") -endif() +message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}") qs_add_pchset(wayland-protocol DEPENDENCIES Qt::Core Qt::WaylandClient Qt::WaylandClientPrivate @@ -114,15 +114,6 @@ if (HYPRLAND) add_subdirectory(hyprland) endif() -add_subdirectory(idle_inhibit) -list(APPEND WAYLAND_MODULES Quickshell.Wayland._IdleInhibitor) - -add_subdirectory(idle_notify) -list(APPEND WAYLAND_MODULES Quickshell.Wayland._IdleNotify) - -add_subdirectory(shortcuts_inhibit) -list(APPEND WAYLAND_MODULES Quickshell.Wayland._ShortcutsInhibitor) - add_subdirectory(windowmanager) # widgets for qmenu diff --git a/src/wayland/buffer/CMakeLists.txt b/src/wayland/buffer/CMakeLists.txt index 15818fc..f80c53a 100644 --- a/src/wayland/buffer/CMakeLists.txt +++ b/src/wayland/buffer/CMakeLists.txt @@ -1,8 +1,6 @@ find_package(PkgConfig REQUIRED) pkg_check_modules(dmabuf-deps REQUIRED IMPORTED_TARGET libdrm gbm egl) -find_package(VulkanHeaders REQUIRED) - qt_add_library(quickshell-wayland-buffer STATIC manager.cpp dmabuf.cpp @@ -12,10 +10,9 @@ qt_add_library(quickshell-wayland-buffer STATIC wl_proto(wlp-linux-dmabuf linux-dmabuf-v1 "${WAYLAND_PROTOCOLS}/stable/linux-dmabuf") target_link_libraries(quickshell-wayland-buffer PRIVATE - Qt::Quick Qt::QuickPrivate Qt::GuiPrivate Qt::WaylandClient Qt::WaylandClientPrivate wayland-client + Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client PkgConfig::dmabuf-deps wlp-linux-dmabuf - Vulkan::Headers ) qs_pch(quickshell-wayland-buffer SET large) diff --git a/src/wayland/buffer/dmabuf.cpp b/src/wayland/buffer/dmabuf.cpp index ed9dbeb..4593389 100644 --- a/src/wayland/buffer/dmabuf.cpp +++ b/src/wayland/buffer/dmabuf.cpp @@ -1,6 +1,7 @@ #include "dmabuf.hpp" #include #include +#include #include #include #include @@ -14,8 +15,6 @@ #include #include #include -#include -#include #include #include #include @@ -26,17 +25,12 @@ #include #include #include -#include #include -#include -#include -#include #include #include #include #include #include -#include #include #include #include @@ -55,36 +49,6 @@ QS_LOGGING_CATEGORY(logDmabuf, "quickshell.wayland.buffer.dmabuf", QtWarningMsg) LinuxDmabufManager* MANAGER = nullptr; // NOLINT -VkFormat drmFormatToVkFormat(uint32_t drmFormat) { - // NOLINTBEGIN(bugprone-branch-clone): XRGB/ARGB intentionally map to the same VK format - switch (drmFormat) { - case DRM_FORMAT_ARGB8888: return VK_FORMAT_B8G8R8A8_UNORM; - case DRM_FORMAT_XRGB8888: return VK_FORMAT_B8G8R8A8_UNORM; - case DRM_FORMAT_ABGR8888: return VK_FORMAT_R8G8B8A8_UNORM; - case DRM_FORMAT_XBGR8888: return VK_FORMAT_R8G8B8A8_UNORM; - case DRM_FORMAT_ARGB2101010: return VK_FORMAT_A2R10G10B10_UNORM_PACK32; - case DRM_FORMAT_XRGB2101010: return VK_FORMAT_A2R10G10B10_UNORM_PACK32; - case DRM_FORMAT_ABGR2101010: return VK_FORMAT_A2B10G10R10_UNORM_PACK32; - case DRM_FORMAT_XBGR2101010: return VK_FORMAT_A2B10G10R10_UNORM_PACK32; - case DRM_FORMAT_ABGR16161616F: return VK_FORMAT_R16G16B16A16_SFLOAT; - case DRM_FORMAT_RGB565: return VK_FORMAT_R5G6B5_UNORM_PACK16; - case DRM_FORMAT_BGR565: return VK_FORMAT_B5G6R5_UNORM_PACK16; - default: return VK_FORMAT_UNDEFINED; - } - // NOLINTEND(bugprone-branch-clone) -} - -bool drmFormatHasAlpha(uint32_t drmFormat) { - switch (drmFormat) { - case DRM_FORMAT_ARGB8888: - case DRM_FORMAT_ABGR8888: - case DRM_FORMAT_ARGB2101010: - case DRM_FORMAT_ABGR2101010: - case DRM_FORMAT_ABGR16161616F: return true; - default: return false; - } -} - } // namespace QDebug& operator<<(QDebug& debug, const FourCCStr& fourcc) { @@ -113,32 +77,30 @@ QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer) { } GbmDeviceHandle::~GbmDeviceHandle() { - if (this->device) { + if (device) { MANAGER->unrefGbmDevice(this->device); } } -// Prefer ARGB over XRGB: XRGB has undefined alpha bytes which cause -// transparency artifacts on Vulkan (notably Intel GPUs) since Vulkan -// doesn't auto-fill alpha=1.0 for X formats like EGL does. +// This will definitely backfire later void LinuxDmabufFormatSelection::ensureSorted() { if (this->sorted) return; auto beginIter = this->formats.begin(); - auto argbIter = std::ranges::find_if(this->formats, [](const auto& format) { - return format.first == DRM_FORMAT_ARGB8888; - }); - - if (argbIter != this->formats.end()) { - std::swap(*beginIter, *argbIter); - ++beginIter; - } - auto xrgbIter = std::ranges::find_if(this->formats, [](const auto& format) { return format.first == DRM_FORMAT_XRGB8888; }); - if (xrgbIter != this->formats.end()) std::swap(*beginIter, *xrgbIter); + if (xrgbIter != this->formats.end()) { + std::swap(*beginIter, *xrgbIter); + ++beginIter; + } + + auto argbIter = std::ranges::find_if(this->formats, [](const auto& format) { + return format.first == DRM_FORMAT_ARGB8888; + }); + + if (argbIter != this->formats.end()) std::swap(*beginIter, *argbIter); this->sorted = true; } @@ -452,8 +414,7 @@ WlBuffer* LinuxDmabufManager::createDmabuf( if (modifiers.modifiers.isEmpty()) { if (!modifiers.implicit) { - qCritical( - logDmabuf + qCritical(logDmabuf ) << "Failed to create gbm_bo: format supports no implicit OR explicit modifiers."; return nullptr; } @@ -561,7 +522,7 @@ WlDmaBuffer::~WlDmaBuffer() { bool WlDmaBuffer::isCompatible(const WlBufferRequest& request) const { if (request.width != this->width || request.height != this->height) return false; - auto matchingFormat = std::ranges::find_if(request.dmabuf.formats, [this](const auto& format) { + auto matchingFormat = std::ranges::find_if(request.dmabuf.formats, [&](const auto& format) { return format.format == this->format && (format.modifiers.isEmpty() || std::ranges::find(format.modifiers, this->modifier) != format.modifiers.end()); @@ -571,15 +532,6 @@ bool WlDmaBuffer::isCompatible(const WlBufferRequest& request) const { } WlBufferQSGTexture* WlDmaBuffer::createQsgTexture(QQuickWindow* window) const { - auto* ri = window->rendererInterface(); - if (ri && ri->graphicsApi() == QSGRendererInterface::Vulkan) { - return this->createQsgTextureVulkan(window); - } - - return this->createQsgTextureGl(window); -} - -WlBufferQSGTexture* WlDmaBuffer::createQsgTextureGl(QQuickWindow* window) const { static auto* glEGLImageTargetTexture2DOES = []() { auto* fn = reinterpret_cast( eglGetProcAddress("glEGLImageTargetTexture2DOES") @@ -710,313 +662,6 @@ WlBufferQSGTexture* WlDmaBuffer::createQsgTextureGl(QQuickWindow* window) const return tex; } -WlBufferQSGTexture* WlDmaBuffer::createQsgTextureVulkan(QQuickWindow* window) const { - auto* ri = window->rendererInterface(); - auto* vkInst = window->vulkanInstance(); - - if (!vkInst) { - qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: no QVulkanInstance."; - return nullptr; - } - - auto* vkDevicePtr = - static_cast(ri->getResource(window, QSGRendererInterface::DeviceResource)); - auto* vkPhysDevicePtr = static_cast( - ri->getResource(window, QSGRendererInterface::PhysicalDeviceResource) - ); - - if (!vkDevicePtr || !vkPhysDevicePtr) { - qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: could not get Vulkan device."; - return nullptr; - } - - VkDevice device = *vkDevicePtr; - VkPhysicalDevice physDevice = *vkPhysDevicePtr; - - auto* devFuncs = vkInst->deviceFunctions(device); - auto* instFuncs = vkInst->functions(); - - if (!devFuncs || !instFuncs) { - qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: " - "could not get Vulkan functions."; - return nullptr; - } - - auto getMemoryFdPropertiesKHR = reinterpret_cast( - instFuncs->vkGetDeviceProcAddr(device, "vkGetMemoryFdPropertiesKHR") - ); - - if (!getMemoryFdPropertiesKHR) { - qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: " - "vkGetMemoryFdPropertiesKHR not available. " - "Missing VK_KHR_external_memory_fd extension."; - return nullptr; - } - - const VkFormat vkFormat = drmFormatToVkFormat(this->format); - if (vkFormat == VK_FORMAT_UNDEFINED) { - qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: unsupported DRM format" - << FourCCStr(this->format); - return nullptr; - } - - if (this->planeCount > 4) { - qCWarning(logDmabuf) << "Failed to create Vulkan QSG texture: too many planes" - << this->planeCount; - return nullptr; - } - - std::array planeLayouts = {}; - for (int i = 0; i < this->planeCount; ++i) { - planeLayouts[i].offset = this->planes[i].offset; // NOLINT - planeLayouts[i].rowPitch = this->planes[i].stride; // NOLINT - planeLayouts[i].size = 0; - planeLayouts[i].arrayPitch = 0; - planeLayouts[i].depthPitch = 0; - } - - const bool useModifier = this->modifier != DRM_FORMAT_MOD_INVALID; - - VkExternalMemoryImageCreateInfo externalInfo = {}; - externalInfo.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO; - externalInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT; - - VkImageDrmFormatModifierExplicitCreateInfoEXT modifierInfo = {}; - modifierInfo.sType = VK_STRUCTURE_TYPE_IMAGE_DRM_FORMAT_MODIFIER_EXPLICIT_CREATE_INFO_EXT; - modifierInfo.drmFormatModifier = this->modifier; - modifierInfo.drmFormatModifierPlaneCount = static_cast(this->planeCount); - modifierInfo.pPlaneLayouts = planeLayouts.data(); - - if (useModifier) { - externalInfo.pNext = &modifierInfo; - } - - VkImageCreateInfo imageInfo = {}; - imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - imageInfo.pNext = &externalInfo; - imageInfo.imageType = VK_IMAGE_TYPE_2D; - imageInfo.format = vkFormat; - imageInfo.extent = {.width = this->width, .height = this->height, .depth = 1}; - imageInfo.mipLevels = 1; - imageInfo.arrayLayers = 1; - imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; - imageInfo.tiling = useModifier ? VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT : VK_IMAGE_TILING_LINEAR; - imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT; - imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - - VkImage image = VK_NULL_HANDLE; - VkResult result = devFuncs->vkCreateImage(device, &imageInfo, nullptr, &image); - if (result != VK_SUCCESS) { - qCWarning(logDmabuf) << "Failed to create VkImage for DMA-BUF import, result:" << result; - return nullptr; - } - - VkDeviceMemory memory = VK_NULL_HANDLE; - - // dup() is required because vkAllocateMemory with VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT - // takes ownership of the fd on succcess. Without dup, WlDmaBuffer would double-close. - const int dupFd = - dup(this->planes[0].fd); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) - if (dupFd < 0) { - qCWarning(logDmabuf) << "Failed to dup() fd for DMA-BUF import"; - goto cleanup_fail; // NOLINT - } - - { - VkMemoryRequirements memReqs = {}; - devFuncs->vkGetImageMemoryRequirements(device, image, &memReqs); - - VkMemoryFdPropertiesKHR fdProps = {}; - fdProps.sType = VK_STRUCTURE_TYPE_MEMORY_FD_PROPERTIES_KHR; - - result = getMemoryFdPropertiesKHR( - device, - VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT, - dupFd, - &fdProps - ); - - if (result != VK_SUCCESS) { - close(dupFd); - qCWarning(logDmabuf) << "vkGetMemoryFdPropertiesKHR failed, result:" << result; - goto cleanup_fail; // NOLINT - } - - const uint32_t memTypeBits = memReqs.memoryTypeBits & fdProps.memoryTypeBits; - - VkPhysicalDeviceMemoryProperties memProps = {}; - instFuncs->vkGetPhysicalDeviceMemoryProperties(physDevice, &memProps); - - uint32_t memTypeIndex = UINT32_MAX; - for (uint32_t j = 0; j < memProps.memoryTypeCount; ++j) { - if (memTypeBits & (1u << j)) { - memTypeIndex = j; - break; - } - } - - if (memTypeIndex == UINT32_MAX) { - close(dupFd); - qCWarning(logDmabuf) << "No compatible memory type for DMA-BUF import"; - goto cleanup_fail; // NOLINT - } - - VkImportMemoryFdInfoKHR importInfo = {}; - importInfo.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR; - importInfo.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT; - importInfo.fd = dupFd; - - VkMemoryDedicatedAllocateInfo dedicatedInfo = {}; - dedicatedInfo.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO; - dedicatedInfo.image = image; - dedicatedInfo.pNext = &importInfo; - - VkMemoryAllocateInfo allocInfo = {}; - allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; - allocInfo.pNext = &dedicatedInfo; - allocInfo.allocationSize = memReqs.size; - allocInfo.memoryTypeIndex = memTypeIndex; - - result = devFuncs->vkAllocateMemory(device, &allocInfo, nullptr, &memory); - if (result != VK_SUCCESS) { - close(dupFd); - qCWarning(logDmabuf) << "vkAllocateMemory failed, result:" << result; - goto cleanup_fail; // NOLINT - } - - result = devFuncs->vkBindImageMemory(device, image, memory, 0); - if (result != VK_SUCCESS) { - qCWarning(logDmabuf) << "vkBindImageMemory failed, result:" << result; - goto cleanup_fail; // NOLINT - } - } - - { - // acquire the DMA-BUF from the foreign (compositor) queue and transition - // to shader-read layout. oldLayout must be GENERAL (not UNDEFINED) to - // preserve the DMA-BUF contents written by the external producer. Hopefully. - window->beginExternalCommands(); - - auto* cmdBufPtr = static_cast( - ri->getResource(window, QSGRendererInterface::CommandListResource) - ); - - if (cmdBufPtr && *cmdBufPtr) { - VkCommandBuffer cmdBuf = *cmdBufPtr; - - // find the graphics queue family index for the ownrship transfer. - uint32_t graphicsQueueFamily = 0; - uint32_t queueFamilyCount = 0; - instFuncs->vkGetPhysicalDeviceQueueFamilyProperties(physDevice, &queueFamilyCount, nullptr); - std::vector queueFamilies(queueFamilyCount); - instFuncs->vkGetPhysicalDeviceQueueFamilyProperties( - physDevice, - &queueFamilyCount, - queueFamilies.data() - ); - for (uint32_t i = 0; i < queueFamilyCount; ++i) { - if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { - graphicsQueueFamily = i; - break; - } - } - - VkImageMemoryBarrier barrier = {}; - barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; - barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT; - barrier.dstQueueFamilyIndex = graphicsQueueFamily; - barrier.image = image; - barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - barrier.subresourceRange.baseMipLevel = 0; - barrier.subresourceRange.levelCount = 1; - barrier.subresourceRange.baseArrayLayer = 0; - barrier.subresourceRange.layerCount = 1; - barrier.srcAccessMask = 0; - barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; - - devFuncs->vkCmdPipelineBarrier( - cmdBuf, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, - 0, - 0, - nullptr, - 0, - nullptr, - 1, - &barrier - ); - } - - window->endExternalCommands(); - - auto* qsgTexture = QQuickWindowPrivate::get(window)->createTextureFromNativeTexture( - reinterpret_cast(image), - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - static_cast(vkFormat), - QSize(static_cast(this->width), static_cast(this->height)), - {} - ); - - // For opaque DRM formats (XRGB, XBGR, etc.), the alpha bytes are underfined. - // EGL silently forces alpha=1.0 for these, but Vulkan doesn't. Replace Qt's - // default identity-swizzle VkImageView with one that maps alpha to ONE. - if (!drmFormatHasAlpha(this->format)) { - auto* vkTexture = static_cast(qsgTexture->rhiTexture()); // NOLINT - - devFuncs->vkDestroyImageView(device, vkTexture->imageView, nullptr); - - VkImageViewCreateInfo viewInfo = {}; - viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - viewInfo.image = image; - viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - viewInfo.format = vkFormat; - viewInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; - viewInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; - viewInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; - viewInfo.components.a = VK_COMPONENT_SWIZZLE_ONE; - viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - viewInfo.subresourceRange.levelCount = 1; - viewInfo.subresourceRange.layerCount = 1; - - result = devFuncs->vkCreateImageView(device, &viewInfo, nullptr, &vkTexture->imageView); - if (result != VK_SUCCESS) { - qCWarning(logDmabuf) << "Failed to create alpha-swizzled VkImageView, result:" << result; - } - } - - auto* tex = new WlDmaBufferVulkanQSGTexture(devFuncs, device, image, memory, qsgTexture); - qCDebug(logDmabuf) << "Created WlDmaBufferVulkanQSGTexture" << tex << "from" << this; - return tex; - } - -cleanup_fail: - if (image != VK_NULL_HANDLE) { - devFuncs->vkDestroyImage(device, image, nullptr); - } - if (memory != VK_NULL_HANDLE) { - devFuncs->vkFreeMemory(device, memory, nullptr); - } - return nullptr; -} - -WlDmaBufferVulkanQSGTexture::~WlDmaBufferVulkanQSGTexture() { - delete this->qsgTexture; - - if (this->image != VK_NULL_HANDLE) { - this->devFuncs->vkDestroyImage(this->device, this->image, nullptr); - } - - if (this->memory != VK_NULL_HANDLE) { - this->devFuncs->vkFreeMemory(this->device, this->memory, nullptr); - } - - qCDebug(logDmabuf) << "WlDmaBufferVulkanQSGTexture" << this << "destroyed."; -} - WlDmaBufferQSGTexture::~WlDmaBufferQSGTexture() { auto* context = QOpenGLContext::currentContext(); auto* display = context->nativeInterface()->display(); diff --git a/src/wayland/buffer/dmabuf.hpp b/src/wayland/buffer/dmabuf.hpp index ffe5d02..a05e82a 100644 --- a/src/wayland/buffer/dmabuf.hpp +++ b/src/wayland/buffer/dmabuf.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include @@ -13,11 +12,9 @@ #include #include #include -#include #include #include #include -#include #include #include #include @@ -43,7 +40,7 @@ public: '\0'} ) { for (auto i = 3; i != 0; i--) { - if (this->chars[i] == ' ') this->chars[i] = '\0'; + if (chars[i] == ' ') chars[i] = '\0'; else break; } } @@ -117,36 +114,6 @@ private: friend class WlDmaBuffer; }; -class WlDmaBufferVulkanQSGTexture: public WlBufferQSGTexture { -public: - ~WlDmaBufferVulkanQSGTexture() override; - Q_DISABLE_COPY_MOVE(WlDmaBufferVulkanQSGTexture); - - [[nodiscard]] QSGTexture* texture() const override { return this->qsgTexture; } - -private: - WlDmaBufferVulkanQSGTexture( - QVulkanDeviceFunctions* devFuncs, - VkDevice device, - VkImage image, - VkDeviceMemory memory, - QSGTexture* qsgTexture - ) - : devFuncs(devFuncs) - , device(device) - , image(image) - , memory(memory) - , qsgTexture(qsgTexture) {} - - QVulkanDeviceFunctions* devFuncs = nullptr; - VkDevice device = VK_NULL_HANDLE; - VkImage image = VK_NULL_HANDLE; - VkDeviceMemory memory = VK_NULL_HANDLE; - QSGTexture* qsgTexture = nullptr; - - friend class WlDmaBuffer; -}; - class WlDmaBuffer: public WlBuffer { public: ~WlDmaBuffer() override; @@ -184,9 +151,6 @@ private: friend class LinuxDmabufManager; friend QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer); - - [[nodiscard]] WlBufferQSGTexture* createQsgTextureGl(QQuickWindow* window) const; - [[nodiscard]] WlBufferQSGTexture* createQsgTextureVulkan(QQuickWindow* window) const; }; QDebug& operator<<(QDebug& debug, const WlDmaBuffer* buffer); diff --git a/src/wayland/buffer/manager.cpp b/src/wayland/buffer/manager.cpp index 713752a..c7448df 100644 --- a/src/wayland/buffer/manager.cpp +++ b/src/wayland/buffer/manager.cpp @@ -22,8 +22,6 @@ namespace { QS_LOGGING_CATEGORY(logBuffer, "quickshell.wayland.buffer", QtWarningMsg); } -void WlBufferRequest::reset() { *this = WlBufferRequest(); } - WlBuffer* WlBufferSwapchain::createBackbuffer(const WlBufferRequest& request, bool* newBuffer) { auto& buffer = this->presentSecondBuffer ? this->buffer1 : this->buffer2; @@ -55,8 +53,7 @@ bool WlBufferManager::isReady() const { return this->p->mReady; } << " (disabled: " << dmabufDisabled << ')'; for (const auto& [format, modifiers]: request.dmabuf.formats) { - qCDebug(logBuffer).nospace() << " Format " << dmabuf::FourCCStr(format) - << (modifiers.length() == 0 ? " (No modifiers specified)" : ""); + qCDebug(logBuffer) << " Format" << dmabuf::FourCCStr(format); for (const auto& modifier: modifiers) { qCDebug(logBuffer) << " Explicit Modifier" << dmabuf::FourCCModStr(modifier); @@ -69,11 +66,6 @@ bool WlBufferManager::isReady() const { return this->p->mReady; } qCDebug(logBuffer) << " Format" << format; } - if (request.width == 0 || request.height == 0) { - qCWarning(logBuffer) << "Cannot create zero-sized buffer."; - return nullptr; - } - if (!dmabufDisabled) { if (auto* buf = this->p->dmabuf.createDmabuf(request)) return buf; qCWarning(logBuffer) << "DMA buffer creation failed, falling back to SHM."; diff --git a/src/wayland/buffer/manager.hpp b/src/wayland/buffer/manager.hpp index 8abc218..b521e89 100644 --- a/src/wayland/buffer/manager.hpp +++ b/src/wayland/buffer/manager.hpp @@ -68,8 +68,6 @@ struct WlBufferRequest { dev_t device = 0; StackList formats; } dmabuf; - - void reset(); }; class WlBuffer { diff --git a/src/wayland/hyprland/CMakeLists.txt b/src/wayland/hyprland/CMakeLists.txt index 66b32b6..570cbe5 100644 --- a/src/wayland/hyprland/CMakeLists.txt +++ b/src/wayland/hyprland/CMakeLists.txt @@ -30,7 +30,6 @@ qt_add_qml_module(quickshell-hyprland IMPORTS ${HYPRLAND_MODULES} ) -qs_add_module_deps_light(quickshell-io Quickshell) install_qml_module(quickshell-hyprland) # intentionally no pch as the module is empty diff --git a/src/wayland/hyprland/focus_grab/qml.hpp b/src/wayland/hyprland/focus_grab/qml.hpp index 705b0d3..4ba7227 100644 --- a/src/wayland/hyprland/focus_grab/qml.hpp +++ b/src/wayland/hyprland/focus_grab/qml.hpp @@ -56,8 +56,6 @@ class HyprlandFocusGrab : public QObject , public QQmlParserStatus { Q_OBJECT; - QML_ELEMENT; - Q_INTERFACES(QQmlParserStatus); /// If the focus grab is active. Defaults to false. /// /// When set to true, an input grab will be created for the listed windows. @@ -68,6 +66,7 @@ class HyprlandFocusGrab Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged); /// The list of windows to whitelist for input. Q_PROPERTY(QList windows READ windows WRITE setWindows NOTIFY windowsChanged); + QML_ELEMENT; public: explicit HyprlandFocusGrab(QObject* parent = nullptr): QObject(parent) {} diff --git a/src/wayland/hyprland/ipc/connection.cpp b/src/wayland/hyprland/ipc/connection.cpp index ad091a6..067b922 100644 --- a/src/wayland/hyprland/ipc/connection.cpp +++ b/src/wayland/hyprland/ipc/connection.cpp @@ -442,8 +442,8 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) { if (!ok) return; auto workspaceName = QString::fromUtf8(args.at(1)); - auto windowClass = QString::fromUtf8(args.at(2)); - auto windowTitle = QString::fromUtf8(args.at(3)); + auto windowTitle = QString::fromUtf8(args.at(2)); + auto windowClass = QString::fromUtf8(args.at(3)); auto* workspace = this->findWorkspaceByName(workspaceName, false); if (!workspace) { @@ -484,7 +484,6 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) { } auto* toplevel = *toplevelIter; - if (toplevel == this->bActiveToplevel.value()) this->bActiveToplevel = nullptr; auto index = toplevelIter - mList.begin(); this->mToplevels.removeAt(index); diff --git a/src/wayland/hyprland/ipc/qml.cpp b/src/wayland/hyprland/ipc/qml.cpp index eb5fdc6..89eec9e 100644 --- a/src/wayland/hyprland/ipc/qml.cpp +++ b/src/wayland/hyprland/ipc/qml.cpp @@ -24,9 +24,9 @@ HyprlandIpcQml::HyprlandIpcQml() { QObject::connect( instance, - &HyprlandIpc::focusedWorkspaceChanged, + &HyprlandIpc::focusedMonitorChanged, this, - &HyprlandIpcQml::focusedWorkspaceChanged + &HyprlandIpcQml::focusedMonitorChanged ); QObject::connect( diff --git a/src/wayland/hyprland/ipc/workspace.cpp b/src/wayland/hyprland/ipc/workspace.cpp index c5d63b0..d16c821 100644 --- a/src/wayland/hyprland/ipc/workspace.cpp +++ b/src/wayland/hyprland/ipc/workspace.cpp @@ -151,7 +151,7 @@ void HyprlandWorkspace::clearUrgent() { } void HyprlandWorkspace::activate() { - this->ipc->dispatch(QString("workspace %1").arg(this->bName.value())); + this->ipc->dispatch(QString("workspace %1").arg(this->bId.value())); } } // namespace qs::hyprland::ipc diff --git a/src/wayland/hyprland/ipc/workspace.hpp b/src/wayland/hyprland/ipc/workspace.hpp index a0b09cf..957639a 100644 --- a/src/wayland/hyprland/ipc/workspace.hpp +++ b/src/wayland/hyprland/ipc/workspace.hpp @@ -54,7 +54,7 @@ public: /// /// > [!NOTE] This is equivalent to running /// > ```qml - /// > HyprlandIpc.dispatch(`workspace ${workspace.name}`); + /// > HyprlandIpc.dispatch(`workspace ${workspace.id}`); /// > ``` Q_INVOKABLE void activate(); diff --git a/src/wayland/hyprland/surface/qml.cpp b/src/wayland/hyprland/surface/qml.cpp index c4f7d67..b00ee33 100644 --- a/src/wayland/hyprland/surface/qml.cpp +++ b/src/wayland/hyprland/surface/qml.cpp @@ -65,8 +65,7 @@ void HyprlandWindow::setOpacity(qreal opacity) { if (opacity == this->mOpacity) return; if (opacity < 0.0 || opacity > 1.0) { - qmlWarning( - this + qmlWarning(this ) << "Cannot set HyprlandWindow.opacity to a value larger than 1.0 or smaller than 0.0"; return; } diff --git a/src/wayland/hyprland/surface/surface.cpp b/src/wayland/hyprland/surface/surface.cpp index 774acd0..f49ab8f 100644 --- a/src/wayland/hyprland/surface/surface.cpp +++ b/src/wayland/hyprland/surface/surface.cpp @@ -1,4 +1,5 @@ #include "surface.hpp" +#include #include #include diff --git a/src/wayland/idle_inhibit/CMakeLists.txt b/src/wayland/idle_inhibit/CMakeLists.txt deleted file mode 100644 index eb346d6..0000000 --- a/src/wayland/idle_inhibit/CMakeLists.txt +++ /dev/null @@ -1,25 +0,0 @@ -qt_add_library(quickshell-wayland-idle-inhibit STATIC - proto.cpp - inhibitor.cpp -) - -qt_add_qml_module(quickshell-wayland-idle-inhibit - URI Quickshell.Wayland._IdleInhibitor - VERSION 0.1 - DEPENDENCIES QtQuick -) - -install_qml_module(quickshell-wayland-idle-inhibit) - -qs_add_module_deps_light(quickshell-wayland-idle-inhibit Quickshell) - -wl_proto(wlp-idle-inhibit idle-inhibit-unstable-v1 "${WAYLAND_PROTOCOLS}/unstable/idle-inhibit") - -target_link_libraries(quickshell-wayland-idle-inhibit PRIVATE - Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client - wlp-idle-inhibit -) - -qs_module_pch(quickshell-wayland-idle-inhibit SET large) - -target_link_libraries(quickshell PRIVATE quickshell-wayland-idle-inhibitplugin) diff --git a/src/wayland/idle_inhibit/inhibitor.cpp b/src/wayland/idle_inhibit/inhibitor.cpp deleted file mode 100644 index efeeae1..0000000 --- a/src/wayland/idle_inhibit/inhibitor.cpp +++ /dev/null @@ -1,140 +0,0 @@ -#include "inhibitor.hpp" - -#include -#include -#include -#include - -#include "../../window/proxywindow.hpp" -#include "../../window/windowinterface.hpp" -#include "proto.hpp" - -namespace qs::wayland::idle_inhibit { -using QtWaylandClient::QWaylandWindow; - -IdleInhibitor::IdleInhibitor() { - this->bBoundWindow.setBinding([this] { - return this->bEnabled ? this->bWindowObject.value() : nullptr; - }); -} - -IdleInhibitor::~IdleInhibitor() { delete this->inhibitor; } - -QObject* IdleInhibitor::window() const { return this->bWindowObject; } - -void IdleInhibitor::setWindow(QObject* window) { - if (window == this->bWindowObject) return; - - auto* proxyWindow = qobject_cast(window); - - if (proxyWindow == nullptr) { - if (auto* iface = qobject_cast(window)) { - proxyWindow = iface->proxyWindow(); - } - } - - this->bWindowObject = proxyWindow ? window : nullptr; -} - -void IdleInhibitor::boundWindowChanged() { - auto* window = this->bBoundWindow.value(); - auto* proxyWindow = qobject_cast(window); - - if (proxyWindow == nullptr) { - if (auto* iface = qobject_cast(window)) { - proxyWindow = iface->proxyWindow(); - } - } - - if (proxyWindow == this->proxyWindow) return; - - if (this->mWaylandWindow) { - QObject::disconnect(this->mWaylandWindow, nullptr, this, nullptr); - this->mWaylandWindow = nullptr; - this->onWaylandSurfaceDestroyed(); - } - - if (this->proxyWindow) { - QObject::disconnect(this->proxyWindow, nullptr, this, nullptr); - this->proxyWindow = nullptr; - } - - if (proxyWindow) { - this->proxyWindow = proxyWindow; - - QObject::connect(proxyWindow, &QObject::destroyed, this, &IdleInhibitor::onWindowDestroyed); - - QObject::connect( - proxyWindow, - &ProxyWindowBase::backerVisibilityChanged, - this, - &IdleInhibitor::onWindowVisibilityChanged - ); - - this->onWindowVisibilityChanged(); - } - - emit this->windowChanged(); -} - -void IdleInhibitor::onWindowDestroyed() { - this->proxyWindow = nullptr; - this->onWaylandSurfaceDestroyed(); - this->bWindowObject = nullptr; -} - -void IdleInhibitor::onWindowVisibilityChanged() { - if (!this->proxyWindow->isVisibleDirect()) return; - - auto* window = this->proxyWindow->backingWindow(); - if (!window->handle()) window->create(); - - auto* waylandWindow = dynamic_cast(window->handle()); - if (waylandWindow == this->mWaylandWindow) return; - this->mWaylandWindow = waylandWindow; - - QObject::connect( - waylandWindow, - &QObject::destroyed, - this, - &IdleInhibitor::onWaylandWindowDestroyed - ); - - QObject::connect( - waylandWindow, - &QWaylandWindow::surfaceCreated, - this, - &IdleInhibitor::onWaylandSurfaceCreated - ); - - QObject::connect( - waylandWindow, - &QWaylandWindow::surfaceDestroyed, - this, - &IdleInhibitor::onWaylandSurfaceDestroyed - ); - - if (waylandWindow->surface()) this->onWaylandSurfaceCreated(); -} - -void IdleInhibitor::onWaylandWindowDestroyed() { this->mWaylandWindow = nullptr; } - -void IdleInhibitor::onWaylandSurfaceCreated() { - auto* manager = impl::IdleInhibitManager::instance(); - - if (!manager) { - qWarning() << "Cannot enable idle inhibitor as idle-inhibit-unstable-v1 is not supported by " - "the current compositor."; - return; - } - - delete this->inhibitor; - this->inhibitor = manager->createIdleInhibitor(this->mWaylandWindow); -} - -void IdleInhibitor::onWaylandSurfaceDestroyed() { - delete this->inhibitor; - this->inhibitor = nullptr; -} - -} // namespace qs::wayland::idle_inhibit diff --git a/src/wayland/idle_inhibit/inhibitor.hpp b/src/wayland/idle_inhibit/inhibitor.hpp deleted file mode 100644 index c1a3e95..0000000 --- a/src/wayland/idle_inhibit/inhibitor.hpp +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "../../window/proxywindow.hpp" -#include "proto.hpp" - -namespace qs::wayland::idle_inhibit { - -///! Prevents a wayland session from idling -/// If an idle daemon is running, it may perform actions such as locking the screen -/// or putting the computer to sleep. -/// -/// An idle inhibitor prevents a wayland session from being marked as idle, if compositor -/// defined heuristics determine the window the inhibitor is attached to is important. -/// -/// A compositor will usually consider a @@Quickshell.PanelWindow or -/// a focused @@Quickshell.FloatingWindow to be important. -/// -/// > [!NOTE] Using an idle inhibitor requires the compositor support the [idle-inhibit-unstable-v1] protocol. -/// -/// [idle-inhibit-unstable-v1]: https://wayland.app/protocols/idle-inhibit-unstable-v1 -class IdleInhibitor: public QObject { - Q_OBJECT; - QML_ELEMENT; - // clang-format off - /// If the idle inhibitor should be enabled. Defaults to false. - Q_PROPERTY(bool enabled READ default WRITE default NOTIFY enabledChanged BINDABLE bindableEnabled); - /// The window to associate the idle inhibitor with. This may be used by the compositor - /// to determine if the inhibitor should be respected. - /// - /// Must be set to a non null value to enable the inhibitor. - Q_PROPERTY(QObject* window READ window WRITE setWindow NOTIFY windowChanged); - // clang-format on - -public: - IdleInhibitor(); - ~IdleInhibitor() override; - Q_DISABLE_COPY_MOVE(IdleInhibitor); - - [[nodiscard]] QObject* window() const; - void setWindow(QObject* window); - - [[nodiscard]] QBindable bindableEnabled() { return &this->bEnabled; } - -signals: - void enabledChanged(); - void windowChanged(); - -private slots: - void onWindowDestroyed(); - void onWindowVisibilityChanged(); - void onWaylandWindowDestroyed(); - void onWaylandSurfaceCreated(); - void onWaylandSurfaceDestroyed(); - -private: - void boundWindowChanged(); - ProxyWindowBase* proxyWindow = nullptr; - QtWaylandClient::QWaylandWindow* mWaylandWindow = nullptr; - - impl::IdleInhibitor* inhibitor = nullptr; - - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(IdleInhibitor, bool, bEnabled, &IdleInhibitor::enabledChanged); - Q_OBJECT_BINDABLE_PROPERTY(IdleInhibitor, QObject*, bWindowObject, &IdleInhibitor::windowChanged); - Q_OBJECT_BINDABLE_PROPERTY(IdleInhibitor, QObject*, bBoundWindow, &IdleInhibitor::boundWindowChanged); - // clang-format on -}; - -} // namespace qs::wayland::idle_inhibit diff --git a/src/wayland/idle_inhibit/proto.cpp b/src/wayland/idle_inhibit/proto.cpp deleted file mode 100644 index 25701a7..0000000 --- a/src/wayland/idle_inhibit/proto.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "proto.hpp" - -#include -#include -#include -#include - -#include "../../core/logcat.hpp" - -namespace qs::wayland::idle_inhibit::impl { - -namespace { -QS_LOGGING_CATEGORY(logIdleInhibit, "quickshell.wayland.idle_inhibit", QtWarningMsg); -} - -IdleInhibitManager::IdleInhibitManager(): QWaylandClientExtensionTemplate(1) { this->initialize(); } - -IdleInhibitManager* IdleInhibitManager::instance() { - static auto* instance = new IdleInhibitManager(); // NOLINT - return instance->isInitialized() ? instance : nullptr; -} - -IdleInhibitor* IdleInhibitManager::createIdleInhibitor(QtWaylandClient::QWaylandWindow* surface) { - auto* inhibitor = new IdleInhibitor(this->create_inhibitor(surface->surface())); - qCDebug(logIdleInhibit) << "Created inhibitor" << inhibitor; - return inhibitor; -} - -IdleInhibitor::~IdleInhibitor() { - qCDebug(logIdleInhibit) << "Destroyed inhibitor" << this; - if (this->isInitialized()) this->destroy(); -} - -} // namespace qs::wayland::idle_inhibit::impl diff --git a/src/wayland/idle_inhibit/proto.hpp b/src/wayland/idle_inhibit/proto.hpp deleted file mode 100644 index c797c33..0000000 --- a/src/wayland/idle_inhibit/proto.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "wayland-idle-inhibit-unstable-v1-client-protocol.h" - -namespace qs::wayland::idle_inhibit::impl { - -class IdleInhibitor; - -class IdleInhibitManager - : public QWaylandClientExtensionTemplate - , public QtWayland::zwp_idle_inhibit_manager_v1 { -public: - explicit IdleInhibitManager(); - - IdleInhibitor* createIdleInhibitor(QtWaylandClient::QWaylandWindow* surface); - - static IdleInhibitManager* instance(); -}; - -class IdleInhibitor: public QtWayland::zwp_idle_inhibitor_v1 { -public: - explicit IdleInhibitor(::zwp_idle_inhibitor_v1* inhibitor) - : QtWayland::zwp_idle_inhibitor_v1(inhibitor) {} - - ~IdleInhibitor() override; - Q_DISABLE_COPY_MOVE(IdleInhibitor); -}; - -} // namespace qs::wayland::idle_inhibit::impl diff --git a/src/wayland/idle_inhibit/test/manual/idle_inhibit.qml b/src/wayland/idle_inhibit/test/manual/idle_inhibit.qml deleted file mode 100644 index f80e647..0000000 --- a/src/wayland/idle_inhibit/test/manual/idle_inhibit.qml +++ /dev/null @@ -1,20 +0,0 @@ -import QtQuick -import QtQuick.Controls -import Quickshell -import Quickshell.Wayland - -FloatingWindow { - id: root - color: contentItem.palette.window - - CheckBox { - id: enableCb - anchors.centerIn: parent - text: "Enable Inhibitor" - } - - IdleInhibitor { - window: root - enabled: enableCb.checked - } -} diff --git a/src/wayland/idle_notify/CMakeLists.txt b/src/wayland/idle_notify/CMakeLists.txt deleted file mode 100644 index 889c7ce..0000000 --- a/src/wayland/idle_notify/CMakeLists.txt +++ /dev/null @@ -1,25 +0,0 @@ -qt_add_library(quickshell-wayland-idle-notify STATIC - proto.cpp - monitor.cpp -) - -qt_add_qml_module(quickshell-wayland-idle-notify - URI Quickshell.Wayland._IdleNotify - VERSION 0.1 - DEPENDENCIES QtQuick -) - -install_qml_module(quickshell-wayland-idle-notify) - -qs_add_module_deps_light(quickshell-wayland-idle-notify Quickshell) - -wl_proto(wlp-idle-notify ext-idle-notify-v1 "${WAYLAND_PROTOCOLS}/staging/ext-idle-notify") - -target_link_libraries(quickshell-wayland-idle-notify PRIVATE - Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client - wlp-idle-notify -) - -qs_module_pch(quickshell-wayland-idle-notify SET large) - -target_link_libraries(quickshell PRIVATE quickshell-wayland-idle-notifyplugin) diff --git a/src/wayland/idle_notify/monitor.cpp b/src/wayland/idle_notify/monitor.cpp deleted file mode 100644 index 3f496e4..0000000 --- a/src/wayland/idle_notify/monitor.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "monitor.hpp" -#include - -#include -#include -#include - -#include "proto.hpp" - -namespace qs::wayland::idle_notify { - -IdleMonitor::~IdleMonitor() { delete this->bNotification.value(); } - -void IdleMonitor::onPostReload() { - this->bParams.setBinding([this] { - return Params { - .enabled = this->bEnabled.value(), - .timeout = this->bTimeout.value(), - .respectInhibitors = this->bRespectInhibitors.value() - }; - }); - - this->bIsIdle.setBinding([this] { - auto* notification = this->bNotification.value(); - return notification ? notification->bIsIdle.value() : false; - }); -} - -void IdleMonitor::updateNotification() { - auto* notification = this->bNotification.value(); - delete notification; - notification = nullptr; - - auto guard = qScopeGuard([&, this] { this->bNotification = notification; }); - - auto params = this->bParams.value(); - - if (params.enabled) { - auto* manager = impl::IdleNotificationManager::instance(); - - if (!manager) { - qWarning() << "Cannot create idle monitor as ext-idle-notify-v1 is not supported by the " - "current compositor."; - return; - } - - auto timeout = static_cast(std::max(0, static_cast(params.timeout * 1000))); - notification = manager->createIdleNotification(timeout, params.respectInhibitors); - } -} - -} // namespace qs::wayland::idle_notify diff --git a/src/wayland/idle_notify/monitor.hpp b/src/wayland/idle_notify/monitor.hpp deleted file mode 100644 index 25bd5a6..0000000 --- a/src/wayland/idle_notify/monitor.hpp +++ /dev/null @@ -1,78 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "../../core/reload.hpp" -#include "proto.hpp" - -namespace qs::wayland::idle_notify { - -///! Provides a notification when a wayland session is makred idle -/// An idle monitor detects when the user stops providing input for a period of time. -/// -/// > [!NOTE] Using an idle monitor requires the compositor support the [ext-idle-notify-v1] protocol. -/// -/// [ext-idle-notify-v1]: https://wayland.app/protocols/ext-idle-notify-v1 -class IdleMonitor: public PostReloadHook { - Q_OBJECT; - QML_ELEMENT; - // clang-format off - /// If the idle monitor should be enabled. Defaults to true. - Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged); - /// The amount of time in seconds the idle monitor should wait before reporting an idle state. - /// - /// Defaults to zero, which reports idle status immediately. - Q_PROPERTY(qreal timeout READ default WRITE default NOTIFY timeoutChanged BINDABLE bindableTimeout); - /// When set to true, @@isIdle will depend on both user interaction and active idle inhibitors. - /// When false, the value will depend solely on user interaction. Defaults to true. - Q_PROPERTY(bool respectInhibitors READ default WRITE default NOTIFY respectInhibitorsChanged BINDABLE bindableRespectInhibitors); - /// This property is true if the user has been idle for at least @@timeout. - /// What is considered to be idle is influenced by @@respectInhibitors. - Q_PROPERTY(bool isIdle READ default NOTIFY isIdleChanged BINDABLE bindableIsIdle); - // clang-format on - -public: - IdleMonitor() = default; - ~IdleMonitor() override; - Q_DISABLE_COPY_MOVE(IdleMonitor); - - void onPostReload() override; - - [[nodiscard]] bool isEnabled() const { return this->bNotification.value(); } - void setEnabled(bool enabled) { this->bEnabled = enabled; } - - [[nodiscard]] QBindable bindableTimeout() { return &this->bTimeout; } - [[nodiscard]] QBindable bindableRespectInhibitors() { return &this->bRespectInhibitors; } - [[nodiscard]] QBindable bindableIsIdle() const { return &this->bIsIdle; } - -signals: - void enabledChanged(); - void timeoutChanged(); - void respectInhibitorsChanged(); - void isIdleChanged(); - -private: - void updateNotification(); - - struct Params { - bool enabled; - qreal timeout; - bool respectInhibitors; - }; - - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(IdleMonitor, bool, bEnabled, true, &IdleMonitor::enabledChanged); - Q_OBJECT_BINDABLE_PROPERTY(IdleMonitor, qreal, bTimeout, &IdleMonitor::timeoutChanged); - Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(IdleMonitor, bool, bRespectInhibitors, true, &IdleMonitor::respectInhibitorsChanged); - Q_OBJECT_BINDABLE_PROPERTY(IdleMonitor, Params, bParams, &IdleMonitor::updateNotification); - Q_OBJECT_BINDABLE_PROPERTY(IdleMonitor, impl::IdleNotification*, bNotification); - Q_OBJECT_BINDABLE_PROPERTY(IdleMonitor, bool, bIsIdle, &IdleMonitor::isIdleChanged); - // clang-format on -}; - -} // namespace qs::wayland::idle_notify diff --git a/src/wayland/idle_notify/proto.cpp b/src/wayland/idle_notify/proto.cpp deleted file mode 100644 index 9b3fa81..0000000 --- a/src/wayland/idle_notify/proto.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include "proto.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../core/logcat.hpp" - -namespace qs::wayland::idle_notify { -QS_LOGGING_CATEGORY(logIdleNotify, "quickshell.wayland.idle_notify", QtWarningMsg); -} - -namespace qs::wayland::idle_notify::impl { - -IdleNotificationManager::IdleNotificationManager(): QWaylandClientExtensionTemplate(2) { - this->initialize(); -} - -IdleNotificationManager* IdleNotificationManager::instance() { - static auto* instance = new IdleNotificationManager(); // NOLINT - return instance->isInitialized() ? instance : nullptr; -} - -IdleNotification* -IdleNotificationManager::createIdleNotification(quint32 timeout, bool respectInhibitors) { - if (!respectInhibitors - && this->QtWayland::ext_idle_notifier_v1::version() - < EXT_IDLE_NOTIFIER_V1_GET_INPUT_IDLE_NOTIFICATION_SINCE_VERSION) - { - qCWarning(logIdleNotify) << "Cannot ignore inhibitors for new idle notifier: Compositor does " - "not support protocol version 2."; - - respectInhibitors = true; - } - - auto* display = QtWaylandClient::QWaylandIntegration::instance()->display(); - auto* inputDevice = display->lastInputDevice(); - if (inputDevice == nullptr) inputDevice = display->defaultInputDevice(); - if (inputDevice == nullptr) { - qCCritical(logIdleNotify) << "Could not create idle notifier: No seat."; - return nullptr; - } - - ::ext_idle_notification_v1* notification = nullptr; - if (respectInhibitors) notification = this->get_idle_notification(timeout, inputDevice->object()); - else notification = this->get_input_idle_notification(timeout, inputDevice->object()); - - auto* wrapper = new IdleNotification(notification); - qCDebug(logIdleNotify) << "Created" << wrapper << "with timeout:" << timeout - << "respects inhibitors:" << respectInhibitors; - return wrapper; -} - -IdleNotification::~IdleNotification() { - qCDebug(logIdleNotify) << "Destroyed" << this; - if (this->isInitialized()) this->destroy(); -} - -void IdleNotification::ext_idle_notification_v1_idled() { - qCDebug(logIdleNotify) << this << "has been marked idle"; - this->bIsIdle = true; -} - -void IdleNotification::ext_idle_notification_v1_resumed() { - qCDebug(logIdleNotify) << this << "has been marked resumed"; - this->bIsIdle = false; -} - -} // namespace qs::wayland::idle_notify::impl diff --git a/src/wayland/idle_notify/proto.hpp b/src/wayland/idle_notify/proto.hpp deleted file mode 100644 index 11dbf29..0000000 --- a/src/wayland/idle_notify/proto.hpp +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../core/logcat.hpp" - -namespace qs::wayland::idle_notify { -QS_DECLARE_LOGGING_CATEGORY(logIdleNotify); -} - -namespace qs::wayland::idle_notify::impl { - -class IdleNotification; - -class IdleNotificationManager - : public QWaylandClientExtensionTemplate - , public QtWayland::ext_idle_notifier_v1 { -public: - explicit IdleNotificationManager(); - IdleNotification* createIdleNotification(quint32 timeout, bool respectInhibitors); - - static IdleNotificationManager* instance(); -}; - -class IdleNotification - : public QObject - , public QtWayland::ext_idle_notification_v1 { - Q_OBJECT; - -public: - explicit IdleNotification(::ext_idle_notification_v1* notification) - : QtWayland::ext_idle_notification_v1(notification) {} - - ~IdleNotification() override; - Q_DISABLE_COPY_MOVE(IdleNotification); - - Q_OBJECT_BINDABLE_PROPERTY(IdleNotification, bool, bIsIdle); - -protected: - void ext_idle_notification_v1_idled() override; - void ext_idle_notification_v1_resumed() override; -}; - -} // namespace qs::wayland::idle_notify::impl diff --git a/src/wayland/idle_notify/test/manual/idle_notify.qml b/src/wayland/idle_notify/test/manual/idle_notify.qml deleted file mode 100644 index 3bf6cbd..0000000 --- a/src/wayland/idle_notify/test/manual/idle_notify.qml +++ /dev/null @@ -1,44 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts -import Quickshell -import Quickshell.Wayland - -FloatingWindow { - color: contentItem.palette.window - - IdleMonitor { - id: monitor - enabled: enabledCb.checked - timeout: timeoutSb.value - respectInhibitors: respectInhibitorsCb.checked - } - - ColumnLayout { - Label { text: `Is idle? ${monitor.isIdle}` } - - CheckBox { - id: enabledCb - text: "Enabled" - checked: true - } - - CheckBox { - id: respectInhibitorsCb - text: "Respect Inhibitors" - checked: true - } - - RowLayout { - Label { text: "Timeout" } - - SpinBox { - id: timeoutSb - editable: true - from: 0 - to: 1000 - value: 5 - } - } - } -} diff --git a/src/wayland/module.md b/src/wayland/module.md index 9ad15ba..b9f8f59 100644 --- a/src/wayland/module.md +++ b/src/wayland/module.md @@ -5,8 +5,5 @@ headers = [ "session_lock.hpp", "toplevel_management/qml.hpp", "screencopy/view.hpp", - "idle_inhibit/inhibitor.hpp", - "idle_notify/monitor.hpp", - "shortcuts_inhibit/inhibitor.hpp", ] ----- diff --git a/src/wayland/popupanchor.cpp b/src/wayland/popupanchor.cpp index 14e1923..cbbccae 100644 --- a/src/wayland/popupanchor.cpp +++ b/src/wayland/popupanchor.cpp @@ -16,6 +16,7 @@ using XdgPositioner = QtWayland::xdg_positioner; using qs::wayland::xdg_shell::XdgWmBase; void WaylandPopupPositioner::reposition(PopupAnchor* anchor, QWindow* window, bool onlyIfDirty) { + auto* waylandWindow = dynamic_cast(window->handle()); auto* popupRole = waylandWindow ? waylandWindow->surfaceRole<::xdg_popup>() : nullptr; diff --git a/src/wayland/screencopy/hyprland_screencopy/hyprland_screencopy.cpp b/src/wayland/screencopy/hyprland_screencopy/hyprland_screencopy.cpp index 6fc2955..b8aef96 100644 --- a/src/wayland/screencopy/hyprland_screencopy/hyprland_screencopy.cpp +++ b/src/wayland/screencopy/hyprland_screencopy/hyprland_screencopy.cpp @@ -64,8 +64,6 @@ void HyprlandScreencopyContext::onToplevelDestroyed() { void HyprlandScreencopyContext::captureFrame() { if (this->object()) return; - this->request.reset(); - this->init(this->manager->capture_toplevel_with_wlr_toplevel_handle( this->paintCursors ? 1 : 0, this->handle->object() @@ -103,16 +101,6 @@ void HyprlandScreencopyContext::hyprland_toplevel_export_frame_v1_flags(uint32_t void HyprlandScreencopyContext::hyprland_toplevel_export_frame_v1_buffer_done() { auto* backbuffer = this->mSwapchain.createBackbuffer(this->request); - - if (!backbuffer || !backbuffer->buffer()) { - qCWarning(logScreencopy) << "Backbuffer creation failed for screencopy. Skipping frame."; - - // Try again. This will be spammy if the compositor continuously sends bad frames. - this->destroy(); - this->captureFrame(); - return; - } - this->copy(backbuffer->buffer(), this->copiedFirstFrame ? 0 : 1); } diff --git a/src/wayland/screencopy/image_copy_capture/image_copy_capture.cpp b/src/wayland/screencopy/image_copy_capture/image_copy_capture.cpp index 13d1bc6..a307d1e 100644 --- a/src/wayland/screencopy/image_copy_capture/image_copy_capture.cpp +++ b/src/wayland/screencopy/image_copy_capture/image_copy_capture.cpp @@ -117,12 +117,6 @@ void IccScreencopyContext::doCapture() { auto newBuffer = false; auto* backbuffer = this->mSwapchain.createBackbuffer(this->request, &newBuffer); - if (!backbuffer || !backbuffer->buffer()) { - qCWarning(logIcc) << "Backbuffer creation failed for screencopy. Waiting for updated buffer " - "creation parameters before trying again."; - return; - } - this->IccCaptureFrame::init(this->IccCaptureSession::create_frame()); this->IccCaptureFrame::attach_buffer(backbuffer->buffer()); diff --git a/src/wayland/screencopy/wlr_screencopy/wlr_screencopy.cpp b/src/wayland/screencopy/wlr_screencopy/wlr_screencopy.cpp index 927da8d..f4d8c48 100644 --- a/src/wayland/screencopy/wlr_screencopy/wlr_screencopy.cpp +++ b/src/wayland/screencopy/wlr_screencopy/wlr_screencopy.cpp @@ -65,14 +65,12 @@ void WlrScreencopyContext::onScreenDestroyed() { void WlrScreencopyContext::captureFrame() { if (this->object()) return; - this->request.reset(); - if (this->region.isEmpty()) { - this->init(this->manager->capture_output(this->paintCursors ? 1 : 0, this->screen->output())); + this->init(manager->capture_output(this->paintCursors ? 1 : 0, screen->output())); } else { - this->init(this->manager->capture_output_region( + this->init(manager->capture_output_region( this->paintCursors ? 1 : 0, - this->screen->output(), + screen->output(), this->region.x(), this->region.y(), this->region.width(), @@ -111,15 +109,6 @@ void WlrScreencopyContext::zwlr_screencopy_frame_v1_flags(uint32_t flags) { void WlrScreencopyContext::zwlr_screencopy_frame_v1_buffer_done() { auto* backbuffer = this->mSwapchain.createBackbuffer(this->request); - if (!backbuffer || !backbuffer->buffer()) { - qCWarning(logScreencopy) << "Backbuffer creation failed for screencopy. Skipping frame."; - - // Try again. This will be spammy if the compositor continuously sends bad frames. - this->destroy(); - this->captureFrame(); - return; - } - if (this->copiedFirstFrame) { this->copy_with_damage(backbuffer->buffer()); } else { @@ -165,8 +154,7 @@ WlrScreencopyContext::OutputTransformQuery::~OutputTransformQuery() { if (this->isInitialized()) this->release(); } -void WlrScreencopyContext::OutputTransformQuery::setScreen( - QtWaylandClient::QWaylandScreen* screen +void WlrScreencopyContext::OutputTransformQuery::setScreen(QtWaylandClient::QWaylandScreen* screen ) { // cursed hack class QWaylandScreenReflector: public QtWaylandClient::QWaylandScreen { diff --git a/src/wayland/session_lock.cpp b/src/wayland/session_lock.cpp index 2ebe3fd..0ecf9ec 100644 --- a/src/wayland/session_lock.cpp +++ b/src/wayland/session_lock.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -80,8 +79,8 @@ void WlSessionLock::updateSurfaces(bool show, WlSessionLock* old) { auto* instance = qobject_cast(instanceObj); if (instance == nullptr) { - qWarning() - << "WlSessionLock.surface does not create a WlSessionLockSurface. Aborting lock."; + qWarning( + ) << "WlSessionLock.surface does not create a WlSessionLockSurface. Aborting lock."; if (instanceObj != nullptr) instanceObj->deleteLater(); this->unlock(); return; @@ -217,15 +216,6 @@ void WlSessionLockSurface::onReload(QObject* oldInstance) { if (this->window == nullptr) { this->window = new QQuickWindow(); - - // needed for vulkan dmabuf import, qt ignores these if not applicable - auto graphicsConfig = this->window->graphicsConfiguration(); - graphicsConfig.setDeviceExtensions({ - "VK_KHR_external_memory_fd", - "VK_EXT_external_memory_dma_buf", - "VK_EXT_image_drm_format_modifier", - }); - this->window->setGraphicsConfiguration(graphicsConfig); } this->mContentItem->setParentItem(this->window->contentItem()); diff --git a/src/wayland/session_lock/shell_integration.cpp b/src/wayland/session_lock/shell_integration.cpp index 207ef42..2b5fdbf 100644 --- a/src/wayland/session_lock/shell_integration.cpp +++ b/src/wayland/session_lock/shell_integration.cpp @@ -10,11 +10,10 @@ QtWaylandClient::QWaylandShellSurface* QSWaylandSessionLockIntegration::createShellSurface(QtWaylandClient::QWaylandWindow* window) { auto* lock = LockWindowExtension::get(window->window()); - if (lock == nullptr || lock->surface == nullptr) { + if (lock == nullptr || lock->surface == nullptr || !lock->surface->isExposed()) { qFatal() << "Visibility canary failed. A window with a LockWindowExtension MUST be set to " "visible via LockWindowExtension::setVisible"; } - QSWaylandSessionLockSurface* surface = lock->surface; // shut up the unused include linter - return surface; + return lock->surface; } diff --git a/src/wayland/session_lock/shell_integration.hpp b/src/wayland/session_lock/shell_integration.hpp index b2e2891..d6f9175 100644 --- a/src/wayland/session_lock/shell_integration.hpp +++ b/src/wayland/session_lock/shell_integration.hpp @@ -8,6 +8,6 @@ class QSWaylandSessionLockIntegration: public QtWaylandClient::QWaylandShellIntegration { public: bool initialize(QtWaylandClient::QWaylandDisplay* /* display */) override { return true; } - QtWaylandClient::QWaylandShellSurface* - createShellSurface(QtWaylandClient::QWaylandWindow* window) override; + QtWaylandClient::QWaylandShellSurface* createShellSurface(QtWaylandClient::QWaylandWindow* window + ) override; }; diff --git a/src/wayland/session_lock/surface.cpp b/src/wayland/session_lock/surface.cpp index c73f459..bc0e75d 100644 --- a/src/wayland/session_lock/surface.cpp +++ b/src/wayland/session_lock/surface.cpp @@ -28,7 +28,7 @@ QSWaylandSessionLockSurface::QSWaylandSessionLockSurface(QtWaylandClient::QWayla wl_output* output = nullptr; // NOLINT (include) auto* waylandScreen = dynamic_cast(qwindow->screen()->handle()); - if (waylandScreen != nullptr && !waylandScreen->isPlaceholder() && waylandScreen->output()) { + if (waylandScreen != nullptr) { output = waylandScreen->output(); } else { qFatal() << "Session lock screen does not corrospond to a real screen. Force closing window"; @@ -48,6 +48,16 @@ void QSWaylandSessionLockSurface::applyConfigure() { this->window()->resizeFromApplyConfigure(this->size); } +bool QSWaylandSessionLockSurface::handleExpose(const QRegion& region) { + if (this->initBuf != nullptr) { + // at this point qt's next commit to the surface will have a new buffer, and we can safely delete this one. + delete this->initBuf; + this->initBuf = nullptr; + } + + return this->QtWaylandClient::QWaylandShellSurface::handleExpose(region); +} + void QSWaylandSessionLockSurface::setExtension(LockWindowExtension* ext) { if (ext == nullptr) { if (this->window() != nullptr) this->window()->window()->close(); @@ -61,6 +71,11 @@ void QSWaylandSessionLockSurface::setExtension(LockWindowExtension* ext) { } } +void QSWaylandSessionLockSurface::setVisible() { + if (this->configured && !this->visible) this->initVisible(); + this->visible = true; +} + void QSWaylandSessionLockSurface::ext_session_lock_surface_v1_configure( quint32 serial, quint32 width, @@ -82,41 +97,13 @@ void QSWaylandSessionLockSurface::ext_session_lock_surface_v1_configure( #else this->window()->updateExposure(); #endif - -#if QT_VERSION < QT_VERSION_CHECK(6, 10, 0) if (this->visible) this->initVisible(); -#endif } else { // applyConfigureWhenPossible runs too late and causes a protocol error on reconfigure. this->window()->resizeFromApplyConfigure(this->size); } } -#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0) - -bool QSWaylandSessionLockSurface::commitSurfaceRole() const { return false; } - -void QSWaylandSessionLockSurface::setVisible() { this->window()->window()->setVisible(true); } - -#else - -bool QSWaylandSessionLockSurface::handleExpose(const QRegion& region) { - if (this->initBuf != nullptr) { - // at this point qt's next commit to the surface will have a new buffer, and we can safely delete this one. - delete this->initBuf; - this->initBuf = nullptr; - } - - return this->QtWaylandClient::QWaylandShellSurface::handleExpose(region); -} - -void QSWaylandSessionLockSurface::setVisible() { - if (this->configured && !this->visible) this->initVisible(); - this->visible = true; -} - -#endif - #if QT_VERSION < QT_VERSION_CHECK(6, 9, 0) #include @@ -136,7 +123,7 @@ void QSWaylandSessionLockSurface::initVisible() { this->window()->window()->setVisible(true); } -#elif QT_VERSION < QT_VERSION_CHECK(6, 10, 0) +#else #include diff --git a/src/wayland/session_lock/surface.hpp b/src/wayland/session_lock/surface.hpp index 39be88d..f7abc77 100644 --- a/src/wayland/session_lock/surface.hpp +++ b/src/wayland/session_lock/surface.hpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include @@ -21,12 +20,7 @@ public: [[nodiscard]] bool isExposed() const override; void applyConfigure() override; - -#if QT_VERSION >= QT_VERSION_CHECK(6, 10, 0) - [[nodiscard]] bool commitSurfaceRole() const override; -#else bool handleExpose(const QRegion& region) override; -#endif void setExtension(LockWindowExtension* ext); void setVisible(); @@ -35,13 +29,11 @@ private: void ext_session_lock_surface_v1_configure(quint32 serial, quint32 width, quint32 height) override; -#if QT_VERSION < QT_VERSION_CHECK(6, 10, 0) void initVisible(); - bool visible = false; - QtWaylandClient::QWaylandShmBuffer* initBuf = nullptr; -#endif LockWindowExtension* ext = nullptr; QSize size; bool configured = false; + bool visible = false; + QtWaylandClient::QWaylandShmBuffer* initBuf = nullptr; }; diff --git a/src/wayland/shortcuts_inhibit/CMakeLists.txt b/src/wayland/shortcuts_inhibit/CMakeLists.txt deleted file mode 100644 index 8dedd3d..0000000 --- a/src/wayland/shortcuts_inhibit/CMakeLists.txt +++ /dev/null @@ -1,25 +0,0 @@ -qt_add_library(quickshell-wayland-shortcuts-inhibit STATIC - proto.cpp - inhibitor.cpp -) - -qt_add_qml_module(quickshell-wayland-shortcuts-inhibit - URI Quickshell.Wayland._ShortcutsInhibitor - VERSION 0.1 - DEPENDENCIES QtQuick -) - -install_qml_module(quickshell-wayland-shortcuts-inhibit) - -qs_add_module_deps_light(quickshell-wayland-shortcuts-inhibit Quickshell) - -wl_proto(wlp-shortcuts-inhibit keyboard-shortcuts-inhibit-unstable-v1 "${WAYLAND_PROTOCOLS}/unstable/keyboard-shortcuts-inhibit") - -target_link_libraries(quickshell-wayland-shortcuts-inhibit PRIVATE - Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client - wlp-shortcuts-inhibit -) - -qs_module_pch(quickshell-wayland-shortcuts-inhibit SET large) - -target_link_libraries(quickshell PRIVATE quickshell-wayland-shortcuts-inhibitplugin) \ No newline at end of file diff --git a/src/wayland/shortcuts_inhibit/inhibitor.cpp b/src/wayland/shortcuts_inhibit/inhibitor.cpp deleted file mode 100644 index 2fca9bc..0000000 --- a/src/wayland/shortcuts_inhibit/inhibitor.cpp +++ /dev/null @@ -1,187 +0,0 @@ -#include "inhibitor.hpp" - -#include -#include -#include -#include -#include -#include -#include - -#include "../../window/proxywindow.hpp" -#include "../../window/windowinterface.hpp" -#include "proto.hpp" - -namespace qs::wayland::shortcuts_inhibit { -using QtWaylandClient::QWaylandWindow; - -ShortcutInhibitor::ShortcutInhibitor() { - this->bBoundWindow.setBinding([this] { - return this->bEnabled ? this->bWindowObject.value() : nullptr; - }); - - this->bActive.setBinding([this]() { - auto* inhibitor = this->bInhibitor.value(); - if (!inhibitor) return false; - if (!inhibitor->bindableActive().value()) return false; - return this->bWindow.value() == this->bFocusedWindow; - }); - - QObject::connect( - dynamic_cast(QGuiApplication::instance()), - &QGuiApplication::focusWindowChanged, - this, - &ShortcutInhibitor::onFocusedWindowChanged - ); - - this->onFocusedWindowChanged(QGuiApplication::focusWindow()); -} - -ShortcutInhibitor::~ShortcutInhibitor() { - if (!this->bInhibitor) return; - - auto* manager = impl::ShortcutsInhibitManager::instance(); - if (!manager) return; - - manager->unrefShortcutsInhibitor(this->bInhibitor); -} - -void ShortcutInhibitor::onBoundWindowChanged() { - auto* window = this->bBoundWindow.value(); - auto* proxyWindow = qobject_cast(window); - - if (!proxyWindow) { - if (auto* iface = qobject_cast(window)) { - proxyWindow = iface->proxyWindow(); - } - } - - if (proxyWindow == this->proxyWindow) return; - - if (this->proxyWindow) { - QObject::disconnect(this->proxyWindow, nullptr, this, nullptr); - this->proxyWindow = nullptr; - } - - if (this->mWaylandWindow) { - QObject::disconnect(this->mWaylandWindow, nullptr, this, nullptr); - this->mWaylandWindow = nullptr; - this->onWaylandSurfaceDestroyed(); - } - - if (proxyWindow) { - this->proxyWindow = proxyWindow; - - QObject::connect(proxyWindow, &QObject::destroyed, this, &ShortcutInhibitor::onWindowDestroyed); - - QObject::connect( - proxyWindow, - &ProxyWindowBase::backerVisibilityChanged, - this, - &ShortcutInhibitor::onWindowVisibilityChanged - ); - - this->onWindowVisibilityChanged(); - } -} - -void ShortcutInhibitor::onWindowDestroyed() { - this->proxyWindow = nullptr; - this->onWaylandSurfaceDestroyed(); -} - -void ShortcutInhibitor::onWindowVisibilityChanged() { - if (!this->proxyWindow->isVisibleDirect()) return; - - auto* window = this->proxyWindow->backingWindow(); - if (!window->handle()) window->create(); - - auto* waylandWindow = dynamic_cast(window->handle()); - if (!waylandWindow) { - qCCritical(impl::logShortcutsInhibit()) << "Window handle is not a QWaylandWindow"; - return; - } - if (waylandWindow == this->mWaylandWindow) return; - this->mWaylandWindow = waylandWindow; - this->bWindow = window; - - QObject::connect( - waylandWindow, - &QObject::destroyed, - this, - &ShortcutInhibitor::onWaylandWindowDestroyed - ); - - QObject::connect( - waylandWindow, - &QWaylandWindow::surfaceCreated, - this, - &ShortcutInhibitor::onWaylandSurfaceCreated - ); - - QObject::connect( - waylandWindow, - &QWaylandWindow::surfaceDestroyed, - this, - &ShortcutInhibitor::onWaylandSurfaceDestroyed - ); - - if (waylandWindow->surface()) this->onWaylandSurfaceCreated(); -} - -void ShortcutInhibitor::onWaylandWindowDestroyed() { this->mWaylandWindow = nullptr; } - -void ShortcutInhibitor::onWaylandSurfaceCreated() { - auto* manager = impl::ShortcutsInhibitManager::instance(); - - if (!manager) { - qWarning() << "Cannot enable shortcuts inhibitor as keyboard-shortcuts-inhibit-unstable-v1 is " - "not supported by " - "the current compositor."; - return; - } - - if (this->bInhibitor) { - qFatal("ShortcutsInhibitor: inhibitor already exists when creating surface"); - } - - this->bInhibitor = manager->createShortcutsInhibitor(this->mWaylandWindow); -} - -void ShortcutInhibitor::onWaylandSurfaceDestroyed() { - if (!this->bInhibitor) return; - - auto* manager = impl::ShortcutsInhibitManager::instance(); - if (!manager) return; - - manager->unrefShortcutsInhibitor(this->bInhibitor); - this->bInhibitor = nullptr; -} - -void ShortcutInhibitor::onInhibitorChanged() { - auto* inhibitor = this->bInhibitor.value(); - if (inhibitor) { - QObject::connect( - inhibitor, - &impl::ShortcutsInhibitor::activeChanged, - this, - &ShortcutInhibitor::onInhibitorActiveChanged - ); - } -} - -void ShortcutInhibitor::onInhibitorActiveChanged() { - auto* inhibitor = this->bInhibitor.value(); - if (inhibitor && !inhibitor->isActive()) { - // Compositor has deactivated the inhibitor, making it invalid. - // Set enabled to false so the user can enable it again to create a new inhibitor. - this->bEnabled = false; - emit this->cancelled(); - } -} - -void ShortcutInhibitor::onFocusedWindowChanged(QWindow* focusedWindow) { - this->bFocusedWindow = focusedWindow; -} - -} // namespace qs::wayland::shortcuts_inhibit diff --git a/src/wayland/shortcuts_inhibit/inhibitor.hpp b/src/wayland/shortcuts_inhibit/inhibitor.hpp deleted file mode 100644 index 2eed54f..0000000 --- a/src/wayland/shortcuts_inhibit/inhibitor.hpp +++ /dev/null @@ -1,89 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "../../window/proxywindow.hpp" -#include "proto.hpp" - -namespace qs::wayland::shortcuts_inhibit { - -///! Prevents compositor keyboard shortcuts from being triggered -/// A shortcuts inhibitor prevents the compositor from processing its own keyboard shortcuts -/// for the focused surface. This allows applications to receive key events for shortcuts -/// that would normally be handled by the compositor. -/// -/// The inhibitor only takes effect when the associated window is focused and the inhibitor -/// is enabled. The compositor may choose to ignore inhibitor requests based on its policy. -/// -/// > [!NOTE] Using a shortcuts inhibitor requires the compositor support the [keyboard-shortcuts-inhibit-unstable-v1] protocol. -/// -/// [keyboard-shortcuts-inhibit-unstable-v1]: https://wayland.app/protocols/keyboard-shortcuts-inhibit-unstable-v1 -class ShortcutInhibitor: public QObject { - Q_OBJECT; - QML_ELEMENT; - // clang-format off - /// If the shortcuts inhibitor should be enabled. Defaults to false. - Q_PROPERTY(bool enabled READ default WRITE default NOTIFY enabledChanged BINDABLE bindableEnabled); - /// The window to associate the shortcuts inhibitor with. - /// The inhibitor will only inhibit shortcuts pressed while this window has keyboard focus. - /// - /// Must be set to a non null value to enable the inhibitor. - Q_PROPERTY(QObject* window READ default WRITE default NOTIFY windowChanged BINDABLE bindableWindow); - /// Whether the inhibitor is currently active. The inhibitor is only active if @@enabled is true, - /// @@window has keyboard focus, and the compositor grants the inhibit request. - /// - /// The compositor may deactivate the inhibitor at any time (for example, if the user requests - /// normal shortcuts to be restored). When deactivated by the compositor, the inhibitor cannot be - /// programmatically reactivated. - Q_PROPERTY(bool active READ default NOTIFY activeChanged BINDABLE bindableActive); - // clang-format on - -public: - ShortcutInhibitor(); - ~ShortcutInhibitor() override; - Q_DISABLE_COPY_MOVE(ShortcutInhibitor); - - [[nodiscard]] QBindable bindableEnabled() { return &this->bEnabled; } - [[nodiscard]] QBindable bindableWindow() { return &this->bWindowObject; } - [[nodiscard]] QBindable bindableActive() const { return &this->bActive; } - -signals: - void enabledChanged(); - void windowChanged(); - void activeChanged(); - /// Sent if the compositor cancels the inhibitor while it is active. - void cancelled(); - -private slots: - void onWindowDestroyed(); - void onWindowVisibilityChanged(); - void onWaylandWindowDestroyed(); - void onWaylandSurfaceCreated(); - void onWaylandSurfaceDestroyed(); - void onInhibitorActiveChanged(); - -private: - void onBoundWindowChanged(); - void onInhibitorChanged(); - void onFocusedWindowChanged(QWindow* focusedWindow); - - ProxyWindowBase* proxyWindow = nullptr; - QtWaylandClient::QWaylandWindow* mWaylandWindow = nullptr; - - // clang-format off - Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, bool, bEnabled, &ShortcutInhibitor::enabledChanged); - Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, QObject*, bWindowObject, &ShortcutInhibitor::windowChanged); - Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, QObject*, bBoundWindow, &ShortcutInhibitor::onBoundWindowChanged); - Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, impl::ShortcutsInhibitor*, bInhibitor, &ShortcutInhibitor::onInhibitorChanged); - Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, QWindow*, bWindow); - Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, QWindow*, bFocusedWindow); - Q_OBJECT_BINDABLE_PROPERTY(ShortcutInhibitor, bool, bActive, &ShortcutInhibitor::activeChanged); - // clang-format on -}; - -} // namespace qs::wayland::shortcuts_inhibit diff --git a/src/wayland/shortcuts_inhibit/proto.cpp b/src/wayland/shortcuts_inhibit/proto.cpp deleted file mode 100644 index 8d35d5e..0000000 --- a/src/wayland/shortcuts_inhibit/proto.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include "proto.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../core/logcat.hpp" - -namespace qs::wayland::shortcuts_inhibit::impl { - -QS_LOGGING_CATEGORY(logShortcutsInhibit, "quickshell.wayland.shortcuts_inhibit", QtWarningMsg); - -ShortcutsInhibitManager::ShortcutsInhibitManager(): QWaylandClientExtensionTemplate(1) { - this->initialize(); -} - -ShortcutsInhibitManager* ShortcutsInhibitManager::instance() { - static auto* instance = new ShortcutsInhibitManager(); // NOLINT - return instance->isInitialized() ? instance : nullptr; -} - -ShortcutsInhibitor* -ShortcutsInhibitManager::createShortcutsInhibitor(QtWaylandClient::QWaylandWindow* surface) { - auto* display = QtWaylandClient::QWaylandIntegration::instance()->display(); - auto* inputDevice = display->lastInputDevice(); - if (inputDevice == nullptr) inputDevice = display->defaultInputDevice(); - - if (inputDevice == nullptr) { - qCCritical(logShortcutsInhibit) << "Could not create shortcuts inhibitor: No seat."; - return nullptr; - } - - auto* wlSurface = surface->surface(); - - if (this->inhibitors.contains(wlSurface)) { - auto& pair = this->inhibitors[wlSurface]; - pair.second++; - qCDebug(logShortcutsInhibit) << "Reusing existing inhibitor" << pair.first << "for surface" - << wlSurface << "- refcount:" << pair.second; - return pair.first; - } - - auto* inhibitor = - new ShortcutsInhibitor(this->inhibit_shortcuts(wlSurface, inputDevice->object()), wlSurface); - this->inhibitors.insert(wlSurface, qMakePair(inhibitor, 1)); - qCDebug(logShortcutsInhibit) << "Created inhibitor" << inhibitor << "for surface" << wlSurface; - return inhibitor; -} - -void ShortcutsInhibitManager::unrefShortcutsInhibitor(ShortcutsInhibitor* inhibitor) { - if (!inhibitor) return; - - auto* surface = inhibitor->surface(); - if (!this->inhibitors.contains(surface)) return; - - auto& pair = this->inhibitors[surface]; - pair.second--; - qCDebug(logShortcutsInhibit) << "Decremented refcount for inhibitor" << inhibitor - << "- refcount:" << pair.second; - - if (pair.second <= 0) { - qCDebug(logShortcutsInhibit) << "Refcount reached 0, destroying inhibitor" << inhibitor; - this->inhibitors.remove(surface); - delete inhibitor; - } -} - -ShortcutsInhibitor::~ShortcutsInhibitor() { - qCDebug(logShortcutsInhibit) << "Destroying inhibitor" << this << "for surface" << this->mSurface; - if (this->isInitialized()) this->destroy(); -} - -void ShortcutsInhibitor::zwp_keyboard_shortcuts_inhibitor_v1_active() { - qCDebug(logShortcutsInhibit) << "Inhibitor became active" << this; - this->bActive = true; -} - -void ShortcutsInhibitor::zwp_keyboard_shortcuts_inhibitor_v1_inactive() { - qCDebug(logShortcutsInhibit) << "Inhibitor became inactive" << this; - this->bActive = false; -} - -} // namespace qs::wayland::shortcuts_inhibit::impl \ No newline at end of file diff --git a/src/wayland/shortcuts_inhibit/proto.hpp b/src/wayland/shortcuts_inhibit/proto.hpp deleted file mode 100644 index e79d5ca..0000000 --- a/src/wayland/shortcuts_inhibit/proto.hpp +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "../../core/logcat.hpp" -#include "wayland-keyboard-shortcuts-inhibit-unstable-v1-client-protocol.h" - -namespace qs::wayland::shortcuts_inhibit::impl { - -QS_DECLARE_LOGGING_CATEGORY(logShortcutsInhibit); - -class ShortcutsInhibitor; - -class ShortcutsInhibitManager - : public QWaylandClientExtensionTemplate - , public QtWayland::zwp_keyboard_shortcuts_inhibit_manager_v1 { -public: - explicit ShortcutsInhibitManager(); - - ShortcutsInhibitor* createShortcutsInhibitor(QtWaylandClient::QWaylandWindow* surface); - void unrefShortcutsInhibitor(ShortcutsInhibitor* inhibitor); - - static ShortcutsInhibitManager* instance(); - -private: - QHash> inhibitors; -}; - -class ShortcutsInhibitor - : public QObject - , public QtWayland::zwp_keyboard_shortcuts_inhibitor_v1 { - Q_OBJECT; - -public: - explicit ShortcutsInhibitor(::zwp_keyboard_shortcuts_inhibitor_v1* inhibitor, wl_surface* surface) - : QtWayland::zwp_keyboard_shortcuts_inhibitor_v1(inhibitor) - , mSurface(surface) - , bActive(false) {} - - ~ShortcutsInhibitor() override; - Q_DISABLE_COPY_MOVE(ShortcutsInhibitor); - - [[nodiscard]] QBindable bindableActive() const { return &this->bActive; } - [[nodiscard]] bool isActive() const { return this->bActive; } - [[nodiscard]] wl_surface* surface() const { return this->mSurface; } - -signals: - void activeChanged(); - -protected: - void zwp_keyboard_shortcuts_inhibitor_v1_active() override; - void zwp_keyboard_shortcuts_inhibitor_v1_inactive() override; - -private: - wl_surface* mSurface; - Q_OBJECT_BINDABLE_PROPERTY(ShortcutsInhibitor, bool, bActive, &ShortcutsInhibitor::activeChanged); -}; - -} // namespace qs::wayland::shortcuts_inhibit::impl \ No newline at end of file diff --git a/src/wayland/shortcuts_inhibit/test/manual/test.qml b/src/wayland/shortcuts_inhibit/test/manual/test.qml deleted file mode 100644 index 1f64cbf..0000000 --- a/src/wayland/shortcuts_inhibit/test/manual/test.qml +++ /dev/null @@ -1,65 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import QtQuick.Controls -import Quickshell -import Quickshell.Wayland - -Scope { - Timer { - id: toggleTimer - interval: 100 - onTriggered: windowLoader.active = true - } - - LazyLoader { - id: windowLoader - active: true - - property bool enabled: false - - FloatingWindow { - id: w - color: contentItem.palette.window - - ColumnLayout { - anchors.centerIn: parent - - CheckBox { - id: loadedCb - text: "Loaded" - checked: true - } - - CheckBox { - id: enabledCb - text: "Enabled" - checked: windowLoader.enabled - onCheckedChanged: windowLoader.enabled = checked - } - - Label { - text: `Active: ${inhibitorLoader.item?.active ?? false}` - } - - Button { - text: "Toggle Window" - onClicked: { - windowLoader.active = false; - toggleTimer.start(); - } - } - } - - LazyLoader { - id: inhibitorLoader - active: loadedCb.checked - - ShortcutInhibitor { - window: w - enabled: enabledCb.checked - onCancelled: enabledCb.checked = false - } - } - } - } -} diff --git a/src/wayland/toplevel_management/manager.hpp b/src/wayland/toplevel_management/manager.hpp index 83e3e09..4b906a5 100644 --- a/src/wayland/toplevel_management/manager.hpp +++ b/src/wayland/toplevel_management/manager.hpp @@ -33,8 +33,8 @@ signals: protected: explicit ToplevelManager(); - void - zwlr_foreign_toplevel_manager_v1_toplevel(::zwlr_foreign_toplevel_handle_v1* toplevel) override; + void zwlr_foreign_toplevel_manager_v1_toplevel(::zwlr_foreign_toplevel_handle_v1* toplevel + ) override; private slots: void onToplevelReady(); diff --git a/src/wayland/wlr_layershell/shell_integration.hpp b/src/wayland/wlr_layershell/shell_integration.hpp index 93cda01..e92b7c6 100644 --- a/src/wayland/wlr_layershell/shell_integration.hpp +++ b/src/wayland/wlr_layershell/shell_integration.hpp @@ -15,8 +15,8 @@ public: ~LayerShellIntegration() override; Q_DISABLE_COPY_MOVE(LayerShellIntegration); - QtWaylandClient::QWaylandShellSurface* - createShellSurface(QtWaylandClient::QWaylandWindow* window) override; + QtWaylandClient::QWaylandShellSurface* createShellSurface(QtWaylandClient::QWaylandWindow* window + ) override; }; } // namespace qs::wayland::layershell diff --git a/src/wayland/wlr_layershell/surface.cpp b/src/wayland/wlr_layershell/surface.cpp index 4a5015e..26d7558 100644 --- a/src/wayland/wlr_layershell/surface.cpp +++ b/src/wayland/wlr_layershell/surface.cpp @@ -30,8 +30,8 @@ namespace qs::wayland::layershell { namespace { -[[nodiscard]] QtWayland::zwlr_layer_shell_v1::layer -toWaylandLayer(const WlrLayer::Enum& layer) noexcept { +[[nodiscard]] QtWayland::zwlr_layer_shell_v1::layer toWaylandLayer(const WlrLayer::Enum& layer +) noexcept { switch (layer) { case WlrLayer::Background: return QtWayland::zwlr_layer_shell_v1::layer_background; case WlrLayer::Bottom: return QtWayland::zwlr_layer_shell_v1::layer_bottom; @@ -42,8 +42,8 @@ toWaylandLayer(const WlrLayer::Enum& layer) noexcept { return QtWayland::zwlr_layer_shell_v1::layer_top; } -[[nodiscard]] QtWayland::zwlr_layer_surface_v1::anchor -toWaylandAnchors(const Anchors& anchors) noexcept { +[[nodiscard]] QtWayland::zwlr_layer_surface_v1::anchor toWaylandAnchors(const Anchors& anchors +) noexcept { quint32 wl = 0; if (anchors.mLeft) wl |= QtWayland::zwlr_layer_surface_v1::anchor_left; if (anchors.mRight) wl |= QtWayland::zwlr_layer_surface_v1::anchor_right; @@ -143,11 +143,11 @@ LayerSurface::LayerSurface(LayerShellIntegration* shell, QtWaylandClient::QWayla auto* waylandScreen = dynamic_cast(qwindow->screen()->handle()); - if (waylandScreen != nullptr && !waylandScreen->isPlaceholder() && waylandScreen->output()) { + if (waylandScreen != nullptr) { output = waylandScreen->output(); } else { - qWarning() - << "Layershell screen does not correspond to a real screen. Letting the compositor pick."; + qWarning( + ) << "Layershell screen does not corrospond to a real screen. Letting the compositor pick."; } } diff --git a/src/wayland/wlr_layershell/wlr_layershell.cpp b/src/wayland/wlr_layershell/wlr_layershell.cpp index 947c51a..e4726b5 100644 --- a/src/wayland/wlr_layershell/wlr_layershell.cpp +++ b/src/wayland/wlr_layershell/wlr_layershell.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "../../core/qmlscreen.hpp" @@ -26,12 +27,11 @@ WlrLayershell::WlrLayershell(QObject* parent): ProxyWindowBase(parent) { switch (this->bcExclusionEdge.value()) { case Qt::TopEdge: return this->bImplicitHeight + margins.bottom; case Qt::BottomEdge: return this->bImplicitHeight + margins.top; - case Qt::LeftEdge: return this->bImplicitWidth + margins.right; - case Qt::RightEdge: return this->bImplicitWidth + margins.left; + case Qt::LeftEdge: return this->bImplicitHeight + margins.right; + case Qt::RightEdge: return this->bImplicitHeight + margins.left; + default: return 0; } } - - return 0; }); this->bcExclusionEdge.setBinding([this] { return this->bAnchors.value().exclusionEdge(); }); @@ -173,9 +173,23 @@ WlrLayershell* WlrLayershell::qmlAttachedProperties(QObject* object) { WaylandPanelInterface::WaylandPanelInterface(QObject* parent) : PanelWindowInterface(parent) , layer(new WlrLayershell(this)) { - this->connectSignals(); // clang-format off + QObject::connect(this->layer, &ProxyWindowBase::windowConnected, this, &WaylandPanelInterface::windowConnected); + QObject::connect(this->layer, &ProxyWindowBase::visibleChanged, this, &WaylandPanelInterface::visibleChanged); + QObject::connect(this->layer, &ProxyWindowBase::backerVisibilityChanged, this, &WaylandPanelInterface::backingWindowVisibleChanged); + QObject::connect(this->layer, &ProxyWindowBase::implicitHeightChanged, this, &WaylandPanelInterface::implicitHeightChanged); + QObject::connect(this->layer, &ProxyWindowBase::implicitWidthChanged, this, &WaylandPanelInterface::implicitWidthChanged); + QObject::connect(this->layer, &ProxyWindowBase::heightChanged, this, &WaylandPanelInterface::heightChanged); + QObject::connect(this->layer, &ProxyWindowBase::widthChanged, this, &WaylandPanelInterface::widthChanged); + QObject::connect(this->layer, &ProxyWindowBase::devicePixelRatioChanged, this, &WaylandPanelInterface::devicePixelRatioChanged); + QObject::connect(this->layer, &ProxyWindowBase::screenChanged, this, &WaylandPanelInterface::screenChanged); + QObject::connect(this->layer, &ProxyWindowBase::windowTransformChanged, this, &WaylandPanelInterface::windowTransformChanged); + QObject::connect(this->layer, &ProxyWindowBase::colorChanged, this, &WaylandPanelInterface::colorChanged); + QObject::connect(this->layer, &ProxyWindowBase::maskChanged, this, &WaylandPanelInterface::maskChanged); + QObject::connect(this->layer, &ProxyWindowBase::surfaceFormatChanged, this, &WaylandPanelInterface::surfaceFormatChanged); + + // panel specific QObject::connect(this->layer, &WlrLayershell::anchorsChanged, this, &WaylandPanelInterface::anchorsChanged); QObject::connect(this->layer, &WlrLayershell::marginsChanged, this, &WaylandPanelInterface::marginsChanged); QObject::connect(this->layer, &WlrLayershell::exclusiveZoneChanged, this, &WaylandPanelInterface::exclusiveZoneChanged); @@ -192,13 +206,32 @@ void WaylandPanelInterface::onReload(QObject* oldInstance) { this->layer->reload(old != nullptr ? old->layer : nullptr); } +QQmlListProperty WaylandPanelInterface::data() { return this->layer->data(); } ProxyWindowBase* WaylandPanelInterface::proxyWindow() const { return this->layer; } +QQuickItem* WaylandPanelInterface::contentItem() const { return this->layer->contentItem(); } + +bool WaylandPanelInterface::isBackingWindowVisible() const { + return this->layer->isVisibleDirect(); +} + +qreal WaylandPanelInterface::devicePixelRatio() const { return this->layer->devicePixelRatio(); } // NOLINTBEGIN #define proxyPair(type, get, set) \ type WaylandPanelInterface::get() const { return this->layer->get(); } \ void WaylandPanelInterface::set(type value) { this->layer->set(value); } +proxyPair(bool, isVisible, setVisible); +proxyPair(qint32, implicitWidth, setImplicitWidth); +proxyPair(qint32, implicitHeight, setImplicitHeight); +proxyPair(qint32, width, setWidth); +proxyPair(qint32, height, setHeight); +proxyPair(QuickshellScreenInfo*, screen, setScreen); +proxyPair(QColor, color, setColor); +proxyPair(PendingRegion*, mask, setMask); +proxyPair(QsSurfaceFormat, surfaceFormat, setSurfaceFormat); + +// panel specific proxyPair(Anchors, anchors, setAnchors); proxyPair(Margins, margins, setMargins); proxyPair(qint32, exclusiveZone, setExclusiveZone); diff --git a/src/wayland/wlr_layershell/wlr_layershell.hpp b/src/wayland/wlr_layershell/wlr_layershell.hpp index 0f5617f..457a1f5 100644 --- a/src/wayland/wlr_layershell/wlr_layershell.hpp +++ b/src/wayland/wlr_layershell/wlr_layershell.hpp @@ -216,8 +216,43 @@ public: void onReload(QObject* oldInstance) override; [[nodiscard]] ProxyWindowBase* proxyWindow() const override; + [[nodiscard]] QQuickItem* contentItem() const override; // NOLINTBEGIN + [[nodiscard]] bool isVisible() const override; + [[nodiscard]] bool isBackingWindowVisible() const override; + void setVisible(bool visible) override; + + [[nodiscard]] qint32 implicitWidth() const override; + void setImplicitWidth(qint32 implicitWidth) override; + + [[nodiscard]] qint32 implicitHeight() const override; + void setImplicitHeight(qint32 implicitHeight) override; + + [[nodiscard]] qint32 width() const override; + void setWidth(qint32 width) override; + + [[nodiscard]] qint32 height() const override; + void setHeight(qint32 height) override; + + [[nodiscard]] virtual qreal devicePixelRatio() const override; + + [[nodiscard]] QuickshellScreenInfo* screen() const override; + void setScreen(QuickshellScreenInfo* screen) override; + + [[nodiscard]] QColor color() const override; + void setColor(QColor color) override; + + [[nodiscard]] PendingRegion* mask() const override; + void setMask(PendingRegion* mask) override; + + [[nodiscard]] QsSurfaceFormat surfaceFormat() const override; + void setSurfaceFormat(QsSurfaceFormat mask) override; + + [[nodiscard]] QQmlListProperty data() override; + + // panel specific + [[nodiscard]] Anchors anchors() const override; void setAnchors(Anchors anchors) override; diff --git a/src/widgets/ClippingRectangle.qml b/src/widgets/ClippingRectangle.qml index 604f346..86fe601 100644 --- a/src/widgets/ClippingRectangle.qml +++ b/src/widgets/ClippingRectangle.qml @@ -1,5 +1,3 @@ -pragma ComponentBehavior: Bound - import QtQuick ///! Rectangle capable of clipping content inside its border. @@ -74,12 +72,6 @@ Item { } } - ShaderEffectSource { - id: shaderSource - hideSource: true - sourceItem: contentItemContainer - } - ShaderEffect { id: shader anchors.fill: root @@ -87,6 +79,10 @@ Item { property Rectangle rect: rectangle property color backgroundColor: "white" property color borderColor: root.border.color - property ShaderEffectSource content: shaderSource + + property ShaderEffectSource content: ShaderEffectSource { + hideSource: true + sourceItem: contentItemContainer + } } } diff --git a/src/widgets/marginwrapper.cpp b/src/widgets/marginwrapper.cpp index b7d410c..9960bba 100644 --- a/src/widgets/marginwrapper.cpp +++ b/src/widgets/marginwrapper.cpp @@ -12,8 +12,8 @@ namespace qs::widgets { MarginWrapperManager::MarginWrapperManager(QObject* parent): WrapperManager(parent) { this->bTopMargin.setBinding([this] { return this->bExtraMargin - + (this->bOverrides.value().testFlag(TopMargin) ? this->bTopMarginOverride - : this->bMargin); + + (this->bOverrides.value().testFlag(TopMargin) ? this->bTopMarginOverride : this->bMargin + ); }); this->bBottomMargin.setBinding([this] { diff --git a/src/widgets/wrapper.hpp b/src/widgets/wrapper.hpp index aca4172..d506750 100644 --- a/src/widgets/wrapper.hpp +++ b/src/widgets/wrapper.hpp @@ -85,9 +85,6 @@ class WrapperManager : public QObject , public QQmlParserStatus { Q_OBJECT; - QML_ELEMENT; - Q_INTERFACES(QQmlParserStatus); - // clang-format off /// The wrapper component's selected child. /// @@ -105,6 +102,7 @@ class WrapperManager /// This property may not be changed after Component.onCompleted. Q_PROPERTY(QQuickItem* wrapper READ wrapper WRITE setWrapper NOTIFY wrapperChanged FINAL); // clang-format on + QML_ELEMENT; public: explicit WrapperManager(QObject* parent = nullptr): QObject(parent) {} @@ -139,8 +137,8 @@ protected: void printChildCountWarning() const; void updateGeometry(); - virtual void disconnectChild() {} - virtual void connectChild() {} + virtual void disconnectChild() {}; + virtual void connectChild() {}; QQuickItem* mWrapper = nullptr; QQuickItem* mAssignedWrapper = nullptr; diff --git a/src/window/floatingwindow.cpp b/src/window/floatingwindow.cpp index a0c9fdd..2f196fc 100644 --- a/src/window/floatingwindow.cpp +++ b/src/window/floatingwindow.cpp @@ -1,12 +1,11 @@ #include "floatingwindow.hpp" -#include #include #include #include +#include #include #include -#include #include "proxywindow.hpp" #include "windowinterface.hpp" @@ -51,13 +50,24 @@ void ProxyFloatingWindow::onMaximumSizeChanged() { FloatingWindowInterface::FloatingWindowInterface(QObject* parent) : WindowInterface(parent) , window(new ProxyFloatingWindow(this)) { - this->connectSignals(); - // clang-format off + QObject::connect(this->window, &ProxyWindowBase::windowConnected, this, &FloatingWindowInterface::windowConnected); + QObject::connect(this->window, &ProxyWindowBase::visibleChanged, this, &FloatingWindowInterface::visibleChanged); + QObject::connect(this->window, &ProxyWindowBase::backerVisibilityChanged, this, &FloatingWindowInterface::backingWindowVisibleChanged); + QObject::connect(this->window, &ProxyWindowBase::heightChanged, this, &FloatingWindowInterface::heightChanged); + QObject::connect(this->window, &ProxyWindowBase::widthChanged, this, &FloatingWindowInterface::widthChanged); + QObject::connect(this->window, &ProxyWindowBase::implicitHeightChanged, this, &FloatingWindowInterface::implicitHeightChanged); + QObject::connect(this->window, &ProxyWindowBase::implicitWidthChanged, this, &FloatingWindowInterface::implicitWidthChanged); + QObject::connect(this->window, &ProxyWindowBase::devicePixelRatioChanged, this, &FloatingWindowInterface::devicePixelRatioChanged); + QObject::connect(this->window, &ProxyWindowBase::screenChanged, this, &FloatingWindowInterface::screenChanged); + QObject::connect(this->window, &ProxyWindowBase::windowTransformChanged, this, &FloatingWindowInterface::windowTransformChanged); + QObject::connect(this->window, &ProxyWindowBase::colorChanged, this, &FloatingWindowInterface::colorChanged); + QObject::connect(this->window, &ProxyWindowBase::maskChanged, this, &FloatingWindowInterface::maskChanged); + QObject::connect(this->window, &ProxyWindowBase::surfaceFormatChanged, this, &FloatingWindowInterface::surfaceFormatChanged); + QObject::connect(this->window, &ProxyFloatingWindow::titleChanged, this, &FloatingWindowInterface::titleChanged); QObject::connect(this->window, &ProxyFloatingWindow::minimumSizeChanged, this, &FloatingWindowInterface::minimumSizeChanged); QObject::connect(this->window, &ProxyFloatingWindow::maximumSizeChanged, this, &FloatingWindowInterface::maximumSizeChanged); - QObject::connect(this->window, &ProxyWindowBase::windowConnected, this, &FloatingWindowInterface::onWindowConnected); // clang-format on } @@ -68,104 +78,30 @@ void FloatingWindowInterface::onReload(QObject* oldInstance) { this->window->reload(old != nullptr ? old->window : nullptr); } +QQmlListProperty FloatingWindowInterface::data() { return this->window->data(); } ProxyWindowBase* FloatingWindowInterface::proxyWindow() const { return this->window; } +QQuickItem* FloatingWindowInterface::contentItem() const { return this->window->contentItem(); } -void FloatingWindowInterface::onWindowConnected() { - auto* qw = this->window->backingWindow(); - if (qw) { - QObject::connect( - qw, - &QWindow::windowStateChanged, - this, - &FloatingWindowInterface::onWindowStateChanged - ); - this->setMinimized(this->mMinimized); - this->setMaximized(this->mMaximized); - this->setFullscreen(this->mFullscreen); - this->onWindowStateChanged(); - } +bool FloatingWindowInterface::isBackingWindowVisible() const { + return this->window->isVisibleDirect(); } -void FloatingWindowInterface::onWindowStateChanged() { - auto* qw = this->window->backingWindow(); - auto states = qw ? qw->windowStates() : Qt::WindowStates(); +qreal FloatingWindowInterface::devicePixelRatio() const { return this->window->devicePixelRatio(); } - auto minimized = states.testFlag(Qt::WindowMinimized); - auto maximized = states.testFlag(Qt::WindowMaximized); - auto fullscreen = states.testFlag(Qt::WindowFullScreen); +// NOLINTBEGIN +#define proxyPair(type, get, set) \ + type FloatingWindowInterface::get() const { return this->window->get(); } \ + void FloatingWindowInterface::set(type value) { this->window->set(value); } - if (minimized != this->mWasMinimized) { - this->mWasMinimized = minimized; - emit this->minimizedChanged(); - } +proxyPair(bool, isVisible, setVisible); +proxyPair(qint32, implicitWidth, setImplicitWidth); +proxyPair(qint32, implicitHeight, setImplicitHeight); +proxyPair(qint32, width, setWidth); +proxyPair(qint32, height, setHeight); +proxyPair(QuickshellScreenInfo*, screen, setScreen); +proxyPair(QColor, color, setColor); +proxyPair(PendingRegion*, mask, setMask); +proxyPair(QsSurfaceFormat, surfaceFormat, setSurfaceFormat); - if (maximized != this->mWasMaximized) { - this->mWasMaximized = maximized; - emit this->maximizedChanged(); - } - - if (fullscreen != this->mWasFullscreen) { - this->mWasFullscreen = fullscreen; - emit this->fullscreenChanged(); - } -} - -bool FloatingWindowInterface::isMinimized() const { - auto* qw = this->window->backingWindow(); - if (!qw) return this->mWasMinimized; - return qw->windowStates().testFlag(Qt::WindowMinimized); -} - -void FloatingWindowInterface::setMinimized(bool minimized) { - this->mMinimized = minimized; - - if (auto* qw = this->window->backingWindow()) { - auto states = qw->windowStates(); - states.setFlag(Qt::WindowMinimized, minimized); - qw->setWindowStates(states); - } -} - -bool FloatingWindowInterface::isMaximized() const { - auto* qw = this->window->backingWindow(); - if (!qw) return this->mWasMaximized; - return qw->windowStates().testFlag(Qt::WindowMaximized); -} - -void FloatingWindowInterface::setMaximized(bool maximized) { - this->mMaximized = maximized; - - if (auto* qw = this->window->backingWindow()) { - auto states = qw->windowStates(); - states.setFlag(Qt::WindowMaximized, maximized); - qw->setWindowStates(states); - } -} - -bool FloatingWindowInterface::isFullscreen() const { - auto* qw = this->window->backingWindow(); - if (!qw) return this->mWasFullscreen; - return qw->windowStates().testFlag(Qt::WindowFullScreen); -} - -void FloatingWindowInterface::setFullscreen(bool fullscreen) { - this->mFullscreen = fullscreen; - - if (auto* qw = this->window->backingWindow()) { - auto states = qw->windowStates(); - states.setFlag(Qt::WindowFullScreen, fullscreen); - qw->setWindowStates(states); - } -} - -bool FloatingWindowInterface::startSystemMove() const { - auto* qw = this->window->backingWindow(); - if (!qw) return false; - return qw->startSystemMove(); -} - -bool FloatingWindowInterface::startSystemResize(Qt::Edges edges) const { - auto* qw = this->window->backingWindow(); - if (!qw) return false; - return qw->startSystemResize(edges); -} +#undef proxyPair +// NOLINTEND diff --git a/src/window/floatingwindow.hpp b/src/window/floatingwindow.hpp index 06b5b9e..48b7c13 100644 --- a/src/window/floatingwindow.hpp +++ b/src/window/floatingwindow.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -69,12 +68,6 @@ class FloatingWindowInterface: public WindowInterface { Q_PROPERTY(QSize minimumSize READ default WRITE default NOTIFY minimumSizeChanged BINDABLE bindableMinimumSize); /// Maximum window size given to the window system. Q_PROPERTY(QSize maximumSize READ default WRITE default NOTIFY maximumSizeChanged BINDABLE bindableMaximumSize); - /// Whether the window is currently minimized. - Q_PROPERTY(bool minimized READ isMinimized WRITE setMinimized NOTIFY minimizedChanged); - /// Whether the window is currently maximized. - Q_PROPERTY(bool maximized READ isMaximized WRITE setMaximized NOTIFY maximizedChanged); - /// Whether the window is currently fullscreen. - Q_PROPERTY(bool fullscreen READ isFullscreen WRITE setFullscreen NOTIFY fullscreenChanged); // clang-format on QML_NAMED_ELEMENT(FloatingWindow); @@ -84,41 +77,51 @@ public: void onReload(QObject* oldInstance) override; [[nodiscard]] ProxyWindowBase* proxyWindow() const override; + [[nodiscard]] QQuickItem* contentItem() const override; - [[nodiscard]] QBindable bindableMinimumSize() { return &this->window->bMinimumSize; } - [[nodiscard]] QBindable bindableMaximumSize() { return &this->window->bMaximumSize; } - [[nodiscard]] QBindable bindableTitle() { return &this->window->bTitle; } + // NOLINTBEGIN + [[nodiscard]] bool isVisible() const override; + [[nodiscard]] bool isBackingWindowVisible() const override; + void setVisible(bool visible) override; - [[nodiscard]] bool isMinimized() const; - void setMinimized(bool minimized); - [[nodiscard]] bool isMaximized() const; - void setMaximized(bool maximized); - [[nodiscard]] bool isFullscreen() const; - void setFullscreen(bool fullscreen); + [[nodiscard]] qint32 implicitWidth() const override; + void setImplicitWidth(qint32 implicitWidth) override; - /// Start a system move operation. Must be called during a pointer press/drag. - Q_INVOKABLE [[nodiscard]] bool startSystemMove() const; - /// Start a system resize operation. Must be called during a pointer press/drag. - Q_INVOKABLE [[nodiscard]] bool startSystemResize(Qt::Edges edges) const; + [[nodiscard]] qint32 implicitHeight() const override; + void setImplicitHeight(qint32 implicitHeight) override; + + [[nodiscard]] qint32 width() const override; + void setWidth(qint32 width) override; + + [[nodiscard]] qint32 height() const override; + void setHeight(qint32 height) override; + + [[nodiscard]] virtual qreal devicePixelRatio() const override; + + [[nodiscard]] QuickshellScreenInfo* screen() const override; + void setScreen(QuickshellScreenInfo* screen) override; + + [[nodiscard]] QColor color() const override; + void setColor(QColor color) override; + + [[nodiscard]] PendingRegion* mask() const override; + void setMask(PendingRegion* mask) override; + + [[nodiscard]] QsSurfaceFormat surfaceFormat() const override; + void setSurfaceFormat(QsSurfaceFormat mask) override; + + [[nodiscard]] QQmlListProperty data() override; + // NOLINTEND + + QBindable bindableMinimumSize() { return &this->window->bMinimumSize; } + QBindable bindableMaximumSize() { return &this->window->bMaximumSize; } + QBindable bindableTitle() { return &this->window->bTitle; } signals: void minimumSizeChanged(); void maximumSizeChanged(); void titleChanged(); - void minimizedChanged(); - void maximizedChanged(); - void fullscreenChanged(); - -private slots: - void onWindowConnected(); - void onWindowStateChanged(); private: ProxyFloatingWindow* window; - bool mMinimized = false; - bool mMaximized = false; - bool mFullscreen = false; - bool mWasMinimized = false; - bool mWasMaximized = false; - bool mWasFullscreen = false; }; diff --git a/src/window/popupwindow.cpp b/src/window/popupwindow.cpp index bfe261e..ec2be7e 100644 --- a/src/window/popupwindow.cpp +++ b/src/window/popupwindow.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -13,74 +12,29 @@ ProxyPopupWindow::ProxyPopupWindow(QObject* parent): ProxyWindowBase(parent) { this->mVisible = false; - // clang-format off - QObject::connect(&this->mAnchor, &PopupAnchor::windowChanged, this, &ProxyPopupWindow::onParentWindowChanged); + QObject::connect(&this->mAnchor, &PopupAnchor::windowChanged, this, &ProxyPopupWindow::parentWindowChanged); QObject::connect(&this->mAnchor, &PopupAnchor::windowRectChanged, this, &ProxyPopupWindow::reposition); QObject::connect(&this->mAnchor, &PopupAnchor::edgesChanged, this, &ProxyPopupWindow::reposition); QObject::connect(&this->mAnchor, &PopupAnchor::gravityChanged, this, &ProxyPopupWindow::reposition); QObject::connect(&this->mAnchor, &PopupAnchor::adjustmentChanged, this, &ProxyPopupWindow::reposition); + QObject::connect(&this->mAnchor, &PopupAnchor::backingWindowVisibilityChanged, this, &ProxyPopupWindow::onParentUpdated); // clang-format on - - this->bTargetVisible.setBinding([this] { - auto* window = this->mAnchor.bindableProxyWindow().value(); - - if (window == this) { - qmlWarning(this) << "Anchor assigned to current window"; - return false; - } - - if (!window) return false; - - if (!this->bWantsVisible) return false; - return window->bindableBackerVisibility().value(); - }); -} - -void ProxyPopupWindow::targetVisibleChanged() { - this->ProxyWindowBase::setVisible(this->bTargetVisible); } void ProxyPopupWindow::completeWindow() { this->ProxyWindowBase::completeWindow(); // clang-format off - QObject::connect(this, &ProxyWindowBase::closed, this, &ProxyPopupWindow::onClosed); + QObject::connect(this->window, &QWindow::visibleChanged, this, &ProxyPopupWindow::onVisibleChanged); QObject::connect(this->window, &QWindow::widthChanged, this, &ProxyPopupWindow::reposition); QObject::connect(this->window, &QWindow::heightChanged, this, &ProxyPopupWindow::reposition); // clang-format on - auto* bw = this->mAnchor.backingWindow(); - - if (bw && PopupPositioner::instance()->shouldRepositionOnMove()) { - QObject::connect(bw, &QWindow::xChanged, this, &ProxyPopupWindow::reposition); - QObject::connect(bw, &QWindow::yChanged, this, &ProxyPopupWindow::reposition); - QObject::connect(bw, &QWindow::widthChanged, this, &ProxyPopupWindow::reposition); - QObject::connect(bw, &QWindow::heightChanged, this, &ProxyPopupWindow::reposition); - } - - this->window->setTransientParent(bw); - this->window->setFlag(this->bWantsGrab ? Qt::Popup : Qt::ToolTip); - - this->mAnchor.markDirty(); - PopupPositioner::instance()->reposition(&this->mAnchor, this->window); + this->window->setFlag(Qt::ToolTip); } -void ProxyPopupWindow::postCompleteWindow() { - this->ProxyWindowBase::setVisible(this->bTargetVisible); -} - -void ProxyPopupWindow::onClosed() { this->bWantsVisible = false; } - -void ProxyPopupWindow::onParentWindowChanged() { - // recreate for new parent - if (this->bTargetVisible && this->isVisibleDirect()) { - this->ProxyWindowBase::setVisibleDirect(false); - this->ProxyWindowBase::setVisibleDirect(true); - } - - emit this->parentWindowChanged(); -} +void ProxyPopupWindow::postCompleteWindow() { this->updateTransientParent(); } void ProxyPopupWindow::setParentWindow(QObject* parent) { qmlWarning(this) << "PopupWindow.parentWindow is deprecated. Use PopupWindow.anchor.window."; @@ -89,13 +43,59 @@ void ProxyPopupWindow::setParentWindow(QObject* parent) { QObject* ProxyPopupWindow::parentWindow() const { return this->mAnchor.window(); } +void ProxyPopupWindow::updateTransientParent() { + auto* bw = this->mAnchor.backingWindow(); + + if (this->window != nullptr && bw != this->window->transientParent()) { + if (this->window->transientParent()) { + QObject::disconnect(this->window->transientParent(), nullptr, this, nullptr); + } + + if (bw && PopupPositioner::instance()->shouldRepositionOnMove()) { + QObject::connect(bw, &QWindow::xChanged, this, &ProxyPopupWindow::reposition); + QObject::connect(bw, &QWindow::yChanged, this, &ProxyPopupWindow::reposition); + QObject::connect(bw, &QWindow::widthChanged, this, &ProxyPopupWindow::reposition); + QObject::connect(bw, &QWindow::heightChanged, this, &ProxyPopupWindow::reposition); + } + + this->window->setTransientParent(bw); + } + + this->updateVisible(); +} + +void ProxyPopupWindow::onParentUpdated() { this->updateTransientParent(); } + void ProxyPopupWindow::setScreen(QuickshellScreenInfo* /*unused*/) { - qmlWarning( - this + qmlWarning(this ) << "Cannot set screen of popup window, as that is controlled by the parent window"; } -void ProxyPopupWindow::setVisible(bool visible) { this->bWantsVisible = visible; } +void ProxyPopupWindow::setVisible(bool visible) { + if (visible == this->wantsVisible) return; + this->wantsVisible = visible; + this->updateVisible(); +} + +void ProxyPopupWindow::updateVisible() { + auto target = this->wantsVisible && this->mAnchor.window() != nullptr + && this->mAnchor.proxyWindow()->isVisibleDirect(); + + if (target && this->window != nullptr && !this->window->isVisible()) { + PopupPositioner::instance()->reposition(&this->mAnchor, this->window); + } + + this->ProxyWindowBase::setVisible(target); +} + +void ProxyPopupWindow::onVisibleChanged() { + // If the window was made invisible without its parent becoming invisible + // the compositor probably destroyed it. Without this the window won't ever + // be able to become visible again. + if (this->window->transientParent() && this->window->transientParent()->isVisible()) { + this->wantsVisible = this->window->isVisible(); + } +} void ProxyPopupWindow::setRelativeX(qint32 x) { qmlWarning(this) << "PopupWindow.relativeX is deprecated. Use PopupWindow.anchor.rect.x."; @@ -143,5 +143,3 @@ void ProxyPopupWindow::onPolished() { } } } - -bool ProxyPopupWindow::deleteOnInvisible() const { return true; } diff --git a/src/window/popupwindow.hpp b/src/window/popupwindow.hpp index d95eac0..e00495c 100644 --- a/src/window/popupwindow.hpp +++ b/src/window/popupwindow.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include #include @@ -76,15 +75,6 @@ class ProxyPopupWindow: public ProxyWindowBase { /// /// The popup will not be shown until @@anchor is valid, regardless of this property. QSDOC_PROPERTY_OVERRIDE(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged); - /// If true, the popup window will be dismissed and @@visible will change to false - /// if the user clicks outside of the popup or it is otherwise closed. - /// - /// > [!WARNING] Changes to this property while the window is open will only take - /// > effect after the window is hidden and shown again. - /// - /// > [!NOTE] Under Hyprland, @@Quickshell.Hyprland.HyprlandFocusGrab provides more advanced - /// > functionality such as detecting clicks outside without closing the popup. - Q_PROPERTY(bool grabFocus READ default WRITE default NOTIFY grabFocusChanged BINDABLE bindableGrabFocus); /// The screen that the window currently occupies. /// /// This may be modified to move the window to the given screen. @@ -98,7 +88,6 @@ public: void completeWindow() override; void postCompleteWindow() override; void onPolished() override; - bool deleteOnInvisible() const override; void setScreen(QuickshellScreenInfo* screen) override; void setVisible(bool visible) override; @@ -112,37 +101,24 @@ public: [[nodiscard]] qint32 relativeY() const; void setRelativeY(qint32 y); - [[nodiscard]] QBindable bindableGrabFocus() { return &this->bWantsGrab; } - [[nodiscard]] PopupAnchor* anchor(); signals: void parentWindowChanged(); void relativeXChanged(); void relativeYChanged(); - void grabFocusChanged(); private slots: - void onParentWindowChanged(); - void onClosed(); + void onVisibleChanged(); + void onParentUpdated(); void reposition(); private: - void targetVisibleChanged(); - QQuickWindow* parentBackingWindow(); + void updateTransientParent(); + void updateVisible(); PopupAnchor mAnchor {this}; + bool wantsVisible = false; bool pendingReposition = false; - - Q_OBJECT_BINDABLE_PROPERTY(ProxyPopupWindow, bool, bWantsVisible); - - Q_OBJECT_BINDABLE_PROPERTY( - ProxyPopupWindow, - bool, - bTargetVisible, - &ProxyPopupWindow::targetVisibleChanged - ); - - Q_OBJECT_BINDABLE_PROPERTY(ProxyPopupWindow, bool, bWantsGrab); }; diff --git a/src/window/proxywindow.cpp b/src/window/proxywindow.cpp index 62126bd..56d250c 100644 --- a/src/window/proxywindow.cpp +++ b/src/window/proxywindow.cpp @@ -1,7 +1,6 @@ #include "proxywindow.hpp" #include -#include #include #include #include @@ -13,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -58,10 +56,9 @@ ProxyWindowBase::ProxyWindowBase(QObject* parent) ProxyWindowBase::~ProxyWindowBase() { this->deleteWindow(true); } void ProxyWindowBase::onReload(QObject* oldInstance) { - if (this->mVisible) this->window = this->retrieveWindow(oldInstance); + this->window = this->retrieveWindow(oldInstance); auto wasVisible = this->window != nullptr && this->window->isVisible(); - - if (this->mVisible) this->ensureQWindow(); + this->ensureQWindow(); // The qml engine will leave the WindowInterface as owner of everything // nested in an item, so we have to make sure the interface's children @@ -78,21 +75,17 @@ void ProxyWindowBase::onReload(QObject* oldInstance) { Reloadable::reloadChildrenRecursive(this, oldInstance); - if (this->mVisible) { - this->connectWindow(); - this->completeWindow(); - } + this->connectWindow(); + this->completeWindow(); this->reloadComplete = true; - if (this->mVisible) { - emit this->windowConnected(); - this->postCompleteWindow(); + emit this->windowConnected(); + this->postCompleteWindow(); - if (wasVisible && this->isVisibleDirect()) { - this->bBackerVisibility = true; - this->onExposed(); - } + if (wasVisible && this->isVisibleDirect()) { + emit this->backerVisibilityChanged(); + this->onExposed(); } } @@ -119,8 +112,6 @@ void ProxyWindowBase::ensureQWindow() { auto opaque = this->qsSurfaceFormat.opaqueModified ? this->qsSurfaceFormat.opaque : this->mColor.alpha() >= 255; - format.setOption(QSurfaceFormat::ResetNotification); - if (opaque) format.setAlphaBufferSize(0); else format.setAlphaBufferSize(8); @@ -148,15 +139,6 @@ void ProxyWindowBase::ensureQWindow() { this->window = nullptr; // createQQuickWindow may indirectly reference this->window this->window = this->createQQuickWindow(); this->window->setFormat(format); - - // needed for vulkan dmabuf import, qt ignores these if not applicable - auto graphicsConfig = this->window->graphicsConfiguration(); - graphicsConfig.setDeviceExtensions({ - "VK_KHR_external_memory_fd", - "VK_EXT_external_memory_dma_buf", - "VK_EXT_image_drm_format_modifier", - }); - this->window->setGraphicsConfiguration(graphicsConfig); } void ProxyWindowBase::createWindow() { @@ -168,7 +150,13 @@ void ProxyWindowBase::createWindow() { void ProxyWindowBase::deleteWindow(bool keepItemOwnership) { if (this->window != nullptr) emit this->windowDestroyed(); - if (auto* window = this->disownWindow(keepItemOwnership)) window->deleteLater(); + if (auto* window = this->disownWindow(keepItemOwnership)) { + if (auto* generation = EngineGeneration::findObjectGeneration(this)) { + generation->deregisterIncubationController(window->incubationController()); + } + + window->deleteLater(); + } } ProxiedWindow* ProxyWindowBase::disownWindow(bool keepItemOwnership) { @@ -194,20 +182,19 @@ void ProxyWindowBase::connectWindow() { if (auto* generation = EngineGeneration::findObjectGeneration(this)) { // All windows have effectively the same incubation controller so it dosen't matter // which window it belongs to. We do want to replace the delay one though. - generation->trackWindowIncubationController(this->window); + generation->registerIncubationController(this->window->incubationController()); } this->window->setProxy(this); // clang-format off - QObject::connect(this->window, &QWindow::visibilityChanged, this, &ProxyWindowBase::onVisibleChanged); + QObject::connect(this->window, &QWindow::visibilityChanged, this, &ProxyWindowBase::visibleChanged); QObject::connect(this->window, &QWindow::xChanged, this, &ProxyWindowBase::xChanged); QObject::connect(this->window, &QWindow::yChanged, this, &ProxyWindowBase::yChanged); QObject::connect(this->window, &QWindow::widthChanged, this, &ProxyWindowBase::widthChanged); QObject::connect(this->window, &QWindow::heightChanged, this, &ProxyWindowBase::heightChanged); QObject::connect(this->window, &QWindow::screenChanged, this, &ProxyWindowBase::screenChanged); QObject::connect(this->window, &QQuickWindow::colorChanged, this, &ProxyWindowBase::colorChanged); - QObject::connect(this->window, &QQuickWindow::sceneGraphError, this, &ProxyWindowBase::onSceneGraphError); QObject::connect(this->window, &ProxiedWindow::exposed, this, &ProxyWindowBase::onExposed); QObject::connect(this->window, &ProxiedWindow::devicePixelRatioChanged, this, &ProxyWindowBase::devicePixelRatioChanged); // clang-format on @@ -223,7 +210,6 @@ void ProxyWindowBase::completeWindow() { this->trySetHeight(this->implicitHeight()); this->setColor(this->mColor); this->updateMask(); - QQuickWindowPrivate::get(this->window)->updatesEnabled = this->mUpdatesEnabled; // notify initial / post-connection geometry emit this->xChanged(); @@ -240,32 +226,6 @@ void ProxyWindowBase::completeWindow() { emit this->screenChanged(); } -void ProxyWindowBase::onSceneGraphError( - QQuickWindow::SceneGraphError error, - const QString& message -) { - if (error == QQuickWindow::ContextNotAvailable) { - qCritical().nospace() << "Failed to create graphics context for " << this << ": " << message; - } else { - qCritical().nospace() << "Scene graph error " << error << " occurred for " << this << ": " - << message; - } - - emit this->resourcesLost(); - this->mVisible = false; - this->setVisibleDirect(false); -} - -void ProxyWindowBase::onVisibleChanged() { - if (this->mVisible && !this->window->isVisible()) { - this->mVisible = false; - this->setVisibleDirect(false); - emit this->closed(); - } - - emit this->visibleChanged(); -} - bool ProxyWindowBase::deleteOnInvisible() const { return false; } QQuickWindow* ProxyWindowBase::backingWindow() const { return this->window; } @@ -288,27 +248,24 @@ void ProxyWindowBase::setVisible(bool visible) { void ProxyWindowBase::setVisibleDirect(bool visible) { if (this->deleteOnInvisible()) { + if (visible == this->isVisibleDirect()) return; + if (visible) { - if (visible == this->isVisibleDirect()) return; this->createWindow(); this->polishItems(); this->window->setVisible(true); - this->bBackerVisibility = true; + emit this->backerVisibilityChanged(); } else { - if (this->window != nullptr) this->window->setVisible(false); - this->bBackerVisibility = false; - this->deleteWindow(); - } - } else { - if (visible && this->window == nullptr) { - this->createWindow(); - } - - if (this->window != nullptr) { - if (visible) this->polishItems(); - this->window->setVisible(visible); - this->bBackerVisibility = visible; + if (this->window != nullptr) { + this->window->setVisible(false); + emit this->backerVisibilityChanged(); + this->deleteWindow(); + } } + } else if (this->window != nullptr) { + if (visible) this->polishItems(); + this->window->setVisible(visible); + emit this->backerVisibilityChanged(); } } @@ -480,19 +437,6 @@ void ProxyWindowBase::setSurfaceFormat(QsSurfaceFormat format) { emit this->surfaceFormatChanged(); } -bool ProxyWindowBase::updatesEnabled() const { return this->mUpdatesEnabled; } - -void ProxyWindowBase::setUpdatesEnabled(bool updatesEnabled) { - if (updatesEnabled == this->mUpdatesEnabled) return; - this->mUpdatesEnabled = updatesEnabled; - - if (this->window != nullptr) { - QQuickWindowPrivate::get(this->window)->updatesEnabled = updatesEnabled; - } - - emit this->updatesEnabledChanged(); -} - qreal ProxyWindowBase::devicePixelRatio() const { if (this->window != nullptr) return this->window->devicePixelRatio(); if (this->mScreen != nullptr) return this->mScreen->devicePixelRatio(); diff --git a/src/window/proxywindow.hpp b/src/window/proxywindow.hpp index aec821e..3fbc08e 100644 --- a/src/window/proxywindow.hpp +++ b/src/window/proxywindow.hpp @@ -57,7 +57,6 @@ class ProxyWindowBase: public Reloadable { Q_PROPERTY(QObject* windowTransform READ windowTransform NOTIFY windowTransformChanged); Q_PROPERTY(bool backingWindowVisible READ isVisibleDirect NOTIFY backerVisibilityChanged); Q_PROPERTY(QsSurfaceFormat surfaceFormat READ surfaceFormat WRITE setSurfaceFormat NOTIFY surfaceFormatChanged); - Q_PROPERTY(bool updatesEnabled READ updatesEnabled WRITE setUpdatesEnabled NOTIFY updatesEnabledChanged); Q_PROPERTY(QQmlListProperty data READ data); // clang-format on Q_CLASSINFO("DefaultProperty", "data"); @@ -102,10 +101,6 @@ public: virtual void setVisible(bool visible); virtual void setVisibleDirect(bool visible); - [[nodiscard]] QBindable bindableBackerVisibility() const { - return &this->bBackerVisibility; - } - void schedulePolish(); [[nodiscard]] virtual qint32 x() const; @@ -141,16 +136,11 @@ public: [[nodiscard]] QsSurfaceFormat surfaceFormat() const { return this->qsSurfaceFormat; } void setSurfaceFormat(QsSurfaceFormat format); - [[nodiscard]] bool updatesEnabled() const; - void setUpdatesEnabled(bool updatesEnabled); - [[nodiscard]] QObject* windowTransform() const { return nullptr; } // NOLINT [[nodiscard]] QQmlListProperty data(); signals: - void closed(); - void resourcesLost(); void windowConnected(); void windowDestroyed(); void visibleChanged(); @@ -167,20 +157,15 @@ signals: void colorChanged(); void maskChanged(); void surfaceFormatChanged(); - void updatesEnabledChanged(); void polished(); protected slots: virtual void onWidthChanged(); virtual void onHeightChanged(); - virtual void onPolished(); - -private slots: - void onSceneGraphError(QQuickWindow::SceneGraphError error, const QString& message); - void onVisibleChanged(); void onMaskChanged(); void onMaskDestroyed(); void onScreenDestroyed(); + virtual void onPolished(); void onExposed(); protected: @@ -192,7 +177,6 @@ protected: ProxyWindowContentItem* mContentItem = nullptr; bool reloadComplete = false; bool ranLints = false; - bool mUpdatesEnabled = true; QsSurfaceFormat qsSurfaceFormat; QSurfaceFormat mSurfaceFormat; @@ -216,13 +200,6 @@ protected: &ProxyWindowBase::implicitHeightChanged ); - Q_OBJECT_BINDABLE_PROPERTY( - ProxyWindowBase, - bool, - bBackerVisibility, - &ProxyWindowBase::backerVisibilityChanged - ); - private: void polishItems(); void updateMask(); diff --git a/src/window/test/popupwindow.cpp b/src/window/test/popupwindow.cpp index f9498d2..1262044 100644 --- a/src/window/test/popupwindow.cpp +++ b/src/window/test/popupwindow.cpp @@ -13,7 +13,7 @@ void TestPopupWindow::initiallyVisible() { // NOLINT auto parent = ProxyWindowBase(); auto popup = ProxyPopupWindow(); - popup.anchor()->setWindow(&parent); + popup.setParentWindow(&parent); popup.setVisible(true); parent.reload(); @@ -33,7 +33,7 @@ void TestPopupWindow::reloadReparent() { // NOLINT win2->setVisible(true); parent.setVisible(true); - popup.anchor()->setWindow(&parent); + popup.setParentWindow(&parent); popup.setVisible(true); parent.reload(); @@ -43,7 +43,7 @@ void TestPopupWindow::reloadReparent() { // NOLINT auto newParent = ProxyWindowBase(); auto newPopup = ProxyPopupWindow(); - newPopup.anchor()->setWindow(&newParent); + newPopup.setParentWindow(&newParent); newPopup.setVisible(true); auto* oldWindow = popup.backingWindow(); @@ -66,7 +66,7 @@ void TestPopupWindow::reloadUnparent() { // NOLINT auto parent = ProxyWindowBase(); auto popup = ProxyPopupWindow(); - popup.anchor()->setWindow(&parent); + popup.setParentWindow(&parent); popup.setVisible(true); parent.reload(); @@ -80,7 +80,8 @@ void TestPopupWindow::reloadUnparent() { // NOLINT newPopup.reload(&popup); QVERIFY(!newPopup.isVisible()); - QVERIFY(!newPopup.backingWindow() || !newPopup.backingWindow()->isVisible()); + QVERIFY(!newPopup.backingWindow()->isVisible()); + QCOMPARE(newPopup.backingWindow()->transientParent(), nullptr); } void TestPopupWindow::invisibleWithoutParent() { // NOLINT @@ -96,11 +97,9 @@ void TestPopupWindow::moveWithParent() { // NOLINT auto parent = ProxyWindowBase(); auto popup = ProxyPopupWindow(); - popup.anchor()->setWindow(&parent); - auto rect = popup.anchor()->rect(); - rect.x = 10; - rect.y = 10; - popup.anchor()->setRect(rect); + popup.setParentWindow(&parent); + popup.setRelativeX(10); + popup.setRelativeY(10); popup.setVisible(true); parent.reload(); @@ -127,7 +126,7 @@ void TestPopupWindow::attachParentLate() { // NOLINT QVERIFY(!popup.isVisible()); - popup.anchor()->setWindow(&parent); + popup.setParentWindow(&parent); QVERIFY(popup.isVisible()); QVERIFY(popup.backingWindow()->isVisible()); QCOMPARE(popup.backingWindow()->transientParent(), parent.backingWindow()); @@ -137,7 +136,7 @@ void TestPopupWindow::reparentLate() { // NOLINT auto parent = ProxyWindowBase(); auto popup = ProxyPopupWindow(); - popup.anchor()->setWindow(&parent); + popup.setParentWindow(&parent); popup.setVisible(true); parent.reload(); @@ -152,7 +151,7 @@ void TestPopupWindow::reparentLate() { // NOLINT parent2.backingWindow()->setX(10); parent2.backingWindow()->setY(10); - popup.anchor()->setWindow(&parent2); + popup.setParentWindow(&parent2); QVERIFY(popup.isVisible()); QVERIFY(popup.backingWindow()->isVisible()); QCOMPARE(popup.backingWindow()->transientParent(), parent2.backingWindow()); @@ -164,7 +163,7 @@ void TestPopupWindow::xMigrationFix() { // NOLINT auto parent = ProxyWindowBase(); auto popup = ProxyPopupWindow(); - popup.anchor()->setWindow(&parent); + popup.setParentWindow(&parent); popup.setVisible(true); parent.reload(); diff --git a/src/window/windowinterface.cpp b/src/window/windowinterface.cpp index e41afc2..20057d6 100644 --- a/src/window/windowinterface.cpp +++ b/src/window/windowinterface.cpp @@ -2,12 +2,9 @@ #include #include -#include #include #include -#include "../core/qmlscreen.hpp" -#include "../core/region.hpp" #include "proxywindow.hpp" QPointF WindowInterface::itemPosition(QQuickItem* item) const { @@ -94,67 +91,6 @@ QsWindowAttached::mapFromItem(QQuickItem* item, qreal x, qreal y, qreal width, q } } -// clang-format off -QQuickItem* WindowInterface::contentItem() const { return this->proxyWindow()->contentItem(); } - -bool WindowInterface::isVisible() const { return this->proxyWindow()->isVisible(); }; -bool WindowInterface::isBackingWindowVisible() const { return this->proxyWindow()->isVisibleDirect(); }; -void WindowInterface::setVisible(bool visible) const { this->proxyWindow()->setVisible(visible); }; - -qint32 WindowInterface::implicitWidth() const { return this->proxyWindow()->implicitWidth(); }; -void WindowInterface::setImplicitWidth(qint32 implicitWidth) const { this->proxyWindow()->setImplicitWidth(implicitWidth); }; - -qint32 WindowInterface::implicitHeight() const { return this->proxyWindow()->implicitHeight(); }; -void WindowInterface::setImplicitHeight(qint32 implicitHeight) const { this->proxyWindow()->setImplicitHeight(implicitHeight); }; - -qint32 WindowInterface::width() const { return this->proxyWindow()->width(); }; -void WindowInterface::setWidth(qint32 width) const { this->proxyWindow()->setWidth(width); }; - -qint32 WindowInterface::height() const { return this->proxyWindow()->height(); }; -void WindowInterface::setHeight(qint32 height) const { this->proxyWindow()->setHeight(height); }; - -qreal WindowInterface::devicePixelRatio() const { return this->proxyWindow()->devicePixelRatio(); }; - -QuickshellScreenInfo* WindowInterface::screen() const { return this->proxyWindow()->screen(); }; -void WindowInterface::setScreen(QuickshellScreenInfo* screen) const { this->proxyWindow()->setScreen(screen); }; - -QColor WindowInterface::color() const { return this->proxyWindow()->color(); }; -void WindowInterface::setColor(QColor color) const { this->proxyWindow()->setColor(color); }; - -PendingRegion* WindowInterface::mask() const { return this->proxyWindow()->mask(); }; -void WindowInterface::setMask(PendingRegion* mask) const { this->proxyWindow()->setMask(mask); }; - -QsSurfaceFormat WindowInterface::surfaceFormat() const { return this->proxyWindow()->surfaceFormat(); }; -void WindowInterface::setSurfaceFormat(QsSurfaceFormat format) const { this->proxyWindow()->setSurfaceFormat(format); }; - -bool WindowInterface::updatesEnabled() const { return this->proxyWindow()->updatesEnabled(); }; -void WindowInterface::setUpdatesEnabled(bool updatesEnabled) const { this->proxyWindow()->setUpdatesEnabled(updatesEnabled); }; - -QQmlListProperty WindowInterface::data() const { return this->proxyWindow()->data(); }; -// clang-format on - -void WindowInterface::connectSignals() const { - auto* window = this->proxyWindow(); - // clang-format off - QObject::connect(window, &ProxyWindowBase::closed, this, &WindowInterface::closed); - QObject::connect(window, &ProxyWindowBase::resourcesLost, this, &WindowInterface::resourcesLost); - QObject::connect(window, &ProxyWindowBase::windowConnected, this, &WindowInterface::windowConnected); - QObject::connect(window, &ProxyWindowBase::visibleChanged, this, &WindowInterface::visibleChanged); - QObject::connect(window, &ProxyWindowBase::backerVisibilityChanged, this, &WindowInterface::backingWindowVisibleChanged); - QObject::connect(window, &ProxyWindowBase::implicitHeightChanged, this, &WindowInterface::implicitHeightChanged); - QObject::connect(window, &ProxyWindowBase::implicitWidthChanged, this, &WindowInterface::implicitWidthChanged); - QObject::connect(window, &ProxyWindowBase::heightChanged, this, &WindowInterface::heightChanged); - QObject::connect(window, &ProxyWindowBase::widthChanged, this, &WindowInterface::widthChanged); - QObject::connect(window, &ProxyWindowBase::devicePixelRatioChanged, this, &WindowInterface::devicePixelRatioChanged); - QObject::connect(window, &ProxyWindowBase::screenChanged, this, &WindowInterface::screenChanged); - QObject::connect(window, &ProxyWindowBase::windowTransformChanged, this, &WindowInterface::windowTransformChanged); - QObject::connect(window, &ProxyWindowBase::colorChanged, this, &WindowInterface::colorChanged); - QObject::connect(window, &ProxyWindowBase::maskChanged, this, &WindowInterface::maskChanged); - QObject::connect(window, &ProxyWindowBase::surfaceFormatChanged, this, &WindowInterface::surfaceFormatChanged); - QObject::connect(window, &ProxyWindowBase::updatesEnabledChanged, this, &WindowInterface::updatesEnabledChanged); - // clang-format on -} - QsWindowAttached* WindowInterface::qmlAttachedProperties(QObject* object) { while (object && !qobject_cast(object)) { object = object->parent(); diff --git a/src/window/windowinterface.hpp b/src/window/windowinterface.hpp index 6f3db20..b8edff2 100644 --- a/src/window/windowinterface.hpp +++ b/src/window/windowinterface.hpp @@ -143,12 +143,6 @@ class WindowInterface: public Reloadable { /// /// > [!NOTE] The surface format cannot be changed after the window is created. Q_PROPERTY(QsSurfaceFormat surfaceFormat READ surfaceFormat WRITE setSurfaceFormat NOTIFY surfaceFormatChanged); - /// If the window should receive render updates. Defaults to true. - /// - /// When set to false, the window will not re-render in response to animations - /// or other visual updates from other windows. This is useful for static windows - /// such as wallpapers that do not need to update frequently, saving GPU cycles. - Q_PROPERTY(bool updatesEnabled READ updatesEnabled WRITE setUpdatesEnabled NOTIFY updatesEnabledChanged); Q_PROPERTY(QQmlListProperty data READ data); // clang-format on Q_CLASSINFO("DefaultProperty", "data"); @@ -203,57 +197,45 @@ public: // clang-format on [[nodiscard]] virtual ProxyWindowBase* proxyWindow() const = 0; - [[nodiscard]] QQuickItem* contentItem() const; + [[nodiscard]] virtual QQuickItem* contentItem() const = 0; - [[nodiscard]] bool isVisible() const; - [[nodiscard]] bool isBackingWindowVisible() const; - void setVisible(bool visible) const; + [[nodiscard]] virtual bool isVisible() const = 0; + [[nodiscard]] virtual bool isBackingWindowVisible() const = 0; + virtual void setVisible(bool visible) = 0; - [[nodiscard]] qint32 implicitWidth() const; - void setImplicitWidth(qint32 implicitWidth) const; + [[nodiscard]] virtual qint32 implicitWidth() const = 0; + virtual void setImplicitWidth(qint32 implicitWidth) = 0; - [[nodiscard]] qint32 implicitHeight() const; - void setImplicitHeight(qint32 implicitHeight) const; + [[nodiscard]] virtual qint32 implicitHeight() const = 0; + virtual void setImplicitHeight(qint32 implicitHeight) = 0; - [[nodiscard]] qint32 width() const; - void setWidth(qint32 width) const; + [[nodiscard]] virtual qint32 width() const = 0; + virtual void setWidth(qint32 width) = 0; - [[nodiscard]] qint32 height() const; - void setHeight(qint32 height) const; + [[nodiscard]] virtual qint32 height() const = 0; + virtual void setHeight(qint32 height) = 0; - [[nodiscard]] qreal devicePixelRatio() const; + [[nodiscard]] virtual qreal devicePixelRatio() const = 0; - [[nodiscard]] QuickshellScreenInfo* screen() const; - void setScreen(QuickshellScreenInfo* screen) const; + [[nodiscard]] virtual QuickshellScreenInfo* screen() const = 0; + virtual void setScreen(QuickshellScreenInfo* screen) = 0; [[nodiscard]] QObject* windowTransform() const { return nullptr; } // NOLINT - [[nodiscard]] QColor color() const; - void setColor(QColor color) const; + [[nodiscard]] virtual QColor color() const = 0; + virtual void setColor(QColor color) = 0; - [[nodiscard]] PendingRegion* mask() const; - void setMask(PendingRegion* mask) const; + [[nodiscard]] virtual PendingRegion* mask() const = 0; + virtual void setMask(PendingRegion* mask) = 0; - [[nodiscard]] QsSurfaceFormat surfaceFormat() const; - void setSurfaceFormat(QsSurfaceFormat format) const; + [[nodiscard]] virtual QsSurfaceFormat surfaceFormat() const = 0; + virtual void setSurfaceFormat(QsSurfaceFormat format) = 0; - [[nodiscard]] bool updatesEnabled() const; - void setUpdatesEnabled(bool updatesEnabled) const; - - [[nodiscard]] QQmlListProperty data() const; + [[nodiscard]] virtual QQmlListProperty data() = 0; static QsWindowAttached* qmlAttachedProperties(QObject* object); signals: - /// This signal is emitted when the window is closed by the user, the display server, - /// or an error. It is not emitted when @@visible is set to false. - void closed(); - /// This signal is emitted when resources a window depends on to display are lost, - /// or could not be acquired during window creation. The most common trigger for - /// this signal is a lack of VRAM when creating or resizing a window. - /// - /// Following this signal, @@closed(s) will be sent. - void resourcesLost(); void windowConnected(); void visibleChanged(); void backingWindowVisibleChanged(); @@ -267,10 +249,6 @@ signals: void colorChanged(); void maskChanged(); void surfaceFormatChanged(); - void updatesEnabledChanged(); - -protected: - void connectSignals() const; }; class QsWindowAttached: public QObject { diff --git a/src/x11/i3/ipc/CMakeLists.txt b/src/x11/i3/ipc/CMakeLists.txt index c228ae3..27a4484 100644 --- a/src/x11/i3/ipc/CMakeLists.txt +++ b/src/x11/i3/ipc/CMakeLists.txt @@ -3,8 +3,6 @@ qt_add_library(quickshell-i3-ipc STATIC qml.cpp workspace.cpp monitor.cpp - controller.cpp - listener.cpp ) qt_add_qml_module(quickshell-i3-ipc diff --git a/src/x11/i3/ipc/connection.cpp b/src/x11/i3/ipc/connection.cpp index b765ebc..ba010ed 100644 --- a/src/x11/i3/ipc/connection.cpp +++ b/src/x11/i3/ipc/connection.cpp @@ -1,4 +1,4 @@ -#include "connection.hpp" +#include #include #include #include @@ -23,6 +23,11 @@ #include #include "../../../core/logcat.hpp" +#include "../../../core/model.hpp" +#include "../../../core/qmlscreen.hpp" +#include "connection.hpp" +#include "monitor.hpp" +#include "workspace.hpp" namespace qs::i3::ipc { @@ -31,69 +36,6 @@ QS_LOGGING_CATEGORY(logI3Ipc, "quickshell.I3.ipc", QtWarningMsg); QS_LOGGING_CATEGORY(logI3IpcEvents, "quickshell.I3.ipc.events", QtWarningMsg); } // namespace -QString I3IpcEvent::type() const { return I3IpcEvent::eventToString(this->mCode); } -QString I3IpcEvent::data() const { return QString::fromUtf8(this->mData.toJson()); } - -EventCode I3IpcEvent::intToEvent(quint32 raw) { - if ((EventCode::Workspace <= raw && raw <= EventCode::Input) - || (EventCode::RunCommand <= raw && raw <= EventCode::GetTree)) - { - return static_cast(raw); - } else { - return EventCode::Unknown; - } -} - -QString I3IpcEvent::eventToString(EventCode event) { - switch (event) { - case EventCode::RunCommand: return "run_command"; break; - case EventCode::GetWorkspaces: return "get_workspaces"; break; - case EventCode::Subscribe: return "subscribe"; break; - case EventCode::GetOutputs: return "get_outputs"; break; - case EventCode::GetTree: return "get_tree"; break; - - case EventCode::Output: return "output"; break; - case EventCode::Workspace: return "workspace"; break; - case EventCode::Mode: return "mode"; break; - case EventCode::Window: return "window"; break; - case EventCode::BarconfigUpdate: return "barconfig_update"; break; - case EventCode::Binding: return "binding"; break; - case EventCode::Shutdown: return "shutdown"; break; - case EventCode::Tick: return "tick"; break; - case EventCode::BarStateUpdate: return "bar_state_update"; break; - case EventCode::Input: return "input"; break; - - default: return "unknown"; break; - } -} - -I3Ipc::I3Ipc(const QList& events): mEvents(events) { - auto sock = qEnvironmentVariable("I3SOCK"); - - if (sock.isEmpty()) { - qCWarning(logI3Ipc) << "$I3SOCK is unset. Trying $SWAYSOCK."; - - sock = qEnvironmentVariable("SWAYSOCK"); - - if (sock.isEmpty()) { - qCWarning(logI3Ipc) << "$SWAYSOCK and I3SOCK are unset. Cannot connect to socket."; - return; - } - } - - this->mSocketPath = sock; - - // clang-format off - QObject::connect(&this->liveEventSocket, &QLocalSocket::errorOccurred, this, &I3Ipc::eventSocketError); - QObject::connect(&this->liveEventSocket, &QLocalSocket::stateChanged, this, &I3Ipc::eventSocketStateChanged); - QObject::connect(&this->liveEventSocket, &QLocalSocket::readyRead, this, &I3Ipc::eventSocketReady); - QObject::connect(&this->liveEventSocket, &QLocalSocket::connected, this, &I3Ipc::subscribe); - // clang-format on - - this->liveEventSocketDs.setDevice(&this->liveEventSocket); - this->liveEventSocketDs.setByteOrder(static_cast(QSysInfo::ByteOrder)); -} - void I3Ipc::makeRequest(const QByteArray& request) { if (!this->valid) { qCWarning(logI3IpcEvents) << "IPC connection is not open, ignoring request."; @@ -118,13 +60,50 @@ QByteArray I3Ipc::buildRequestMessage(EventCode cmd, const QByteArray& payload) return MAGIC.data() + len + type + payload; } +I3Ipc::I3Ipc() { + auto sock = qEnvironmentVariable("I3SOCK"); + + if (sock.isEmpty()) { + qCWarning(logI3Ipc) << "$I3SOCK is unset. Trying $SWAYSOCK."; + + sock = qEnvironmentVariable("SWAYSOCK"); + + if (sock.isEmpty()) { + qCWarning(logI3Ipc) << "$SWAYSOCK and I3SOCK are unset. Cannot connect to socket."; + return; + } + } + + this->bFocusedWorkspace.setBinding([this]() -> I3Workspace* { + if (!this->bFocusedMonitor) return nullptr; + return this->bFocusedMonitor->bindableActiveWorkspace().value(); + }); + + this->mSocketPath = sock; + + // clang-format off + QObject::connect(&this->liveEventSocket, &QLocalSocket::errorOccurred, this, &I3Ipc::eventSocketError); + QObject::connect(&this->liveEventSocket, &QLocalSocket::stateChanged, this, &I3Ipc::eventSocketStateChanged); + QObject::connect(&this->liveEventSocket, &QLocalSocket::readyRead, this, &I3Ipc::eventSocketReady); + QObject::connect(&this->liveEventSocket, &QLocalSocket::connected, this, &I3Ipc::subscribe); + // clang-format on + + this->liveEventSocketDs.setDevice(&this->liveEventSocket); + this->liveEventSocketDs.setByteOrder(static_cast(QSysInfo::ByteOrder)); + + this->liveEventSocket.connectToServer(this->mSocketPath); +} + void I3Ipc::subscribe() { - auto jsonArray = QJsonArray::fromStringList(this->mEvents); - auto jsonDoc = QJsonDocument(jsonArray); - auto payload = jsonDoc.toJson(QJsonDocument::Compact); + auto payload = QByteArray(R"(["workspace","output"])"); auto message = I3Ipc::buildRequestMessage(EventCode::Subscribe, payload); this->makeRequest(message); + + // Workspaces must be refreshed before monitors or no focus will be + // detected on launch. + this->refreshWorkspaces(); + this->refreshMonitors(); } void I3Ipc::eventSocketReady() { @@ -132,16 +111,15 @@ void I3Ipc::eventSocketReady() { this->event.mCode = type; this->event.mData = data; + this->onEvent(&this->event); emit this->rawEvent(&this->event); } } -void I3Ipc::connect() { this->liveEventSocket.connectToServer(this->mSocketPath); } - void I3Ipc::reconnectIPC() { qCWarning(logI3Ipc) << "Fatal IPC error occured, recreating connection"; this->liveEventSocket.disconnectFromServer(); - this->connect(); + this->liveEventSocket.connectToServer(this->mSocketPath); } QVector I3Ipc::parseResponse() { @@ -215,4 +193,347 @@ void I3Ipc::eventSocketStateChanged(QLocalSocket::LocalSocketState state) { QString I3Ipc::socketPath() const { return this->mSocketPath; } +void I3Ipc::setFocusedMonitor(I3Monitor* monitor) { + auto* oldMonitor = this->bFocusedMonitor.value(); + if (monitor == oldMonitor) return; + + if (oldMonitor != nullptr) { + QObject::disconnect(oldMonitor, nullptr, this, nullptr); + } + + if (monitor != nullptr) { + QObject::connect(monitor, &QObject::destroyed, this, &I3Ipc::onFocusedMonitorDestroyed); + } + + this->bFocusedMonitor = monitor; +} + +void I3Ipc::onFocusedMonitorDestroyed() { this->bFocusedMonitor = nullptr; } + +I3Ipc* I3Ipc::instance() { + static I3Ipc* instance = nullptr; // NOLINT + + if (instance == nullptr) { + instance = new I3Ipc(); + } + + return instance; +} + +void I3Ipc::refreshWorkspaces() { + this->makeRequest(I3Ipc::buildRequestMessage(EventCode::GetWorkspaces)); +} + +void I3Ipc::handleGetWorkspacesEvent(I3IpcEvent* event) { + auto data = event->mData; + + auto workspaces = data.array(); + + const auto& mList = this->mWorkspaces.valueList(); + auto names = QVector(); + + qCDebug(logI3Ipc) << "There are" << workspaces.toVariantList().length() << "workspaces"; + for (auto entry: workspaces) { + auto object = entry.toObject().toVariantMap(); + auto name = object["name"].toString(); + + auto workspaceIter = std::ranges::find_if(mList, [name](I3Workspace* m) { + return m->bindableName().value() == name; + }); + + auto* workspace = workspaceIter == mList.end() ? nullptr : *workspaceIter; + auto existed = workspace != nullptr; + + if (workspace == nullptr) { + workspace = new I3Workspace(this); + } + + workspace->updateFromObject(object); + + if (!existed) { + this->mWorkspaces.insertObjectSorted(workspace, &I3Ipc::compareWorkspaces); + } + + if (!this->bFocusedWorkspace && object.value("focused").value()) { + this->bFocusedMonitor = workspace->bindableMonitor().value(); + } + + names.push_back(name); + } + + auto removedWorkspaces = QVector(); + + for (auto* workspace: mList) { + if (!names.contains(workspace->bindableName().value())) { + removedWorkspaces.push_back(workspace); + } + } + + qCDebug(logI3Ipc) << "Removing" << removedWorkspaces.length() << "deleted workspaces."; + + for (auto* workspace: removedWorkspaces) { + this->mWorkspaces.removeObject(workspace); + delete workspace; + } +} + +void I3Ipc::refreshMonitors() { + this->makeRequest(I3Ipc::buildRequestMessage(EventCode::GetOutputs)); +} + +void I3Ipc::handleGetOutputsEvent(I3IpcEvent* event) { + auto data = event->mData; + + auto monitors = data.array(); + const auto& mList = this->mMonitors.valueList(); + auto names = QVector(); + + qCDebug(logI3Ipc) << "There are" << monitors.toVariantList().length() << "monitors"; + + for (auto elem: monitors) { + auto object = elem.toObject().toVariantMap(); + auto name = object["name"].toString(); + + auto monitorIter = std::ranges::find_if(mList, [name](I3Monitor* m) { + return m->bindableName().value() == name; + }); + + auto* monitor = monitorIter == mList.end() ? nullptr : *monitorIter; + auto existed = monitor != nullptr; + + if (monitor == nullptr) { + monitor = new I3Monitor(this); + } + + monitor->updateFromObject(object); + + if (monitor->bindableFocused().value()) { + this->setFocusedMonitor(monitor); + } + + if (!existed) { + this->mMonitors.insertObject(monitor); + } + + names.push_back(name); + } + + auto removedMonitors = QVector(); + + for (auto* monitor: mList) { + if (!names.contains(monitor->bindableName().value())) { + removedMonitors.push_back(monitor); + } + } + + qCDebug(logI3Ipc) << "Removing" << removedMonitors.length() << "disconnected monitors."; + + for (auto* monitor: removedMonitors) { + this->mMonitors.removeObject(monitor); + delete monitor; + } +} + +void I3Ipc::onEvent(I3IpcEvent* event) { + switch (event->mCode) { + case EventCode::Workspace: this->handleWorkspaceEvent(event); return; + case EventCode::Output: + /// I3 only sends an "unspecified" event, so we have to query the data changes ourselves + qCInfo(logI3Ipc) << "Refreshing Monitors..."; + this->refreshMonitors(); + return; + case EventCode::Subscribe: qCInfo(logI3Ipc) << "Connected to IPC"; return; + case EventCode::GetOutputs: this->handleGetOutputsEvent(event); return; + case EventCode::GetWorkspaces: this->handleGetWorkspacesEvent(event); return; + case EventCode::RunCommand: I3Ipc::handleRunCommand(event); return; + case EventCode::Unknown: + qCWarning(logI3Ipc) << "Unknown event:" << event->type() << event->data(); + return; + default: qCWarning(logI3Ipc) << "Unhandled event:" << event->type(); + } +} + +void I3Ipc::handleRunCommand(I3IpcEvent* event) { + for (auto r: event->mData.array()) { + auto obj = r.toObject(); + const bool success = obj["success"].toBool(); + + if (!success) { + const QString error = obj["error"].toString(); + qCWarning(logI3Ipc) << "Error occured while running command:" << error; + } + } +} + +void I3Ipc::handleWorkspaceEvent(I3IpcEvent* event) { + // If a workspace doesn't exist, and is being switch to, no focus change event is emited, + // only the init one, which does not contain the previously focused workspace + auto change = event->mData["change"]; + + if (change == "init") { + qCInfo(logI3IpcEvents) << "New workspace has been created"; + + auto workspaceData = event->mData["current"]; + + auto* workspace = this->findWorkspaceByID(workspaceData["id"].toInt(-1)); + auto existed = workspace != nullptr; + + if (!existed) { + workspace = new I3Workspace(this); + } + + if (workspaceData.isObject()) { + workspace->updateFromObject(workspaceData.toObject().toVariantMap()); + } + + if (!existed) { + this->mWorkspaces.insertObjectSorted(workspace, &I3Ipc::compareWorkspaces); + qCInfo(logI3Ipc) << "Added workspace" << workspace->bindableName().value() << "to list"; + } + } else if (change == "focus") { + auto oldData = event->mData["old"]; + auto newData = event->mData["current"]; + auto oldName = oldData["name"].toString(); + auto newName = newData["name"].toString(); + + qCInfo(logI3IpcEvents) << "Focus changed: " << oldName << "->" << newName; + + if (auto* oldWorkspace = this->findWorkspaceByName(oldName)) { + oldWorkspace->updateFromObject(oldData.toObject().toVariantMap()); + } + + auto* newWorkspace = this->findWorkspaceByName(newName); + + if (newWorkspace == nullptr) { + newWorkspace = new I3Workspace(this); + } + + newWorkspace->updateFromObject(newData.toObject().toVariantMap()); + + if (newWorkspace->bindableMonitor().value()) { + auto* monitor = newWorkspace->bindableMonitor().value(); + monitor->setFocusedWorkspace(newWorkspace); + this->bFocusedMonitor = monitor; + } + } else if (change == "empty") { + auto name = event->mData["current"]["name"].toString(); + + auto* oldWorkspace = this->findWorkspaceByName(name); + + if (oldWorkspace != nullptr) { + qCInfo(logI3Ipc) << "Deleting" << oldWorkspace->bindableId().value() << name; + + if (this->bFocusedWorkspace == oldWorkspace) { + this->bFocusedMonitor->setFocusedWorkspace(nullptr); + } + + this->workspaces()->removeObject(oldWorkspace); + + delete oldWorkspace; + } else { + qCInfo(logI3Ipc) << "Workspace" << name << "has already been deleted"; + } + } else if (change == "move" || change == "rename" || change == "urgent") { + auto name = event->mData["current"]["name"].toString(); + + auto* workspace = this->findWorkspaceByName(name); + + if (workspace != nullptr) { + auto data = event->mData["current"].toObject().toVariantMap(); + + workspace->updateFromObject(data); + } else { + qCWarning(logI3Ipc) << "Workspace" << name << "doesn't exist"; + } + } else if (change == "reload") { + qCInfo(logI3Ipc) << "Refreshing Workspaces..."; + this->refreshWorkspaces(); + } +} + +I3Monitor* I3Ipc::monitorFor(QuickshellScreenInfo* screen) { + if (screen == nullptr) return nullptr; + + return this->findMonitorByName(screen->name()); +} + +I3Workspace* I3Ipc::findWorkspaceByID(qint32 id) { + auto list = this->mWorkspaces.valueList(); + auto workspaceIter = + std::ranges::find_if(list, [id](I3Workspace* m) { return m->bindableId().value() == id; }); + + return workspaceIter == list.end() ? nullptr : *workspaceIter; +} + +I3Workspace* I3Ipc::findWorkspaceByName(const QString& name) { + auto list = this->mWorkspaces.valueList(); + auto workspaceIter = std::ranges::find_if(list, [name](I3Workspace* m) { + return m->bindableName().value() == name; + }); + + return workspaceIter == list.end() ? nullptr : *workspaceIter; +} + +I3Monitor* I3Ipc::findMonitorByName(const QString& name, bool createIfMissing) { + auto list = this->mMonitors.valueList(); + auto monitorIter = std::ranges::find_if(list, [name](I3Monitor* m) { + return m->bindableName().value() == name; + }); + + if (monitorIter != list.end()) { + return *monitorIter; + } else if (createIfMissing) { + qCDebug(logI3Ipc) << "Monitor" << name << "requested before creation, performing early init"; + auto* monitor = new I3Monitor(this); + monitor->updateInitial(name); + this->mMonitors.insertObject(monitor); + return monitor; + } else { + return nullptr; + } +} + +ObjectModel* I3Ipc::monitors() { return &this->mMonitors; } +ObjectModel* I3Ipc::workspaces() { return &this->mWorkspaces; } + +bool I3Ipc::compareWorkspaces(I3Workspace* a, I3Workspace* b) { + return a->bindableNumber().value() > b->bindableNumber().value(); +} + +QString I3IpcEvent::type() const { return I3IpcEvent::eventToString(this->mCode); } +QString I3IpcEvent::data() const { return QString::fromUtf8(this->mData.toJson()); } + +EventCode I3IpcEvent::intToEvent(quint32 raw) { + if ((EventCode::Workspace <= raw && raw <= EventCode::Input) + || (EventCode::RunCommand <= raw && raw <= EventCode::GetTree)) + { + return static_cast(raw); + } else { + return EventCode::Unknown; + } +} + +QString I3IpcEvent::eventToString(EventCode event) { + switch (event) { + case EventCode::RunCommand: return "run_command"; break; + case EventCode::GetWorkspaces: return "get_workspaces"; break; + case EventCode::Subscribe: return "subscribe"; break; + case EventCode::GetOutputs: return "get_outputs"; break; + case EventCode::GetTree: return "get_tree"; break; + + case EventCode::Output: return "output"; break; + case EventCode::Workspace: return "workspace"; break; + case EventCode::Mode: return "mode"; break; + case EventCode::Window: return "window"; break; + case EventCode::BarconfigUpdate: return "barconfig_update"; break; + case EventCode::Binding: return "binding"; break; + case EventCode::Shutdown: return "shutdown"; break; + case EventCode::Tick: return "tick"; break; + case EventCode::BarStateUpdate: return "bar_state_update"; break; + case EventCode::Input: return "input"; break; + + case EventCode::Unknown: return "unknown"; break; + } +} + } // namespace qs::i3::ipc diff --git a/src/x11/i3/ipc/connection.hpp b/src/x11/i3/ipc/connection.hpp index 6100f7e..af480c5 100644 --- a/src/x11/i3/ipc/connection.hpp +++ b/src/x11/i3/ipc/connection.hpp @@ -1,14 +1,28 @@ #pragma once #include -#include #include +#include #include #include +#include +#include #include #include #include +#include "../../../core/model.hpp" +#include "../../../core/qmlscreen.hpp" + +namespace qs::i3::ipc { + +class I3Workspace; +class I3Monitor; +} // namespace qs::i3::ipc + +Q_DECLARE_OPAQUE_POINTER(qs::i3::ipc::I3Workspace*); +Q_DECLARE_OPAQUE_POINTER(qs::i3::ipc::I3Monitor*); + namespace qs::i3::ipc { constexpr std::string MAGIC = "i3-ipc"; @@ -40,7 +54,9 @@ using Event = std::tuple; class I3IpcEvent: public QObject { Q_OBJECT; + /// The name of the event Q_PROPERTY(QString type READ type CONSTANT); + /// The payload of the event in JSON format. Q_PROPERTY(QString data READ data CONSTANT); QML_NAMED_ELEMENT(I3Event); @@ -59,48 +75,90 @@ public: static QString eventToString(EventCode event); }; -/// Base class that manages the IPC socket, subscriptions and event reception. class I3Ipc: public QObject { Q_OBJECT; public: - explicit I3Ipc(const QList& events); + static I3Ipc* instance(); [[nodiscard]] QString socketPath() const; void makeRequest(const QByteArray& request); void dispatch(const QString& payload); - void connect(); - [[nodiscard]] QByteArray static buildRequestMessage( - EventCode cmd, - const QByteArray& payload = QByteArray() - ); + static QByteArray buildRequestMessage(EventCode cmd, const QByteArray& payload = QByteArray()); + + I3Workspace* findWorkspaceByName(const QString& name); + I3Monitor* findMonitorByName(const QString& name, bool createIfMissing = false); + I3Workspace* findWorkspaceByID(qint32 id); + + void setFocusedMonitor(I3Monitor* monitor); + + void refreshWorkspaces(); + void refreshMonitors(); + + I3Monitor* monitorFor(QuickshellScreenInfo* screen); + + [[nodiscard]] QBindable bindableFocusedMonitor() const { + return &this->bFocusedMonitor; + }; + + [[nodiscard]] QBindable bindableFocusedWorkspace() const { + return &this->bFocusedWorkspace; + }; + + [[nodiscard]] ObjectModel* monitors(); + [[nodiscard]] ObjectModel* workspaces(); signals: void connected(); void rawEvent(I3IpcEvent* event); + void focusedWorkspaceChanged(); + void focusedMonitorChanged(); -protected slots: +private slots: void eventSocketError(QLocalSocket::LocalSocketError error) const; void eventSocketStateChanged(QLocalSocket::LocalSocketState state); void eventSocketReady(); void subscribe(); -protected: + void onFocusedMonitorDestroyed(); + +private: + explicit I3Ipc(); + + void onEvent(I3IpcEvent* event); + + void handleWorkspaceEvent(I3IpcEvent* event); + void handleGetWorkspacesEvent(I3IpcEvent* event); + void handleGetOutputsEvent(I3IpcEvent* event); + static void handleRunCommand(I3IpcEvent* event); + static bool compareWorkspaces(I3Workspace* a, I3Workspace* b); + void reconnectIPC(); + QVector> parseResponse(); QLocalSocket liveEventSocket; QDataStream liveEventSocketDs; QString mSocketPath; + bool valid = false; + ObjectModel mMonitors {this}; + ObjectModel mWorkspaces {this}; + I3IpcEvent event {this}; -private: - QList mEvents; + Q_OBJECT_BINDABLE_PROPERTY(I3Ipc, I3Monitor*, bFocusedMonitor, &I3Ipc::focusedMonitorChanged); + + Q_OBJECT_BINDABLE_PROPERTY( + I3Ipc, + I3Workspace*, + bFocusedWorkspace, + &I3Ipc::focusedWorkspaceChanged + ); }; } // namespace qs::i3::ipc diff --git a/src/x11/i3/ipc/controller.cpp b/src/x11/i3/ipc/controller.cpp deleted file mode 100644 index 1a08c63..0000000 --- a/src/x11/i3/ipc/controller.cpp +++ /dev/null @@ -1,367 +0,0 @@ -#include "controller.hpp" -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../../core/logcat.hpp" -#include "../../../core/model.hpp" -#include "../../../core/qmlscreen.hpp" -#include "connection.hpp" -#include "monitor.hpp" -#include "workspace.hpp" - -namespace qs::i3::ipc { - -namespace { -QS_LOGGING_CATEGORY(logI3Ipc, "quickshell.I3.ipc", QtWarningMsg); -QS_LOGGING_CATEGORY(logI3IpcEvents, "quickshell.I3.ipc.events", QtWarningMsg); -} // namespace - -I3IpcController::I3IpcController(): I3Ipc({"workspace", "output"}) { - // bind focused workspace to focused monitor's active workspace - this->bFocusedWorkspace.setBinding([this]() -> I3Workspace* { - if (!this->bFocusedMonitor) return nullptr; - return this->bFocusedMonitor->bindableActiveWorkspace().value(); - }); - - // clang-format off - QObject::connect(this, &I3Ipc::rawEvent, this, &I3IpcController::onEvent); - QObject::connect(&this->liveEventSocket, &QLocalSocket::connected, this, &I3IpcController::onConnected); - // clang-format on -} - -void I3IpcController::onConnected() { - // Workspaces must be refreshed before monitors or no focus will be - // detected on launch. - this->refreshWorkspaces(); - this->refreshMonitors(); -} - -void I3IpcController::setFocusedMonitor(I3Monitor* monitor) { - auto* oldMonitor = this->bFocusedMonitor.value(); - if (monitor == oldMonitor) return; - - if (oldMonitor != nullptr) { - QObject::disconnect(oldMonitor, nullptr, this, nullptr); - } - - if (monitor != nullptr) { - QObject::connect( - monitor, - &QObject::destroyed, - this, - &I3IpcController::onFocusedMonitorDestroyed - ); - } - - this->bFocusedMonitor = monitor; -} - -void I3IpcController::onFocusedMonitorDestroyed() { this->bFocusedMonitor = nullptr; } - -I3IpcController* I3IpcController::instance() { - static I3IpcController* instance = nullptr; // NOLINT - - if (instance == nullptr) { - instance = new I3IpcController(); - instance->connect(); - } - - return instance; -} - -void I3IpcController::refreshWorkspaces() { - this->makeRequest(I3Ipc::buildRequestMessage(EventCode::GetWorkspaces)); -} - -void I3IpcController::handleGetWorkspacesEvent(I3IpcEvent* event) { - auto data = event->mData; - - auto workspaces = data.array(); - - const auto& mList = this->mWorkspaces.valueList(); - auto names = QVector(); - - qCDebug(logI3Ipc) << "There are" << workspaces.toVariantList().length() << "workspaces"; - for (auto entry: workspaces) { - auto object = entry.toObject().toVariantMap(); - auto name = object["name"].toString(); - - auto workspaceIter = std::ranges::find_if(mList, [name](I3Workspace* m) { - return m->bindableName().value() == name; - }); - - auto* workspace = workspaceIter == mList.end() ? nullptr : *workspaceIter; - auto existed = workspace != nullptr; - - if (workspace == nullptr) { - workspace = new I3Workspace(this); - } - - workspace->updateFromObject(object); - - if (!existed) { - this->mWorkspaces.insertObjectSorted(workspace, &I3IpcController::compareWorkspaces); - } - - if (!this->bFocusedWorkspace && object.value("focused").value()) { - this->bFocusedMonitor = workspace->bindableMonitor().value(); - } - - names.push_back(name); - } - - auto removedWorkspaces = QVector(); - - for (auto* workspace: mList) { - if (!names.contains(workspace->bindableName().value())) { - removedWorkspaces.push_back(workspace); - } - } - - qCDebug(logI3Ipc) << "Removing" << removedWorkspaces.length() << "deleted workspaces."; - - for (auto* workspace: removedWorkspaces) { - this->mWorkspaces.removeObject(workspace); - delete workspace; - } -} - -void I3IpcController::refreshMonitors() { - this->makeRequest(I3Ipc::buildRequestMessage(EventCode::GetOutputs)); -} - -void I3IpcController::handleGetOutputsEvent(I3IpcEvent* event) { - auto data = event->mData; - - auto monitors = data.array(); - const auto& mList = this->mMonitors.valueList(); - auto names = QVector(); - - qCDebug(logI3Ipc) << "There are" << monitors.toVariantList().length() << "monitors"; - - for (auto elem: monitors) { - auto object = elem.toObject().toVariantMap(); - auto name = object["name"].toString(); - - auto monitorIter = std::ranges::find_if(mList, [name](I3Monitor* m) { - return m->bindableName().value() == name; - }); - - auto* monitor = monitorIter == mList.end() ? nullptr : *monitorIter; - auto existed = monitor != nullptr; - - if (monitor == nullptr) { - monitor = new I3Monitor(this); - } - - monitor->updateFromObject(object); - - if (monitor->bindableFocused().value()) { - this->setFocusedMonitor(monitor); - } - - if (!existed) { - this->mMonitors.insertObject(monitor); - } - - names.push_back(name); - } - - auto removedMonitors = QVector(); - - for (auto* monitor: mList) { - if (!names.contains(monitor->bindableName().value())) { - removedMonitors.push_back(monitor); - } - } - - qCDebug(logI3Ipc) << "Removing" << removedMonitors.length() << "disconnected monitors."; - - for (auto* monitor: removedMonitors) { - this->mMonitors.removeObject(monitor); - delete monitor; - } -} - -void I3IpcController::onEvent(I3IpcEvent* event) { - switch (event->mCode) { - case EventCode::Workspace: this->handleWorkspaceEvent(event); return; - case EventCode::Output: - /// I3 only sends an "unspecified" event, so we have to query the data changes ourselves - qCInfo(logI3Ipc) << "Refreshing Monitors..."; - this->refreshMonitors(); - return; - case EventCode::Subscribe: qCInfo(logI3Ipc) << "Connected to IPC"; return; - case EventCode::GetOutputs: this->handleGetOutputsEvent(event); return; - case EventCode::GetWorkspaces: this->handleGetWorkspacesEvent(event); return; - case EventCode::RunCommand: I3IpcController::handleRunCommand(event); return; - case EventCode::Unknown: - qCWarning(logI3Ipc) << "Unknown event:" << event->type() << event->data(); - return; - default: qCWarning(logI3Ipc) << "Unhandled event:" << event->type(); - } -} - -void I3IpcController::handleRunCommand(I3IpcEvent* event) { - for (auto r: event->mData.array()) { - auto obj = r.toObject(); - const bool success = obj["success"].toBool(); - - if (!success) { - const QString error = obj["error"].toString(); - qCWarning(logI3Ipc) << "Error occured while running command:" << error; - } - } -} - -void I3IpcController::handleWorkspaceEvent(I3IpcEvent* event) { - // If a workspace doesn't exist, and is being switch to, no focus change event is emited, - // only the init one, which does not contain the previously focused workspace - auto change = event->mData["change"]; - - if (change == "init") { - qCInfo(logI3IpcEvents) << "New workspace has been created"; - - auto workspaceData = event->mData["current"]; - - auto* workspace = this->findWorkspaceByID(workspaceData["id"].toInt(-1)); - auto existed = workspace != nullptr; - - if (!existed) { - workspace = new I3Workspace(this); - } - - if (workspaceData.isObject()) { - workspace->updateFromObject(workspaceData.toObject().toVariantMap()); - } - - if (!existed) { - this->mWorkspaces.insertObjectSorted(workspace, &I3IpcController::compareWorkspaces); - qCInfo(logI3Ipc) << "Added workspace" << workspace->bindableName().value() << "to list"; - } - } else if (change == "focus") { - auto oldData = event->mData["old"]; - auto newData = event->mData["current"]; - auto oldName = oldData["name"].toString(); - auto newName = newData["name"].toString(); - - qCInfo(logI3IpcEvents) << "Focus changed: " << oldName << "->" << newName; - - if (auto* oldWorkspace = this->findWorkspaceByName(oldName)) { - oldWorkspace->updateFromObject(oldData.toObject().toVariantMap()); - } - - auto* newWorkspace = this->findWorkspaceByName(newName); - - if (newWorkspace == nullptr) { - newWorkspace = new I3Workspace(this); - } - - newWorkspace->updateFromObject(newData.toObject().toVariantMap()); - - if (newWorkspace->bindableMonitor().value()) { - auto* monitor = newWorkspace->bindableMonitor().value(); - monitor->setFocusedWorkspace(newWorkspace); - this->bFocusedMonitor = monitor; - } - } else if (change == "empty") { - auto name = event->mData["current"]["name"].toString(); - - auto* oldWorkspace = this->findWorkspaceByName(name); - - if (oldWorkspace != nullptr) { - qCInfo(logI3Ipc) << "Deleting" << oldWorkspace->bindableId().value() << name; - - if (this->bFocusedWorkspace == oldWorkspace) { - this->bFocusedMonitor->setFocusedWorkspace(nullptr); - } - - this->workspaces()->removeObject(oldWorkspace); - - delete oldWorkspace; - } else { - qCInfo(logI3Ipc) << "Workspace" << name << "has already been deleted"; - } - } else if (change == "move" || change == "rename" || change == "urgent") { - auto name = event->mData["current"]["name"].toString(); - - auto* workspace = this->findWorkspaceByName(name); - - if (workspace != nullptr) { - auto data = event->mData["current"].toObject().toVariantMap(); - - workspace->updateFromObject(data); - } else { - qCWarning(logI3Ipc) << "Workspace" << name << "doesn't exist"; - } - } else if (change == "reload") { - qCInfo(logI3Ipc) << "Refreshing Workspaces..."; - this->refreshWorkspaces(); - } -} - -I3Monitor* I3IpcController::monitorFor(QuickshellScreenInfo* screen) { - if (screen == nullptr) return nullptr; - - return this->findMonitorByName(screen->name()); -} - -I3Workspace* I3IpcController::findWorkspaceByID(qint32 id) { - auto list = this->mWorkspaces.valueList(); - auto workspaceIter = - std::ranges::find_if(list, [id](I3Workspace* m) { return m->bindableId().value() == id; }); - - return workspaceIter == list.end() ? nullptr : *workspaceIter; -} - -I3Workspace* I3IpcController::findWorkspaceByName(const QString& name) { - auto list = this->mWorkspaces.valueList(); - auto workspaceIter = std::ranges::find_if(list, [name](I3Workspace* m) { - return m->bindableName().value() == name; - }); - - return workspaceIter == list.end() ? nullptr : *workspaceIter; -} - -I3Monitor* I3IpcController::findMonitorByName(const QString& name, bool createIfMissing) { - auto list = this->mMonitors.valueList(); - auto monitorIter = std::ranges::find_if(list, [name](I3Monitor* m) { - return m->bindableName().value() == name; - }); - - if (monitorIter != list.end()) { - return *monitorIter; - } else if (createIfMissing) { - qCDebug(logI3Ipc) << "Monitor" << name << "requested before creation, performing early init"; - auto* monitor = new I3Monitor(this); - monitor->updateInitial(name); - this->mMonitors.insertObject(monitor); - return monitor; - } else { - return nullptr; - } -} - -ObjectModel* I3IpcController::monitors() { return &this->mMonitors; } -ObjectModel* I3IpcController::workspaces() { return &this->mWorkspaces; } - -bool I3IpcController::compareWorkspaces(I3Workspace* a, I3Workspace* b) { - return a->bindableNumber().value() > b->bindableNumber().value(); -} - -} // namespace qs::i3::ipc diff --git a/src/x11/i3/ipc/controller.hpp b/src/x11/i3/ipc/controller.hpp deleted file mode 100644 index 464f6f6..0000000 --- a/src/x11/i3/ipc/controller.hpp +++ /dev/null @@ -1,94 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../../core/model.hpp" -#include "../../../core/qmlscreen.hpp" -#include "connection.hpp" - -namespace qs::i3::ipc { - -class I3Workspace; -class I3Monitor; -} // namespace qs::i3::ipc - -Q_DECLARE_OPAQUE_POINTER(qs::i3::ipc::I3Workspace*); -Q_DECLARE_OPAQUE_POINTER(qs::i3::ipc::I3Monitor*); - -namespace qs::i3::ipc { - -/// I3/Sway IPC controller that manages workspaces and monitors -class I3IpcController: public I3Ipc { - Q_OBJECT; - -public: - static I3IpcController* instance(); - - I3Workspace* findWorkspaceByName(const QString& name); - I3Monitor* findMonitorByName(const QString& name, bool createIfMissing = false); - I3Workspace* findWorkspaceByID(qint32 id); - - void setFocusedMonitor(I3Monitor* monitor); - - void refreshWorkspaces(); - void refreshMonitors(); - - I3Monitor* monitorFor(QuickshellScreenInfo* screen); - - [[nodiscard]] QBindable bindableFocusedMonitor() const { - return &this->bFocusedMonitor; - }; - - [[nodiscard]] QBindable bindableFocusedWorkspace() const { - return &this->bFocusedWorkspace; - }; - - [[nodiscard]] ObjectModel* monitors(); - [[nodiscard]] ObjectModel* workspaces(); - -signals: - void focusedWorkspaceChanged(); - void focusedMonitorChanged(); - -private slots: - void onFocusedMonitorDestroyed(); - - void onEvent(I3IpcEvent* event); - void onConnected(); - -private: - explicit I3IpcController(); - - void handleWorkspaceEvent(I3IpcEvent* event); - void handleGetWorkspacesEvent(I3IpcEvent* event); - void handleGetOutputsEvent(I3IpcEvent* event); - static void handleRunCommand(I3IpcEvent* event); - static bool compareWorkspaces(I3Workspace* a, I3Workspace* b); - - ObjectModel mMonitors {this}; - ObjectModel mWorkspaces {this}; - - Q_OBJECT_BINDABLE_PROPERTY( - I3IpcController, - I3Monitor*, - bFocusedMonitor, - &I3IpcController::focusedMonitorChanged - ); - - Q_OBJECT_BINDABLE_PROPERTY( - I3IpcController, - I3Workspace*, - bFocusedWorkspace, - &I3IpcController::focusedWorkspaceChanged - ); -}; - -} // namespace qs::i3::ipc diff --git a/src/x11/i3/ipc/listener.cpp b/src/x11/i3/ipc/listener.cpp deleted file mode 100644 index aa7719c..0000000 --- a/src/x11/i3/ipc/listener.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "listener.hpp" -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "connection.hpp" - -namespace qs::i3::ipc { - -I3IpcListener::~I3IpcListener() { this->freeI3Ipc(); } - -void I3IpcListener::onPostReload() { this->startListening(); } - -QList I3IpcListener::subscriptions() const { return this->mSubscriptions; } -void I3IpcListener::setSubscriptions(QList subscriptions) { - if (this->mSubscriptions == subscriptions) return; - this->mSubscriptions = std::move(subscriptions); - - emit this->subscriptionsChanged(); - this->startListening(); -} - -void I3IpcListener::startListening() { - this->freeI3Ipc(); - if (this->mSubscriptions.isEmpty()) return; - - this->i3Ipc = new I3Ipc(this->mSubscriptions); - - // clang-format off - QObject::connect(this->i3Ipc, &I3Ipc::rawEvent, this, &I3IpcListener::receiveEvent); - // clang-format on - - this->i3Ipc->connect(); -} - -void I3IpcListener::receiveEvent(I3IpcEvent* event) { emit this->ipcEvent(event); } - -void I3IpcListener::freeI3Ipc() { - delete this->i3Ipc; - this->i3Ipc = nullptr; -} - -} // namespace qs::i3::ipc diff --git a/src/x11/i3/ipc/listener.hpp b/src/x11/i3/ipc/listener.hpp deleted file mode 100644 index 9cb40bb..0000000 --- a/src/x11/i3/ipc/listener.hpp +++ /dev/null @@ -1,68 +0,0 @@ -#pragma once - -#include // NOLINT -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../../../core/doc.hpp" -#include "../../../core/generation.hpp" -#include "../../../core/qmlglobal.hpp" -#include "../../../core/reload.hpp" -#include "connection.hpp" - -namespace qs::i3::ipc { - -///! I3/Sway IPC event listener -/// #### Example -/// ```qml -/// I3IpcListener { -/// subscriptions: ["input"] -/// onIpcEvent: function (event) { -/// handleInputEvent(event.data) -/// } -/// } -/// ``` -class I3IpcListener: public PostReloadHook { - Q_OBJECT; - // clang-format off - /// List of [I3/Sway events](https://man.archlinux.org/man/sway-ipc.7.en#EVENTS) to subscribe to. - Q_PROPERTY(QList subscriptions READ subscriptions WRITE setSubscriptions NOTIFY subscriptionsChanged); - // clang-format on - QML_ELEMENT; - -public: - explicit I3IpcListener(QObject* parent = nullptr): PostReloadHook(parent) {} - ~I3IpcListener() override; - Q_DISABLE_COPY_MOVE(I3IpcListener); - - void onPostReload() override; - - [[nodiscard]] QList subscriptions() const; - void setSubscriptions(QList subscriptions); - -signals: - void ipcEvent(I3IpcEvent* event); - void subscriptionsChanged(); - -private: - void startListening(); - void receiveEvent(I3IpcEvent* event); - - void freeI3Ipc(); - - QList mSubscriptions; - I3Ipc* i3Ipc = nullptr; -}; - -} // namespace qs::i3::ipc diff --git a/src/x11/i3/ipc/monitor.cpp b/src/x11/i3/ipc/monitor.cpp index fb0ec86..1bc593c 100644 --- a/src/x11/i3/ipc/monitor.cpp +++ b/src/x11/i3/ipc/monitor.cpp @@ -7,12 +7,12 @@ #include #include -#include "controller.hpp" +#include "connection.hpp" #include "workspace.hpp" namespace qs::i3::ipc { -I3Monitor::I3Monitor(I3IpcController* ipc): QObject(ipc), ipc(ipc) { +I3Monitor::I3Monitor(I3Ipc* ipc): QObject(ipc), ipc(ipc) { // clang-format off this->bFocused.setBinding([this]() { return this->ipc->bindableFocusedMonitor().value() == this; }); // clang-format on diff --git a/src/x11/i3/ipc/monitor.hpp b/src/x11/i3/ipc/monitor.hpp index cd348b1..00269a1 100644 --- a/src/x11/i3/ipc/monitor.hpp +++ b/src/x11/i3/ipc/monitor.hpp @@ -4,7 +4,6 @@ #include #include "connection.hpp" -#include "controller.hpp" namespace qs::i3::ipc { @@ -40,10 +39,10 @@ class I3Monitor: public QObject { Q_PROPERTY(QVariantMap lastIpcObject READ lastIpcObject NOTIFY lastIpcObjectChanged); // clang-format on QML_ELEMENT; - QML_UNCREATABLE("I3Monitors must be retrieved from the I3IpcController object."); + QML_UNCREATABLE("I3Monitors must be retrieved from the I3Ipc object."); public: - explicit I3Monitor(I3IpcController* ipc); + explicit I3Monitor(I3Ipc* ipc); [[nodiscard]] QBindable bindableId() { return &this->bId; } [[nodiscard]] QBindable bindableName() { return &this->bName; } @@ -80,7 +79,7 @@ signals: void focusedChanged(); private: - I3IpcController* ipc; + I3Ipc* ipc; QVariantMap mLastIpcObject; diff --git a/src/x11/i3/ipc/qml.cpp b/src/x11/i3/ipc/qml.cpp index d835cbd..2804161 100644 --- a/src/x11/i3/ipc/qml.cpp +++ b/src/x11/i3/ipc/qml.cpp @@ -7,49 +7,46 @@ #include "../../../core/model.hpp" #include "../../../core/qmlscreen.hpp" #include "connection.hpp" -#include "controller.hpp" #include "workspace.hpp" namespace qs::i3::ipc { I3IpcQml::I3IpcQml() { - auto* instance = I3IpcController::instance(); + auto* instance = I3Ipc::instance(); // clang-format off QObject::connect(instance, &I3Ipc::rawEvent, this, &I3IpcQml::rawEvent); QObject::connect(instance, &I3Ipc::connected, this, &I3IpcQml::connected); - QObject::connect(instance, &I3IpcController::focusedWorkspaceChanged, this, &I3IpcQml::focusedWorkspaceChanged); - QObject::connect(instance, &I3IpcController::focusedMonitorChanged, this, &I3IpcQml::focusedMonitorChanged); + QObject::connect(instance, &I3Ipc::focusedWorkspaceChanged, this, &I3IpcQml::focusedWorkspaceChanged); + QObject::connect(instance, &I3Ipc::focusedMonitorChanged, this, &I3IpcQml::focusedMonitorChanged); // clang-format on } -void I3IpcQml::dispatch(const QString& request) { I3IpcController::instance()->dispatch(request); } -void I3IpcQml::refreshMonitors() { I3IpcController::instance()->refreshMonitors(); } -void I3IpcQml::refreshWorkspaces() { I3IpcController::instance()->refreshWorkspaces(); } -QString I3IpcQml::socketPath() { return I3IpcController::instance()->socketPath(); } -ObjectModel* I3IpcQml::monitors() { return I3IpcController::instance()->monitors(); } -ObjectModel* I3IpcQml::workspaces() { - return I3IpcController::instance()->workspaces(); -} +void I3IpcQml::dispatch(const QString& request) { I3Ipc::instance()->dispatch(request); } +void I3IpcQml::refreshMonitors() { I3Ipc::instance()->refreshMonitors(); } +void I3IpcQml::refreshWorkspaces() { I3Ipc::instance()->refreshWorkspaces(); } +QString I3IpcQml::socketPath() { return I3Ipc::instance()->socketPath(); } +ObjectModel* I3IpcQml::monitors() { return I3Ipc::instance()->monitors(); } +ObjectModel* I3IpcQml::workspaces() { return I3Ipc::instance()->workspaces(); } QBindable I3IpcQml::bindableFocusedWorkspace() { - return I3IpcController::instance()->bindableFocusedWorkspace(); + return I3Ipc::instance()->bindableFocusedWorkspace(); } QBindable I3IpcQml::bindableFocusedMonitor() { - return I3IpcController::instance()->bindableFocusedMonitor(); + return I3Ipc::instance()->bindableFocusedMonitor(); } I3Workspace* I3IpcQml::findWorkspaceByName(const QString& name) { - return I3IpcController::instance()->findWorkspaceByName(name); + return I3Ipc::instance()->findWorkspaceByName(name); } I3Monitor* I3IpcQml::findMonitorByName(const QString& name) { - return I3IpcController::instance()->findMonitorByName(name); + return I3Ipc::instance()->findMonitorByName(name); } I3Monitor* I3IpcQml::monitorFor(QuickshellScreenInfo* screen) { - return I3IpcController::instance()->monitorFor(screen); + return I3Ipc::instance()->monitorFor(screen); } } // namespace qs::i3::ipc diff --git a/src/x11/i3/ipc/qml.hpp b/src/x11/i3/ipc/qml.hpp index 2e7c81c..804e42a 100644 --- a/src/x11/i3/ipc/qml.hpp +++ b/src/x11/i3/ipc/qml.hpp @@ -7,7 +7,6 @@ #include "../../../core/doc.hpp" #include "../../../core/qmlscreen.hpp" #include "connection.hpp" -#include "controller.hpp" namespace qs::i3::ipc { diff --git a/src/x11/i3/ipc/workspace.cpp b/src/x11/i3/ipc/workspace.cpp index 03fadc2..7d0b730 100644 --- a/src/x11/i3/ipc/workspace.cpp +++ b/src/x11/i3/ipc/workspace.cpp @@ -7,12 +7,12 @@ #include #include -#include "controller.hpp" +#include "connection.hpp" #include "monitor.hpp" namespace qs::i3::ipc { -I3Workspace::I3Workspace(I3IpcController* ipc): QObject(ipc), ipc(ipc) { +I3Workspace::I3Workspace(I3Ipc* ipc): QObject(ipc), ipc(ipc) { Qt::beginPropertyUpdateGroup(); this->bActive.setBinding([this]() { diff --git a/src/x11/i3/ipc/workspace.hpp b/src/x11/i3/ipc/workspace.hpp index f540545..c9cd029 100644 --- a/src/x11/i3/ipc/workspace.hpp +++ b/src/x11/i3/ipc/workspace.hpp @@ -5,7 +5,6 @@ #include #include "connection.hpp" -#include "controller.hpp" namespace qs::i3::ipc { @@ -41,7 +40,7 @@ class I3Workspace: public QObject { QML_UNCREATABLE("I3Workspaces must be retrieved from the I3 object."); public: - I3Workspace(I3IpcController* ipc); + I3Workspace(I3Ipc* ipc); /// Activate the workspace. /// @@ -73,7 +72,7 @@ signals: void lastIpcObjectChanged(); private: - I3IpcController* ipc; + I3Ipc* ipc; QVariantMap mLastIpcObject; diff --git a/src/x11/i3/module.md b/src/x11/i3/module.md index 1dc6180..10afb98 100644 --- a/src/x11/i3/module.md +++ b/src/x11/i3/module.md @@ -2,10 +2,8 @@ name = "Quickshell.I3" description = "I3 specific Quickshell types" headers = [ "ipc/connection.hpp", - "ipc/controller.hpp", "ipc/qml.hpp", "ipc/workspace.hpp", "ipc/monitor.hpp", - "ipc/listener.hpp", ] ----- diff --git a/src/x11/panel_window.cpp b/src/x11/panel_window.cpp index c78b548..adba0ab 100644 --- a/src/x11/panel_window.cpp +++ b/src/x11/panel_window.cpp @@ -115,8 +115,6 @@ XPanelWindow::XPanelWindow(QObject* parent): ProxyWindowBase(parent) { return 0; } } - - return 0; }); this->bcExclusionEdge.setBinding([this] { return this->bAnchors.value().exclusionEdge(); }); @@ -416,9 +414,23 @@ void XPanelWindow::updateFocusable() { XPanelInterface::XPanelInterface(QObject* parent) : PanelWindowInterface(parent) , panel(new XPanelWindow(this)) { - this->connectSignals(); // clang-format off + QObject::connect(this->panel, &ProxyWindowBase::windowConnected, this, &XPanelInterface::windowConnected); + QObject::connect(this->panel, &ProxyWindowBase::visibleChanged, this, &XPanelInterface::visibleChanged); + QObject::connect(this->panel, &ProxyWindowBase::backerVisibilityChanged, this, &XPanelInterface::backingWindowVisibleChanged); + QObject::connect(this->panel, &ProxyWindowBase::implicitHeightChanged, this, &XPanelInterface::implicitHeightChanged); + QObject::connect(this->panel, &ProxyWindowBase::implicitWidthChanged, this, &XPanelInterface::implicitWidthChanged); + QObject::connect(this->panel, &ProxyWindowBase::heightChanged, this, &XPanelInterface::heightChanged); + QObject::connect(this->panel, &ProxyWindowBase::widthChanged, this, &XPanelInterface::widthChanged); + QObject::connect(this->panel, &ProxyWindowBase::devicePixelRatioChanged, this, &XPanelInterface::devicePixelRatioChanged); + QObject::connect(this->panel, &ProxyWindowBase::screenChanged, this, &XPanelInterface::screenChanged); + QObject::connect(this->panel, &ProxyWindowBase::windowTransformChanged, this, &XPanelInterface::windowTransformChanged); + QObject::connect(this->panel, &ProxyWindowBase::colorChanged, this, &XPanelInterface::colorChanged); + QObject::connect(this->panel, &ProxyWindowBase::maskChanged, this, &XPanelInterface::maskChanged); + QObject::connect(this->panel, &ProxyWindowBase::surfaceFormatChanged, this, &XPanelInterface::surfaceFormatChanged); + + // panel specific QObject::connect(this->panel, &XPanelWindow::anchorsChanged, this, &XPanelInterface::anchorsChanged); QObject::connect(this->panel, &XPanelWindow::marginsChanged, this, &XPanelInterface::marginsChanged); QObject::connect(this->panel, &XPanelWindow::exclusiveZoneChanged, this, &XPanelInterface::exclusiveZoneChanged); @@ -435,13 +447,28 @@ void XPanelInterface::onReload(QObject* oldInstance) { this->panel->reload(old != nullptr ? old->panel : nullptr); } +QQmlListProperty XPanelInterface::data() { return this->panel->data(); } ProxyWindowBase* XPanelInterface::proxyWindow() const { return this->panel; } +QQuickItem* XPanelInterface::contentItem() const { return this->panel->contentItem(); } +bool XPanelInterface::isBackingWindowVisible() const { return this->panel->isVisibleDirect(); } +qreal XPanelInterface::devicePixelRatio() const { return this->panel->devicePixelRatio(); } // NOLINTBEGIN #define proxyPair(type, get, set) \ type XPanelInterface::get() const { return this->panel->get(); } \ void XPanelInterface::set(type value) { this->panel->set(value); } +proxyPair(bool, isVisible, setVisible); +proxyPair(qint32, implicitWidth, setImplicitWidth); +proxyPair(qint32, implicitHeight, setImplicitHeight); +proxyPair(qint32, width, setWidth); +proxyPair(qint32, height, setHeight); +proxyPair(QuickshellScreenInfo*, screen, setScreen); +proxyPair(QColor, color, setColor); +proxyPair(PendingRegion*, mask, setMask); +proxyPair(QsSurfaceFormat, surfaceFormat, setSurfaceFormat); + +// panel specific proxyPair(Anchors, anchors, setAnchors); proxyPair(Margins, margins, setMargins); proxyPair(qint32, exclusiveZone, setExclusiveZone); diff --git a/src/x11/panel_window.hpp b/src/x11/panel_window.hpp index ab36826..02c05b1 100644 --- a/src/x11/panel_window.hpp +++ b/src/x11/panel_window.hpp @@ -135,8 +135,43 @@ public: void onReload(QObject* oldInstance) override; [[nodiscard]] ProxyWindowBase* proxyWindow() const override; + [[nodiscard]] QQuickItem* contentItem() const override; // NOLINTBEGIN + [[nodiscard]] bool isVisible() const override; + [[nodiscard]] bool isBackingWindowVisible() const override; + void setVisible(bool visible) override; + + [[nodiscard]] qint32 implicitWidth() const override; + void setImplicitWidth(qint32 implicitWidth) override; + + [[nodiscard]] qint32 implicitHeight() const override; + void setImplicitHeight(qint32 implicitHeight) override; + + [[nodiscard]] qint32 width() const override; + void setWidth(qint32 width) override; + + [[nodiscard]] qint32 height() const override; + void setHeight(qint32 height) override; + + [[nodiscard]] virtual qreal devicePixelRatio() const override; + + [[nodiscard]] QuickshellScreenInfo* screen() const override; + void setScreen(QuickshellScreenInfo* screen) override; + + [[nodiscard]] QColor color() const override; + void setColor(QColor color) override; + + [[nodiscard]] PendingRegion* mask() const override; + void setMask(PendingRegion* mask) override; + + [[nodiscard]] QsSurfaceFormat surfaceFormat() const override; + void setSurfaceFormat(QsSurfaceFormat mask) override; + + [[nodiscard]] QQmlListProperty data() override; + + // panel specific + [[nodiscard]] Anchors anchors() const override; void setAnchors(Anchors anchors) override;