diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index e3e97bef..ce6d9d3e 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -2,12 +2,13 @@ name: Bug Report description: Something is not working right labels: ["bug"] body: - - type: markdown + - type: checkboxes attributes: - value: | - ## Before opening a new issue, please take a moment to search through the current open and closed issues to check if it already exists. - - --- + label: Already reported ? * + description: Before opening a new bug report, please take a moment to search through the current open and closed issues to check if it already exists. + options: + - label: I have searched the existing open and closed issues. + required: true - type: dropdown id: type @@ -28,10 +29,10 @@ body: attributes: label: System Info and Version description: | - Paste the output of `hyprctl systeminfo -c` here (If you are on a - version that shows you help menu, omit the `-c` and attach config files - to the issue). If you have configs outside of the main config shown - here, please attach. + Paste the output of `hyprctl systeminfo -c` here. If you can't + launch Hyprland, paste the output of `Hyprland --systeminfo`. + If `Hyprland --systeminfo` errors out (added in 0.44.0), find + and paste the Hyprland version manually. value: "
System/Version info diff --git a/.github/workflows/nix-build.yml b/.github/workflows/nix-build.yml index d0234498..d1f47ee4 100644 --- a/.github/workflows/nix-build.yml +++ b/.github/workflows/nix-build.yml @@ -1,3 +1,5 @@ +name: Nix (Build) + on: workflow_call: secrets: @@ -10,21 +12,17 @@ jobs: matrix: package: - hyprland + - hyprland-cross - xdg-desktop-portal-hyprland runs-on: ubuntu-latest steps: - - name: Clone repository - uses: actions/checkout@v4 - with: - ref: ${{ github.ref }} - submodules: recursive - - - uses: cachix/install-nix-action@v27 + - uses: DeterminateSystems/nix-installer-action@main - uses: DeterminateSystems/magic-nix-cache-action@main + - uses: cachix/cachix-action@v15 with: name: hyprland - authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" - - run: nix build '.?submodules=1#${{ matrix.package }}' -L --extra-substituters "https://hyprland.cachix.org" + - run: nix build 'github:hyprwm/Hyprland?ref=${{ github.ref }}#${{ matrix.package }}' -L --extra-substituters "https://hyprland.cachix.org" diff --git a/.github/workflows/nix-ci.yml b/.github/workflows/nix-ci.yml index b25ed9aa..75c19790 100644 --- a/.github/workflows/nix-ci.yml +++ b/.github/workflows/nix-ci.yml @@ -1,15 +1,14 @@ -name: Nix +name: Nix (CI) on: [push, pull_request, workflow_dispatch] jobs: update-inputs: - if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') uses: ./.github/workflows/nix-update-inputs.yml secrets: inherit build: - if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork) && !contains(needs.*.result, 'failure') - needs: update-inputs + if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork) uses: ./.github/workflows/nix-build.yml secrets: inherit diff --git a/.github/workflows/nix-update-inputs.yml b/.github/workflows/nix-update-inputs.yml index 000900d9..a8f68f3b 100644 --- a/.github/workflows/nix-update-inputs.yml +++ b/.github/workflows/nix-update-inputs.yml @@ -1,4 +1,4 @@ -name: Nix +name: Nix (Update Inputs) on: workflow_call: @@ -8,6 +8,7 @@ on: jobs: update: + if: github.repository == 'hyprwm/Hyprland' name: inputs runs-on: ubuntu-latest steps: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 1f52b4ff..d5ff1aa0 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -20,7 +20,6 @@ jobs: run: | git fetch --unshallow || echo "failed unshallowing" bash -c scripts/generateVersion.sh - mv scripts/generateVersion.sh scripts/generateVersion.sh.bak - name: Create tarball with submodules id: tar diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 51f6b91e..b8be5f55 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -7,22 +7,22 @@ name: Mark stale issues and pull requests on: schedule: - - cron: '7 */4 * * *' + - cron: "7 */4 * * *" workflow_dispatch: jobs: stale: - + if: github.repository == 'hyprwm/Hyprland' runs-on: ubuntu-latest permissions: issues: write pull-requests: write steps: - - uses: actions/stale@v5 - with: - repo-token: ${{ secrets.STALEBOT_PAT }} - stale-issue-label: 'stale' - stale-pr-label: 'stale' - operations-per-run: 40 - days-before-close: -1 + - uses: actions/stale@v5 + with: + repo-token: ${{ secrets.STALEBOT_PAT }} + stale-issue-label: "stale" + stale-pr-label: "stale" + operations-per-run: 40 + days-before-close: -1 diff --git a/.gitignore b/.gitignore index 78f794fc..a7934790 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ _deps build/ result* +/.pre-commit-config.yaml /.vscode/ /.idea/ .envrc @@ -37,3 +38,8 @@ gmon.out PKGBUILD src/version.h +hyprpm/Makefile +hyprctl/Makefile + +**/.#*.* +**/#*.*# diff --git a/CMakeLists.txt b/CMakeLists.txt index fa58b63d..df919d76 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.27) +cmake_minimum_required(VERSION 3.30) # Get version file(READ "${CMAKE_SOURCE_DIR}/VERSION" VER_RAW) @@ -25,8 +25,18 @@ message(STATUS "Gathering git info") execute_process(COMMAND ./scripts/generateVersion.sh WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) -# udis -add_subdirectory("subprojects/udis86") +find_package(PkgConfig REQUIRED) + +# Try to find canihavesomecoffee's udis86 using pkgconfig vmd/udis86 does not +# provide a .pc file and won't be detected this way +pkg_check_modules(udis_dep IMPORTED_TARGET udis86>=1.7.2) + +# Fallback to subproject +if(NOT udis_dep_FOUND) + add_subdirectory("subprojects/udis86") + include_directories("subprojects/udis86") + message(STATUS "udis86 dependency not found, falling back to subproject") +endif() if(CMAKE_BUILD_TYPE) string(TOLOWER ${CMAKE_BUILD_TYPE} BUILDTYPE_LOWER) @@ -47,11 +57,11 @@ else() set(BUILDTYPE_LOWER "release") endif() -find_package(PkgConfig REQUIRED) - pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir) message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}") -pkg_get_variable(WAYLAND_SERVER_DIR wayland-server pkgdatadir) +pkg_get_variable(WAYLAND_SCANNER_PKGDATA_DIR wayland-scanner pkgdatadir) +message( + STATUS "Found wayland-scanner pkgdatadir at ${WAYLAND_SCANNER_PKGDATA_DIR}") if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) message(STATUS "Configuring Hyprland in Debug with CMake") @@ -61,8 +71,11 @@ else() message(STATUS "Configuring Hyprland in Release with CMake") endif() -include_directories(. "src/" "subprojects/udis86/" "protocols/") -set(CMAKE_CXX_STANDARD 23) +add_compile_definitions(HYPRLAND_VERSION="${HYPRLAND_VERSION}") + +include_directories(. "src/" "protocols/") + +set(CMAKE_CXX_STANDARD 26) add_compile_options( -Wall -Wextra @@ -87,16 +100,19 @@ else() endif() find_package(OpenGL REQUIRED COMPONENTS ${GLES_VERSION}) +pkg_check_modules(hyprctl_deps REQUIRED IMPORTED_TARGET hyprutils>=0.2.1) + +pkg_check_modules(aquamarine_dep REQUIRED IMPORTED_TARGET aquamarine>=0.4.2) + +add_compile_definitions(AQUAMARINE_VERSION="${aquamarine_dep_VERSION}") + pkg_check_modules( deps REQUIRED IMPORTED_TARGET - aquamarine xkbcommon uuid - wayland-server - wayland-client - wayland-cursor + wayland-server>=1.22.90 wayland-protocols cairo pango @@ -105,15 +121,11 @@ pkg_check_modules( xcursor libdrm libinput - hwdata - libseat - libdisplay-info - libliftoff - libudev gbm + gio-2.0 hyprlang>=0.3.2 hyprcursor>=0.1.7 - hyprutils>=0.2.1) + hyprutils>=0.2.3) find_package(hyprwayland-scanner 0.3.10 REQUIRED) @@ -195,14 +207,11 @@ else() REQUIRED IMPORTED_TARGET xcb - xwayland - xcb-util xcb-render xcb-xfixes xcb-icccm xcb-composite xcb-res - xcb-ewmh xcb-errors) target_link_libraries(Hyprland PkgConfig::xdeps) endif() @@ -225,14 +234,19 @@ target_precompile_headers(Hyprland PRIVATE message(STATUS "Setting link libraries") -target_link_libraries(Hyprland rt PkgConfig::deps) +target_link_libraries(Hyprland rt PkgConfig::aquamarine_dep PkgConfig::deps) +if(udis_dep_FOUND) + target_link_libraries(Hyprland PkgConfig::udis_dep) +else() + target_link_libraries(Hyprland libudis86) +endif() # used by `make installheaders`, to ensure the headers are generated add_custom_target(generate-protocol-headers) function(protocolnew protoPath protoName external) if(external) - set(path ${CMAKE_SOURCE_DIR}/${protoPath}) + set(path ${protoPath}) else() set(path ${WAYLAND_PROTOCOLS_DIR}/${protoPath}) endif() @@ -251,22 +265,31 @@ function(protocolWayland) add_custom_command( OUTPUT ${CMAKE_SOURCE_DIR}/protocols/wayland.cpp ${CMAKE_SOURCE_DIR}/protocols/wayland.hpp - COMMAND hyprwayland-scanner --wayland-enums - ${WAYLAND_SERVER_DIR}/wayland.xml ${CMAKE_SOURCE_DIR}/protocols/ + COMMAND + hyprwayland-scanner --wayland-enums + ${WAYLAND_SCANNER_PKGDATA_DIR}/wayland.xml ${CMAKE_SOURCE_DIR}/protocols/ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) target_sources(Hyprland PRIVATE protocols/wayland.cpp protocols/wayland.hpp) target_sources(generate-protocol-headers PRIVATE ${CMAKE_SOURCE_DIR}/protocols/wayland.hpp) endfunction() -target_link_libraries(Hyprland OpenGL::EGL OpenGL::GL Threads::Threads - libudis86 uuid) +target_link_libraries(Hyprland OpenGL::EGL OpenGL::GL Threads::Threads) -protocolnew("subprojects/hyprland-protocols/protocols" - "hyprland-global-shortcuts-v1" true) +pkg_check_modules(hyprland_protocols_dep hyprland-protocols>=0.4.0) +if(hyprland_protocols_dep_FOUND) + pkg_get_variable(HYPRLAND_PROTOCOLS hyprland-protocols pkgdatadir) + message(STATUS "hyprland-protocols dependency set to ${HYPRLAND_PROTOCOLS}") +else() + set(HYPRLAND_PROTOCOLS "subprojects/hyprland-protocols") + message(STATUS "hyprland-protocols subproject set to ${HYPRLAND_PROTOCOLS}") +endif() + +protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-global-shortcuts-v1" + true) protocolnew("unstable/text-input" "text-input-unstable-v1" false) -protocolnew("subprojects/hyprland-protocols/protocols" - "hyprland-toplevel-export-v1" true) +protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-toplevel-export-v1" + true) protocolnew("protocols" "wlr-screencopy-unstable-v1" true) protocolnew("protocols" "wlr-gamma-control-unstable-v1" true) protocolnew("protocols" "wlr-foreign-toplevel-management-unstable-v1" true) @@ -277,10 +300,11 @@ protocolnew("protocols" "input-method-unstable-v2" true) protocolnew("protocols" "wlr-output-management-unstable-v1" true) protocolnew("protocols" "kde-server-decoration" true) protocolnew("protocols" "wlr-data-control-unstable-v1" true) -protocolnew("subprojects/hyprland-protocols/protocols" "hyprland-focus-grab-v1" - true) +protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-focus-grab-v1" true) protocolnew("protocols" "wlr-layer-shell-unstable-v1" true) protocolnew("protocols" "wayland-drm" true) +protocolnew("${HYPRLAND_PROTOCOLS}/protocols" "hyprland-ctm-control-v1" true) + protocolnew("staging/tearing-control" "tearing-control-v1" false) protocolnew("staging/fractional-scale" "fractional-scale-v1" false) protocolnew("unstable/xdg-output" "xdg-output-unstable-v1" false) @@ -309,6 +333,9 @@ protocolnew("stable/viewporter" "viewporter" false) protocolnew("stable/linux-dmabuf" "linux-dmabuf-v1" false) protocolnew("staging/drm-lease" "drm-lease-v1" false) protocolnew("staging/linux-drm-syncobj" "linux-drm-syncobj-v1" false) +protocolnew("staging/xdg-dialog" "xdg-dialog-v1" false) +protocolnew("staging/single-pixel-buffer" "single-pixel-buffer-v1" false) +protocolnew("staging/security-context" "security-context-v1" false) protocolwayland() @@ -323,19 +350,21 @@ install( CODE "execute_process( \ COMMAND ${CMAKE_COMMAND} -E create_symlink \ ${CMAKE_INSTALL_FULL_BINDIR}/Hyprland \ - ${CMAKE_INSTALL_FULL_BINDIR}/hyprland + \"\$ENV{DESTDIR}${CMAKE_INSTALL_FULL_BINDIR}/hyprland\" \ )") # session file install(FILES ${CMAKE_SOURCE_DIR}/example/hyprland.desktop DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/wayland-sessions) -# allow Hyprland to find wallpapers +# allow Hyprland to find assets add_compile_definitions(DATAROOTDIR="${CMAKE_INSTALL_FULL_DATAROOTDIR}") -# wallpapers -file(GLOB_RECURSE WALLPAPERS "assets/wall*") -install(FILES ${WALLPAPERS} DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/hypr) +# installable assets +file(GLOB_RECURSE INSTALLABLE_ASSETS "assets/install/*") +list(FILTER INSTALLABLE_ASSETS EXCLUDE REGEX "meson.build") +install(FILES ${INSTALLABLE_ASSETS} + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/hypr) # default config install(FILES ${CMAKE_SOURCE_DIR}/example/hyprland.conf diff --git a/Makefile b/Makefile index adf6fbe8..98267eea 100644 --- a/Makefile +++ b/Makefile @@ -1,28 +1,24 @@ PREFIX = /usr/local legacyrenderer: - cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:STRING=${PREFIX} -DLEGACY_RENDERER:BOOL=true -S . -B ./build -G Ninja - cmake --build ./build --config Release --target all - chmod -R 777 ./build + cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:STRING=${PREFIX} -DLEGACY_RENDERER:BOOL=true -S . -B ./build + cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF` legacyrendererdebug: - cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_INSTALL_PREFIX:STRING=${PREFIX} -DLEGACY_RENDERER:BOOL=true -S . -B ./build -G Ninja - cmake --build ./build --config Release --target all - chmod -R 777 ./build + cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_INSTALL_PREFIX:STRING=${PREFIX} -DLEGACY_RENDERER:BOOL=true -S . -B ./build + cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF` release: - cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:STRING=${PREFIX} -S . -B ./build -G Ninja - cmake --build ./build --config Release --target all - chmod -R 777 ./build + cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:STRING=${PREFIX} -S . -B ./build + cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF` debug: - cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_INSTALL_PREFIX:STRING=${PREFIX} -S . -B ./build -G Ninja - cmake --build ./build --config Debug --target all - chmod -R 777 ./build + cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_INSTALL_PREFIX:STRING=${PREFIX} -S . -B ./build + cmake --build ./build --config Debug --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF` nopch: - cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:STRING=${PREFIX} -DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON -S . -B ./build -G Ninja - cmake --build ./build --config Release --target all + cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:STRING=${PREFIX} -DCMAKE_DISABLE_PRECOMPILE_HEADERS=ON -S . -B ./build + cmake --build ./build --config Release --target all -j`nproc 2>/dev/null || getconf NPROCESSORS_CONF` clear: rm -rf build @@ -87,8 +83,8 @@ asan: #git reset --hard @echo -en "If you want to apply a patch, input its path (leave empty for none):\n" - @read patchvar - @if [-n "$patchvar"]; then patch -p1 < $patchvar || echo ""; else echo "No patch specified"; fi + @read patchvar; \ + if [ -n "$$patchvar" ]; then patch -p1 < "$$patchvar" || echo ""; else echo "No patch specified"; fi git clone --recursive https://gitlab.freedesktop.org/wayland/wayland cd wayland && patch -p1 < ../scripts/waylandStatic.diff && meson setup build --buildtype=debug -Db_sanitize=address -Ddocumentation=false && ninja -C build && cd .. diff --git a/README.md b/README.md index fc2bd206..f271c29c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
-banner +banner
@@ -125,7 +125,6 @@ easy IPC, much more QoL stuff than other compositors and more... -[Stars Preview]: https://starchart.cc/vaxerski/Hyprland.svg [Preview A]: https://i.ibb.co/C1yTb0r/falf.png [Preview B]: https://linfindel.github.io/cdn/hyprland-preview-b.png [Preview C]: https://i.ibb.co/B3GJg28/20221126-20h53m26s-grim.png diff --git a/VERSION b/VERSION index 6599454d..a8ab6c96 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.41.2 +0.44.0 diff --git a/assets/install/lockdead.png b/assets/install/lockdead.png new file mode 100644 index 00000000..f8bae225 Binary files /dev/null and b/assets/install/lockdead.png differ diff --git a/assets/install/lockdead2.png b/assets/install/lockdead2.png new file mode 100644 index 00000000..3f46c434 Binary files /dev/null and b/assets/install/lockdead2.png differ diff --git a/assets/install/meson.build b/assets/install/meson.build new file mode 100644 index 00000000..45076469 --- /dev/null +++ b/assets/install/meson.build @@ -0,0 +1,10 @@ +globber = run_command('sh', '-c', 'find . -type f -not -name "*.build"', check: true) +files = globber.stdout().strip().split('\n') + +foreach file : files + install_data( + file, + install_dir: join_paths(get_option('datadir'), 'hypr'), + install_tag: 'runtime', + ) +endforeach diff --git a/assets/wall0.png b/assets/install/wall0.png similarity index 100% rename from assets/wall0.png rename to assets/install/wall0.png diff --git a/assets/wall1.png b/assets/install/wall1.png similarity index 100% rename from assets/wall1.png rename to assets/install/wall1.png diff --git a/assets/wall2.png b/assets/install/wall2.png similarity index 100% rename from assets/wall2.png rename to assets/install/wall2.png diff --git a/assets/meson.build b/assets/meson.build index 47de3d02..2a28121d 100644 --- a/assets/meson.build +++ b/assets/meson.build @@ -1,7 +1,7 @@ -wallpapers = ['0', '1', '2'] +install_data( + 'hyprland-portals.conf', + install_dir: join_paths(get_option('datadir'), 'xdg-desktop-portal'), + install_tag: 'runtime', +) -foreach type : wallpapers - install_data(f'wall@type@.png', install_dir: join_paths(get_option('datadir'), 'hypr'), install_tag: 'runtime') -endforeach - -install_data('hyprland-portals.conf', install_dir: join_paths(get_option('datadir'), 'xdg-desktop-portal'), install_tag: 'runtime') +subdir('install') diff --git a/docs/Hyprland.1 b/docs/Hyprland.1 index 92061da2..2d6e1444 100644 --- a/docs/Hyprland.1 +++ b/docs/Hyprland.1 @@ -10,8 +10,8 @@ Hyprland - Dynamic tiling Wayland compositor \f[B]Hyprland\f[R] [\f[I]arg [...]\f[R]]. .SH DESCRIPTION .PP -\f[B]Hyprland\f[R] is a dynamic tiling Wayland compositor based on -wlroots that doesn\[aq]t sacrifice on its looks. +\f[B]Hyprland\f[R] is an independent, highly customizable, dynamic +tiling Wayland compositor that doesn\[aq]t sacrifice on its looks. .PP You can launch Hyprland by either going into a TTY and executing \f[B]Hyprland\f[R], or with a login manager. diff --git a/docs/Hyprland.1.rst b/docs/Hyprland.1.rst index c73b4343..e10d9c6a 100644 --- a/docs/Hyprland.1.rst +++ b/docs/Hyprland.1.rst @@ -14,8 +14,8 @@ SYNOPSIS DESCRIPTION =========== -**Hyprland** is a dynamic tiling Wayland compositor based on -wlroots that doesn't sacrifice on its looks. +**Hyprland** is an independent, highly customizable, +dynamic tiling Wayland compositor that doesn't sacrifice on its looks. You can launch Hyprland by either going into a TTY and executing **Hyprland**, or with a login manager. diff --git a/docs/meson.build b/docs/meson.build index a5d7e737..6ff51d1a 100644 --- a/docs/meson.build +++ b/docs/meson.build @@ -1,2 +1,2 @@ -install_man ('Hyprland.1') -install_man ('hyprctl.1') +install_man('Hyprland.1') +install_man('hyprctl.1') diff --git a/example/hyprland-session.service b/example/hyprland-session.service new file mode 100644 index 00000000..7d33f5b3 --- /dev/null +++ b/example/hyprland-session.service @@ -0,0 +1,14 @@ +[Unit] +Description=Hyprland - Tiling compositor with the looks +Documentation=man:Hyprland(1) +BindsTo=graphical-session.target +Before=graphical-session.target +Wants=graphical-session-pre.target +After=graphical-session-pre.target + +[Service] +Type=notify +ExecStart=/usr/bin/Hyprland +ExecStop=/usr/bin/hyprctl dispatch exit +Restart=on-failure +Slice=session.slice diff --git a/example/hyprland-systemd.desktop b/example/hyprland-systemd.desktop new file mode 100644 index 00000000..b36a87b2 --- /dev/null +++ b/example/hyprland-systemd.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Name=Hyprland +Comment=An intelligent dynamic tiling Wayland compositor +Exec=systemctl --user start --wait hyprland-session +Type=Application diff --git a/example/hyprland.conf b/example/hyprland.conf index f69309c2..6d7bddb8 100644 --- a/example/hyprland.conf +++ b/example/hyprland.conf @@ -59,7 +59,7 @@ env = HYPRCURSOR_SIZE,24 # Refer to https://wiki.hyprland.org/Configuring/Variables/ # https://wiki.hyprland.org/Configuring/Variables/#general -general { +general { gaps_in = 5 gaps_out = 20 @@ -70,7 +70,7 @@ general { col.inactive_border = rgba(595959aa) # Set to true enable resizing windows by clicking and dragging on borders and gaps - resize_on_border = false + resize_on_border = false # Please see https://wiki.hyprland.org/Configuring/Tearing/ before you turn this on allow_tearing = false @@ -96,7 +96,7 @@ decoration { enabled = true size = 3 passes = 1 - + vibrancy = 0.1696 } } @@ -117,6 +117,13 @@ animations { animation = workspaces, 1, 6, default } +# Ref https://wiki.hyprland.org/Configuring/Workspace-Rules/ +# "Smart gaps" / "No gaps when only" +# uncomment all three if you wish to use that. +# workspace = w[t1], gapsout:0, gapsin:0, border: 0, rounding:0 +# workspace = w[tg1], gapsout:0, gapsin:0, border: 0, rounding:0 +# workspace = f[1], gapsout:0, gapsin:0, border: 0, rounding:0 + # See https://wiki.hyprland.org/Configuring/Dwindle-Layout/ for more dwindle { pseudotile = true # Master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below @@ -129,7 +136,7 @@ master { } # https://wiki.hyprland.org/Configuring/Variables/#misc -misc { +misc { force_default_wallpaper = -1 # Set to 0 or 1 to disable the anime mascot wallpapers disable_hyprland_logo = false # If true disables the random hyprland logo / anime girl background. :( } @@ -169,9 +176,9 @@ device { } -#################### -### KEYBINDINGSS ### -#################### +################### +### KEYBINDINGS ### +################### # See https://wiki.hyprland.org/Configuring/Keywords/ $mainMod = SUPER # Sets "Windows" key as main modifier @@ -228,6 +235,19 @@ bind = $mainMod, mouse_up, workspace, e-1 bindm = $mainMod, mouse:272, movewindow bindm = $mainMod, mouse:273, resizewindow +# Laptop multimedia keys for volume and LCD brightness +bindel = ,XF86AudioRaiseVolume, exec, wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 5%+ +bindel = ,XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%- +bindel = ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle +bindel = ,XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle +bindel = ,XF86MonBrightnessUp, exec, brightnessctl s 10%+ +bindel = ,XF86MonBrightnessDown, exec, brightnessctl s 10%- + +# Requires playerctl +bindl = , XF86AudioNext, exec, playerctl next +bindl = , XF86AudioPause, exec, playerctl play-pause +bindl = , XF86AudioPlay, exec, playerctl play-pause +bindl = , XF86AudioPrev, exec, playerctl previous ############################## ### WINDOWS AND WORKSPACES ### @@ -242,4 +262,8 @@ bindm = $mainMod, mouse:273, resizewindow # Example windowrule v2 # windowrulev2 = float,class:^(kitty)$,title:^(kitty)$ -windowrulev2 = suppressevent maximize, class:.* # You'll probably like this. +# Ignore maximize requests from apps. You'll probably like this. +windowrulev2 = suppressevent maximize, class:.* + +# Fix some dragging issues with XWayland +windowrulev2 = nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0 diff --git a/example/hyprland.desktop b/example/hyprland.desktop index 57ad076f..bb2801a9 100644 --- a/example/hyprland.desktop +++ b/example/hyprland.desktop @@ -2,4 +2,6 @@ Name=Hyprland Comment=An intelligent dynamic tiling Wayland compositor Exec=Hyprland -Type=Application \ No newline at end of file +Type=Application +DesktopNames=Hyprland +Keywords=tiling;wayland;compositor; diff --git a/example/meson.build b/example/meson.build index 2fb3a35e..a338644e 100644 --- a/example/meson.build +++ b/example/meson.build @@ -1,2 +1,10 @@ -install_data('hyprland.conf', install_dir: join_paths(get_option('datadir'), 'hypr'), install_tag: 'runtime') -install_data('hyprland.desktop', install_dir: join_paths(get_option('datadir'), 'wayland-sessions'), install_tag: 'runtime') +install_data( + 'hyprland.conf', + install_dir: join_paths(get_option('datadir'), 'hypr'), + install_tag: 'runtime', +) +install_data( + 'hyprland.desktop', + install_dir: join_paths(get_option('datadir'), 'wayland-sessions'), + install_tag: 'runtime', +) diff --git a/flake.lock b/flake.lock index 5c384d4d..0a12fe0a 100644 --- a/flake.lock +++ b/flake.lock @@ -16,11 +16,11 @@ ] }, "locked": { - "lastModified": 1722347739, - "narHash": "sha256-rAoh+K6KG+b1DwSWtqRVocdojnH6nGk6q07mNltoUSM=", + "lastModified": 1728902391, + "narHash": "sha256-44bnoY0nAvbBQ/lVjmn511yL39Sv7SknV0BDxn34P3Q=", "owner": "hyprwm", "repo": "aquamarine", - "rev": "7c3565f9bedc7cb601cc0baa14792247e4dc1d5a", + "rev": "9874e08eec85b5542ca22494e127b0cdce46b786", "type": "github" }, "original": { @@ -29,6 +29,43 @@ "type": "github" } }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, "hyprcursor": { "inputs": { "hyprlang": [ @@ -42,11 +79,11 @@ ] }, "locked": { - "lastModified": 1721330371, - "narHash": "sha256-aYlHTWylczLt6ERJyg6E66Y/XSCbVL7leVcRuJmVbpI=", + "lastModified": 1728669738, + "narHash": "sha256-EDNAU9AYcx8OupUzbTbWE1d3HYdeG0wO6Msg3iL1muk=", "owner": "hyprwm", "repo": "hyprcursor", - "rev": "4493a972b48f9c3014befbbf381ed5fff91a65dc", + "rev": "0264e698149fcb857a66a53018157b41f8d97bb0", "type": "github" }, "original": { @@ -58,20 +95,18 @@ "hyprland-protocols": { "inputs": { "nixpkgs": [ - "xdph", "nixpkgs" ], "systems": [ - "xdph", "systems" ] }, "locked": { - "lastModified": 1721326555, - "narHash": "sha256-zCu4R0CSHEactW9JqYki26gy8h9f6rHmSwj4XJmlHgg=", + "lastModified": 1728345020, + "narHash": "sha256-xGbkc7U/Roe0/Cv3iKlzijIaFBNguasI31ynL2IlEoM=", "owner": "hyprwm", "repo": "hyprland-protocols", - "rev": "5a11232266bf1a1f5952d5b179c3f4b2facaaa84", + "rev": "a7c183800e74f337753de186522b9017a07a8cee", "type": "github" }, "original": { @@ -93,11 +128,11 @@ ] }, "locked": { - "lastModified": 1721324361, - "narHash": "sha256-BiJKO0IIdnSwHQBSrEJlKlFr753urkLE48wtt0UhNG4=", + "lastModified": 1728168612, + "narHash": "sha256-AnB1KfiXINmuiW7BALYrKqcjCnsLZPifhb/7BsfPbns=", "owner": "hyprwm", "repo": "hyprlang", - "rev": "adbefbf49664a6c2c8bf36b6487fd31e3eb68086", + "rev": "f054f2e44d6a0b74607a6bc0f52dba337a3db38e", "type": "github" }, "original": { @@ -116,11 +151,11 @@ ] }, "locked": { - "lastModified": 1722098849, - "narHash": "sha256-D3wIZlBNh7LuZ0NaoCpY/Pvu+xHxIVtSN+KkWZYvvVs=", + "lastModified": 1728941256, + "narHash": "sha256-WRypmcZ2Bw94lLmcmxYokVOHPJSZ7T06V49QZ4tkZeQ=", "owner": "hyprwm", "repo": "hyprutils", - "rev": "5dcbbc1e3de40b2cecfd2007434d86e924468f1f", + "rev": "fd4be8b9ca932f7384e454bcd923c5451ef2aa85", "type": "github" }, "original": { @@ -139,11 +174,11 @@ ] }, "locked": { - "lastModified": 1721324119, - "narHash": "sha256-SOOqIT27/X792+vsLSeFdrNTF+OSRp5qXv6Te+fb2Qg=", + "lastModified": 1726874836, + "narHash": "sha256-VKR0sf0PSNCB0wPHVKSAn41mCNVCnegWmgkrneKDhHM=", "owner": "hyprwm", "repo": "hyprwayland-scanner", - "rev": "a048a6cb015340bd82f97c1f40a4b595ca85cc30", + "rev": "500c81a9e1a76760371049a8d99e008ea77aa59e", "type": "github" }, "original": { @@ -154,11 +189,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1722185531, - "narHash": "sha256-veKR07psFoJjINLC8RK4DiLniGGMgF3QMlS4tb74S6k=", + "lastModified": 1728888510, + "narHash": "sha256-nsNdSldaAyu6PE3YUA+YQLqUDJh+gRbBooMMekZJwvI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "52ec9ac3b12395ad677e8b62106f0b98c1f8569d", + "rev": "a3c0b3b21515f74fd2665903d4ce6bc4dc81c77c", "type": "github" }, "original": { @@ -168,14 +203,55 @@ "type": "github" } }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1720386169, + "narHash": "sha256-NGKVY4PjzwAa4upkGtAMz1npHGoRzWotlSnVlqI40mo=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "194846768975b7ad2c4988bdb82572c00222c0d7", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-24.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "pre-commit-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ], + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { + "lastModified": 1728778939, + "narHash": "sha256-WybK5E3hpGxtCYtBwpRj1E9JoiVxe+8kX83snTNaFHE=", + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "ff68f91754be6f3427e4986d7949e6273659be1d", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "git-hooks.nix", + "type": "github" + } + }, "root": { "inputs": { "aquamarine": "aquamarine", "hyprcursor": "hyprcursor", + "hyprland-protocols": "hyprland-protocols", "hyprlang": "hyprlang", "hyprutils": "hyprutils", "hyprwayland-scanner": "hyprwayland-scanner", "nixpkgs": "nixpkgs", + "pre-commit-hooks": "pre-commit-hooks", "systems": "systems", "xdph": "xdph" } @@ -197,10 +273,18 @@ }, "xdph": { "inputs": { - "hyprland-protocols": "hyprland-protocols", + "hyprland-protocols": [ + "hyprland-protocols" + ], "hyprlang": [ "hyprlang" ], + "hyprutils": [ + "hyprutils" + ], + "hyprwayland-scanner": [ + "hyprwayland-scanner" + ], "nixpkgs": [ "nixpkgs" ], @@ -209,11 +293,11 @@ ] }, "locked": { - "lastModified": 1722365976, - "narHash": "sha256-Khdm+mDzYA//XaU0M+hftod+rKr5q9SSHSEuiQ0/9ow=", + "lastModified": 1728166987, + "narHash": "sha256-w6dVTguAn9zJ+7aPOhBQgDz8bn6YZ7b56cY8Kg5HJRI=", "owner": "hyprwm", "repo": "xdg-desktop-portal-hyprland", - "rev": "7f2a77ddf60390248e2a3de2261d7102a13e5341", + "rev": "fb9c8d665af0588bb087f97d0f673ddf0d501787", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 9c20b3f5..e3419ef8 100644 --- a/flake.nix +++ b/flake.nix @@ -22,6 +22,12 @@ inputs.hyprlang.follows = "hyprlang"; }; + hyprland-protocols = { + url = "github:hyprwm/hyprland-protocols"; + inputs.nixpkgs.follows = "nixpkgs"; + inputs.systems.follows = "systems"; + }; + hyprlang = { url = "github:hyprwm/hyprlang"; inputs.nixpkgs.follows = "nixpkgs"; @@ -45,7 +51,15 @@ url = "github:hyprwm/xdg-desktop-portal-hyprland"; inputs.nixpkgs.follows = "nixpkgs"; inputs.systems.follows = "systems"; + inputs.hyprland-protocols.follows = "hyprland-protocols"; inputs.hyprlang.follows = "hyprlang"; + inputs.hyprutils.follows = "hyprutils"; + inputs.hyprwayland-scanner.follows = "hyprwayland-scanner"; + }; + + pre-commit-hooks = { + url = "github:cachix/git-hooks.nix"; + inputs.nixpkgs.follows = "nixpkgs"; }; }; @@ -65,6 +79,15 @@ hyprland-extras ]; }); + pkgsCrossFor = eachSystem (system: crossSystem: + import nixpkgs { + localSystem = system; + inherit crossSystem; + overlays = with self.overlays; [ + hyprland-packages + hyprland-extras + ]; + }); in { overlays = import ./nix/overlays.nix {inherit self lib inputs;}; @@ -74,6 +97,18 @@ self.packages.${system}) // { inherit (self.packages.${system}) xdg-desktop-portal-hyprland; + pre-commit-check = inputs.pre-commit-hooks.lib.${system}.run { + src = ./.; + hooks = { + hyprland-treewide-formatter = { + enable = true; + entry = "${self.formatter.${system}}/bin/hyprland-treewide-formatter"; + pass_filenames = false; + excludes = ["subprojects"]; + always_run = true; + }; + }; + }; }); packages = eachSystem (system: { @@ -81,34 +116,32 @@ inherit (pkgsFor.${system}) # hyprland-packages - + hyprland hyprland-debug hyprland-legacy-renderer hyprland-unwrapped # hyprland-extras - + xdg-desktop-portal-hyprland ; + hyprland-cross = (pkgsCrossFor.${system} "aarch64-linux").hyprland; }); devShells = eachSystem (system: { default = pkgsFor.${system}.mkShell.override { - stdenv = pkgsFor.${system}.gcc13Stdenv; + inherit (self.packages.${system}.default) stdenv; } { name = "hyprland-shell"; - nativeBuildInputs = with pkgsFor.${system}; [ - expat - libxml2 - ]; hardeningDisable = ["fortify"]; inputsFrom = [pkgsFor.${system}.hyprland]; packages = [pkgsFor.${system}.clang-tools]; + inherit (self.checks.${system}.pre-commit-check) shellHook; }; }); - formatter = eachSystem (system: nixpkgs.legacyPackages.${system}.alejandra); + formatter = eachSystem (system: pkgsFor.${system}.callPackage ./nix/formatter.nix {}); nixosModules.default = import ./nix/module.nix inputs; homeManagerModules.default = import ./nix/hm-module.nix self; diff --git a/hyprctl/CMakeLists.txt b/hyprctl/CMakeLists.txt index 64b983e6..aaffe411 100644 --- a/hyprctl/CMakeLists.txt +++ b/hyprctl/CMakeLists.txt @@ -9,7 +9,7 @@ pkg_check_modules(deps REQUIRED IMPORTED_TARGET hyprutils>=0.1.1) add_executable(hyprctl "main.cpp") -target_link_libraries(hyprctl PUBLIC PkgConfig::deps) +target_link_libraries(hyprctl PUBLIC PkgConfig::hyprctl_deps) # binary install(TARGETS hyprctl) diff --git a/hyprctl/Makefile b/hyprctl/Makefile deleted file mode 100644 index 9798320c..00000000 --- a/hyprctl/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -all: - $(CXX) $(CXXFLAGS) -std=c++2b ./main.cpp -o ./hyprctl -clean: - rm ./hyprctl diff --git a/hyprctl/Strings.hpp b/hyprctl/Strings.hpp index 17725e77..f77626a5 100644 --- a/hyprctl/Strings.hpp +++ b/hyprctl/Strings.hpp @@ -1,5 +1,7 @@ #pragma once +#include + const std::string_view USAGE = R"#(usage: hyprctl [flags] [args...|--help] commands: diff --git a/hyprctl/hyprctl.bash b/hyprctl/hyprctl.bash index c69cca21..e26e623e 100644 --- a/hyprctl/hyprctl.bash +++ b/hyprctl/hyprctl.bash @@ -1,17 +1,17 @@ -_hyprctl_cmd_2 () { +_hyprctl_cmd_1 () { hyprctl monitors | awk '/Monitor/{ print $2 }' } _hyprctl_cmd_3 () { - hyprpm list | awk '/Plugin/{ print $4 }' + hyprctl clients | awk '/class/{print $2}' +} + +_hyprctl_cmd_2 () { + hyprctl devices | sed -n '/Keyboard at/{n; s/^\s\+//; p}' } _hyprctl_cmd_0 () { - hyprctl clients | awk '/class/{ print $2 }' -} - -_hyprctl_cmd_1 () { - hyprctl devices | sed -n '/Keyboard at/{n; s/^\s\+//; p}' + hyprpm list | awk '/Plugin/{print $4}' } _hyprctl () { @@ -23,25 +23,25 @@ _hyprctl () { local words cword _get_comp_words_by_ref -n "$COMP_WORDBREAKS" words cword - local -a literals=("cyclenext" "globalshortcuts" "cursorpos" "bordersize" "renameworkspace" "animationstyle" "focuswindow" "0" "auto" "swapnext" "forceallowsinput" "moveactive" "activebordercolor" "alphafullscreen" "wayland" "layers" "minsize" "monitors" "1" "kill" "settiled" "3" "focusmonitor" "swapwindow" "moveoutofgroup" "notify" "movecursor" "setcursor" "seterror" "movecurrentworkspacetomonitor" "4" "nomaxsize" "forcenoanims" "setprop" "-i" "-q" "togglefloating" "workspacerules" "movetoworkspace" "disable" "setignoregrouplock" "workspaces" "movegroupwindow" "closewindow" "0" "--instance" "binds" "movewindow" "splitratio" "alpha" "denywindowfromgroup" "workspace" "configerrors" "togglegroup" "getoption" "forceopaque" "keepaspectratio" "killactive" "pass" "decorations" "devices" "focuscurrentorlast" "submap" "global" "alphafullscreenoverride" "forcerendererreload" "movewindowpixel" "headless" "version" "dpms" "resizeactive" "moveintogroup" "5" "alphaoverride" "setfloating" "rollinglog" "::=" "rounding" "layouts" "moveworkspacetomonitor" "exec" "alphainactiveoverride" "alterzorder" "fakefullscreen" "nofocus" "keyword" "forcenoborder" "forcenodim" "--quiet" "pin" "output" "forcenoblur" "togglespecialworkspace" "fullscreen" "toggleopaque" "focusworkspaceoncurrentmonitor" "next" "changegroupactive" "-j" "instances" "execr" "exit" "clients" "all" "--batch" "dismissnotify" "inactivebordercolor" "switchxkblayout" "movetoworkspacesilent" "tagwindow" "movewindoworgroup" "-r" "movefocus" "focusurgentorlast" "remove" "activeworkspace" "dispatch" "create" "centerwindow" "2" "hyprpaper" "-1" "reload" "alphainactive" "systeminfo" "plugin" "dimaround" "activewindow" "swapactiveworkspaces" "splash" "sendshortcut" "maxsize" "lockactivegroup" "windowdancecompat" "forceopaqueoverriden" "lockgroups" "movecursortocorner" "x11" "prev" "1" "resizewindowpixel" "forcenoshadow") - + declare -a literals=(resizeactive 2 changegroupactive -r moveintogroup forceallowsinput 4 ::= systeminfo all layouts setprop animationstyle switchxkblayout create denywindowfromgroup headless activebordercolor exec setcursor wayland focusurgentorlast workspacerules movecurrentworkspacetomonitor movetoworkspacesilent hyprpaper alpha inactivebordercolor movegroupwindow movecursortocorner movewindowpixel prev movewindow globalshortcuts clients dimaround setignoregrouplock splash execr monitors 0 forcenoborder -q animations 1 nomaxsize splitratio moveactive pass swapnext devices layers rounding lockactivegroup 5 moveworkspacetomonitor -f -i --quiet forcenodim pin 0 1 forceopaque forcenoshadow setfloating minsize alphaoverride sendshortcut workspaces cyclenext alterzorder togglegroup lockgroups bordersize dpms focuscurrentorlast -1 --batch notify remove instances 1 3 moveoutofgroup killactive 2 movetoworkspace movecursor configerrors closewindow swapwindow tagwindow forcerendererreload centerwindow auto focuswindow seterror nofocus alphafullscreen binds version -h togglespecialworkspace fullscreen windowdancecompat 0 keyword toggleopaque 3 --instance togglefloating renameworkspace alphafullscreenoverride activeworkspace x11 kill forceopaqueoverriden output global dispatch reload forcenoblur -j event --help disable -1 activewindow keepaspectratio dismissnotify focusmonitor movefocus plugin exit workspace fullscreenstate getoption alphainactiveoverride alphainactive decorations settiled config-only descriptions resizewindowpixel fakefullscreen rollinglog swapactiveworkspaces submap next movewindoworgroup cursorpos forcenoanims focusworkspaceoncurrentmonitor maxsize) declare -A literal_transitions - literal_transitions[0]="([105]=1 [75]=2 [33]=3 [35]=4 [1]=2 [2]=2 [78]=2 [107]=5 [37]=2 [111]=4 [41]=2 [46]=2 [115]=2 [85]=6 [116]=8 [52]=2 [88]=4 [54]=2 [90]=9 [120]=2 [122]=2 [124]=2 [15]=2 [59]=10 [60]=2 [17]=11 [125]=12 [19]=2 [127]=2 [129]=2 [25]=13 [68]=2 [98]=4 [99]=2 [27]=2 [28]=14 [102]=2 [104]=4)" - literal_transitions[3]="([73]=17 [13]=2 [32]=17 [55]=17 [56]=17 [91]=17 [106]=2 [123]=2 [77]=1 [16]=2 [126]=17 [3]=1 [5]=2 [64]=17 [131]=2 [133]=17 [81]=17 [134]=17 [84]=17 [31]=17 [49]=2 [12]=2 [86]=17 [10]=17 [87]=17 [141]=17)" - literal_transitions[7]="([105]=1 [75]=2 [33]=3 [1]=2 [2]=2 [78]=2 [107]=5 [37]=2 [41]=2 [46]=2 [115]=2 [85]=6 [116]=8 [52]=2 [54]=2 [90]=9 [120]=2 [122]=2 [124]=2 [15]=2 [59]=10 [60]=2 [17]=11 [125]=12 [19]=2 [127]=2 [129]=2 [25]=13 [68]=2 [99]=2 [27]=2 [28]=14 [102]=2)" - literal_transitions[8]="([101]=2 [130]=2 [132]=2 [0]=2 [74]=2 [36]=2 [108]=2 [109]=2 [38]=2 [110]=2 [4]=2 [79]=2 [40]=2 [80]=2 [113]=2 [6]=2 [42]=2 [43]=2 [82]=2 [83]=2 [47]=2 [48]=2 [9]=2 [50]=2 [51]=2 [53]=2 [11]=2 [112]=2 [89]=2 [118]=2 [57]=2 [92]=2 [58]=2 [93]=2 [94]=2 [61]=2 [62]=2 [128]=2 [95]=2 [63]=2 [20]=2 [97]=2 [22]=2 [23]=2 [65]=2 [66]=2 [135]=2 [136]=2 [24]=2 [26]=2 [69]=2 [100]=2 [70]=2 [140]=2 [29]=2 [71]=2)" - literal_transitions[9]="([117]=20 [114]=16)" - literal_transitions[11]="([103]=2)" - literal_transitions[13]="([21]=1 [119]=1 [30]=1 [139]=1 [121]=1 [44]=1 [72]=1)" - literal_transitions[14]="([39]=2)" - literal_transitions[15]="([138]=2 [96]=2)" - literal_transitions[17]="([18]=2 [7]=2)" - literal_transitions[18]="([76]=19)" - literal_transitions[19]="([34]=4 [45]=4)" - literal_transitions[20]="([8]=2 [67]=2 [14]=2 [137]=2)" - - declare -A match_anything_transitions - match_anything_transitions=([1]=2 [0]=7 [6]=2 [15]=2 [10]=2 [5]=15 [14]=18 [7]=7 [2]=18 [16]=2 [12]=2 [11]=18) + literal_transitions[0]="([120]=14 [43]=2 [125]=21 [81]=2 [3]=21 [51]=2 [50]=2 [128]=2 [89]=2 [58]=21 [8]=2 [10]=2 [11]=3 [130]=4 [13]=5 [97]=6 [101]=2 [102]=21 [133]=7 [100]=2 [137]=2 [22]=2 [19]=2 [140]=8 [25]=2 [143]=2 [107]=9 [146]=10 [69]=2 [33]=2 [34]=2 [78]=21 [114]=2 [37]=2 [151]=2 [116]=2 [121]=13 [123]=21 [39]=11 [42]=21 [79]=15 [118]=12)" + literal_transitions[1]="([81]=2 [51]=2 [50]=2 [128]=2 [8]=2 [89]=2 [10]=2 [11]=3 [130]=4 [13]=5 [97]=6 [101]=2 [133]=7 [100]=2 [22]=2 [19]=2 [137]=2 [140]=8 [25]=2 [143]=2 [107]=9 [146]=10 [69]=2 [33]=2 [34]=2 [114]=2 [37]=2 [151]=2 [116]=2 [39]=11 [118]=12 [121]=13 [120]=14 [79]=15 [43]=2)" + literal_transitions[3]="([139]=2 [63]=16 [64]=16 [45]=16 [105]=16 [27]=2 [26]=2 [52]=4 [5]=16 [66]=2 [67]=16 [129]=16 [113]=16 [12]=2 [74]=4 [99]=2 [35]=16 [152]=16 [98]=16 [59]=16 [117]=16 [41]=16 [17]=2 [138]=16 [154]=2 [122]=16)" + literal_transitions[6]="([126]=2)" + literal_transitions[10]="([56]=2)" + literal_transitions[11]="([9]=2)" + literal_transitions[12]="([14]=19 [80]=22)" + literal_transitions[13]="([142]=2)" + literal_transitions[14]="([0]=2 [84]=2 [2]=2 [85]=2 [4]=2 [87]=2 [88]=2 [90]=2 [91]=2 [92]=2 [93]=2 [94]=2 [96]=2 [15]=2 [18]=2 [103]=2 [21]=2 [104]=2 [23]=2 [24]=2 [28]=2 [29]=2 [30]=2 [108]=2 [111]=2 [32]=2 [112]=2 [36]=2 [38]=2 [119]=2 [124]=2 [46]=2 [47]=2 [48]=2 [49]=2 [53]=2 [55]=2 [131]=2 [132]=2 [134]=2 [135]=2 [60]=2 [136]=20 [141]=2 [65]=2 [144]=2 [145]=2 [68]=2 [147]=2 [70]=2 [71]=2 [72]=2 [73]=2 [148]=2 [75]=2 [76]=2 [150]=2 [153]=2)" + literal_transitions[15]="([86]=4 [6]=4 [109]=4 [61]=4 [77]=4 [54]=4 [62]=4)" + literal_transitions[16]="([40]=2 [44]=2)" + literal_transitions[17]="([7]=23)" + literal_transitions[18]="([31]=2 [149]=2)" + literal_transitions[19]="([95]=2 [16]=2 [115]=2 [20]=2)" + literal_transitions[20]="([106]=2 [82]=2 [127]=2 [1]=2 [83]=2)" + literal_transitions[23]="([57]=21 [110]=21)" + declare -A match_anything_transitions=([6]=17 [7]=2 [0]=1 [22]=2 [5]=18 [4]=2 [2]=17 [18]=2 [11]=17 [8]=2 [9]=2 [13]=17 [10]=17 [1]=1) declare -A subword_transitions local state=0 @@ -79,21 +79,9 @@ _hyprctl () { done + local -a matches=() + local prefix="${words[$cword]}" - - local shortest_suffix="$word" - for ((i=0; i < ${#COMP_WORDBREAKS}; i++)); do - local char="${COMP_WORDBREAKS:$i:1}" - local candidate="${word##*$char}" - if [[ ${#candidate} -lt ${#shortest_suffix} ]]; then - shortest_suffix=$candidate - fi - done - local superfluous_prefix="" - if [[ "$shortest_suffix" != "$word" ]]; then - local superfluous_prefix=${word%$shortest_suffix} - fi - if [[ -v "literal_transitions[$state]" ]]; then local state_transitions_initializer=${literal_transitions[$state]} declare -A state_transitions @@ -102,25 +90,38 @@ _hyprctl () { for literal_id in "${!state_transitions[@]}"; do local literal="${literals[$literal_id]}" if [[ $literal = "${prefix}"* ]]; then - local completion=${literal#"$superfluous_prefix"} - COMPREPLY+=("$completion ") + matches+=("$literal ") fi done fi declare -A commands - commands=([5]=1 [16]=2 [12]=3 [10]=0) + commands=([7]=0 [22]=1 [8]=3 [5]=2) if [[ -v "commands[$state]" ]]; then local command_id=${commands[$state]} local completions=() - mapfile -t completions < <(_hyprctl_cmd_${command_id} "$prefix" | cut -f1) + readarray -t completions < <(_hyprctl_cmd_${command_id} "$prefix" | cut -f1) for item in "${completions[@]}"; do if [[ $item = "${prefix}"* ]]; then - COMPREPLY+=("$item") + matches+=("$item") fi done fi + local shortest_suffix="$prefix" + for ((i=0; i < ${#COMP_WORDBREAKS}; i++)); do + local char="${COMP_WORDBREAKS:$i:1}" + local candidate=${prefix##*$char} + if [[ ${#candidate} -lt ${#shortest_suffix} ]]; then + shortest_suffix=$candidate + fi + done + local superfluous_prefix="" + if [[ "$shortest_suffix" != "$prefix" ]]; then + local superfluous_prefix=${prefix%$shortest_suffix} + fi + COMPREPLY=("${matches[@]#$superfluous_prefix}") + return 0 } diff --git a/hyprctl/hyprctl.fish b/hyprctl/hyprctl.fish index c5c03e49..11309416 100644 --- a/hyprctl/hyprctl.fish +++ b/hyprctl/hyprctl.fish @@ -1,21 +1,21 @@ -function _hyprctl_3 +function _hyprctl_2 set 1 $argv[1] hyprctl monitors | awk '/Monitor/{ print $2 }' end function _hyprctl_4 set 1 $argv[1] - hyprpm list | awk '/Plugin/{ print $4 }' + hyprctl clients | awk '/class/{print $2}' +end + +function _hyprctl_3 + set 1 $argv[1] + hyprctl devices | sed -n '/Keyboard at/{n; s/^\s\+//; p}' end function _hyprctl_1 set 1 $argv[1] - hyprctl clients | awk '/class/{ print $2 }' -end - -function _hyprctl_2 - set 1 $argv[1] - hyprctl devices | sed -n '/Keyboard at/{n; s/^\s\+//; p}' + hyprpm list | awk '/Plugin/{print $4}' end function _hyprctl @@ -29,145 +29,160 @@ function _hyprctl set COMP_CWORD (count $COMP_WORDS) end - set --local literals "cyclenext" "globalshortcuts" "cursorpos" "bordersize" "renameworkspace" "animationstyle" "focuswindow" "0" "auto" "swapnext" "forceallowsinput" "moveactive" "activebordercolor" "alphafullscreen" "wayland" "layers" "minsize" "monitors" "1" "kill" "settiled" "3" "focusmonitor" "swapwindow" "moveoutofgroup" "notify" "movecursor" "setcursor" "seterror" "movecurrentworkspacetomonitor" "4" "nomaxsize" "forcenoanims" "setprop" "-i" "-q" "togglefloating" "workspacerules" "movetoworkspace" "disable" "setignoregrouplock" "workspaces" "movegroupwindow" "closewindow" "0" "--instance" "binds" "movewindow" "splitratio" "alpha" "denywindowfromgroup" "workspace" "configerrors" "togglegroup" "getoption" "forceopaque" "keepaspectratio" "killactive" "pass" "decorations" "devices" "focuscurrentorlast" "submap" "global" "alphafullscreenoverride" "forcerendererreload" "movewindowpixel" "headless" "version" "dpms" "resizeactive" "moveintogroup" "5" "alphaoverride" "setfloating" "rollinglog" "::=" "rounding" "layouts" "moveworkspacetomonitor" "exec" "alphainactiveoverride" "alterzorder" "fakefullscreen" "nofocus" "keyword" "forcenoborder" "forcenodim" "--quiet" "pin" "output" "forcenoblur" "togglespecialworkspace" "fullscreen" "toggleopaque" "focusworkspaceoncurrentmonitor" "next" "changegroupactive" "-j" "instances" "execr" "exit" "clients" "all" "--batch" "dismissnotify" "inactivebordercolor" "switchxkblayout" "movetoworkspacesilent" "tagwindow" "movewindoworgroup" "-r" "movefocus" "focusurgentorlast" "remove" "activeworkspace" "dispatch" "create" "centerwindow" "2" "hyprpaper" "-1" "reload" "alphainactive" "systeminfo" "plugin" "dimaround" "activewindow" "swapactiveworkspaces" "splash" "sendshortcut" "maxsize" "lockactivegroup" "windowdancecompat" "forceopaqueoverriden" "lockgroups" "movecursortocorner" "x11" "prev" "1" "resizewindowpixel" "forcenoshadow" + set literals "resizeactive" "2" "changegroupactive" "-r" "moveintogroup" "forceallowsinput" "4" "::=" "systeminfo" "all" "layouts" "setprop" "animationstyle" "switchxkblayout" "create" "denywindowfromgroup" "headless" "activebordercolor" "exec" "setcursor" "wayland" "focusurgentorlast" "workspacerules" "movecurrentworkspacetomonitor" "movetoworkspacesilent" "hyprpaper" "alpha" "inactivebordercolor" "movegroupwindow" "movecursortocorner" "movewindowpixel" "prev" "movewindow" "globalshortcuts" "clients" "dimaround" "setignoregrouplock" "splash" "execr" "monitors" "0" "forcenoborder" "-q" "animations" "1" "nomaxsize" "splitratio" "moveactive" "pass" "swapnext" "devices" "layers" "rounding" "lockactivegroup" "5" "moveworkspacetomonitor" "-f" "-i" "--quiet" "forcenodim" "pin" "0" "1" "forceopaque" "forcenoshadow" "setfloating" "minsize" "alphaoverride" "sendshortcut" "workspaces" "cyclenext" "alterzorder" "togglegroup" "lockgroups" "bordersize" "dpms" "focuscurrentorlast" "-1" "--batch" "notify" "remove" "instances" "1" "3" "moveoutofgroup" "killactive" "2" "movetoworkspace" "movecursor" "configerrors" "closewindow" "swapwindow" "tagwindow" "forcerendererreload" "centerwindow" "auto" "focuswindow" "seterror" "nofocus" "alphafullscreen" "binds" "version" "-h" "togglespecialworkspace" "fullscreen" "windowdancecompat" "0" "keyword" "toggleopaque" "3" "--instance" "togglefloating" "renameworkspace" "alphafullscreenoverride" "activeworkspace" "x11" "kill" "forceopaqueoverriden" "output" "global" "dispatch" "reload" "forcenoblur" "-j" "event" "--help" "disable" "-1" "activewindow" "keepaspectratio" "dismissnotify" "focusmonitor" "movefocus" "plugin" "exit" "workspace" "fullscreenstate" "getoption" "alphainactiveoverride" "alphainactive" "decorations" "settiled" "config-only" "descriptions" "resizewindowpixel" "fakefullscreen" "rollinglog" "swapactiveworkspaces" "submap" "next" "movewindoworgroup" "cursorpos" "forcenoanims" "focusworkspaceoncurrentmonitor" "maxsize" - set --local descriptions - set descriptions[1] "Focus the next window on a workspace" - set descriptions[3] "Get the current cursor pos in global layout coordinates" - set descriptions[5] "Rename a workspace" - set descriptions[7] "Focus the first window matching" - set descriptions[10] "Swap the focused window with the next window" - set descriptions[12] "Move the active window" - set descriptions[16] "List the layers" - set descriptions[18] "List active outputs with their properties" - set descriptions[20] "Get into a kill mode, where you can kill an app by clicking on it" - set descriptions[21] "Set the current window's floating state to false" - set descriptions[22] "ERROR" - set descriptions[23] "Focus a monitor" - set descriptions[24] "Swap the active window with another window" - set descriptions[25] "Move the active window out of a group" - set descriptions[26] "Send a notification using the built-in Hyprland notification system" - set descriptions[27] "Move the cursor to a specified position" - set descriptions[28] "Set the cursor theme and reloads the cursor manager" - set descriptions[29] "Set the hyprctl error string" - set descriptions[30] "Move the active workspace to a monitor" - set descriptions[31] "CONFUSED" - set descriptions[34] "Set a property of a window" - set descriptions[35] "Specify the Hyprland instance" - set descriptions[36] "Disable output" - set descriptions[37] "Toggle the current window's floating state" - set descriptions[38] "Get the list of defined workspace rules" - set descriptions[39] "Move the focused window to a workspace" - set descriptions[41] "Temporarily enable or disable binds:ignore_group_lock" - set descriptions[42] "List all workspaces with their properties" - set descriptions[43] "Swap the active window with the next or previous in a group" - set descriptions[44] "Close a specified window" - set descriptions[45] "WARNING" - set descriptions[46] "Specify the Hyprland instance" - set descriptions[47] "List all registered binds" - set descriptions[48] "Move the active window in a direction or to a monitor" - set descriptions[49] "Change the split ratio" - set descriptions[51] "Prohibit the active window from becoming or being inserted into group" - set descriptions[52] "Change the workspace" - set descriptions[53] "List all current config parsing errors" - set descriptions[54] "Toggle the current active window into a group" - set descriptions[55] "Get the config option status (values)" - set descriptions[58] "Close the active window" - set descriptions[59] "Pass the key to a specified window" - set descriptions[60] "List all decorations and their info" - set descriptions[61] "List all connected keyboards and mice" - set descriptions[62] "Switch focus from current to previously focused window" - set descriptions[63] "Change the current mapping group" - set descriptions[64] "Execute a Global Shortcut using the GlobalShortcuts portal" - set descriptions[66] "Force the renderer to reload all resources and outputs" - set descriptions[67] "Move a selected window" - set descriptions[69] "Print the Hyprland version: flags, commit and branch of build" - set descriptions[70] "Set all monitors' DPMS status" - set descriptions[71] "Resize the active window" - set descriptions[72] "Move the active window into a group" - set descriptions[73] "OK" - set descriptions[75] "Set the current window's floating state to true" - set descriptions[76] "Print tail of the log" - set descriptions[79] "List all layouts available (including plugin ones)" - set descriptions[80] "Move a workspace to a monitor" - set descriptions[81] "Execute a shell command" - set descriptions[83] "Modify the window stack order of the active or specified window" - set descriptions[84] "Toggle the focused window's internal fullscreen state" - set descriptions[86] "Issue a keyword to call a config keyword dynamically" - set descriptions[89] "Disable output" - set descriptions[90] "Pin a window" - set descriptions[91] "Allows adding/removing fake outputs to a specific backend" - set descriptions[93] "Toggle a special workspace on/off" - set descriptions[94] "Toggle the focused window's fullscreen state" - set descriptions[95] "Toggle the current window to always be opaque" - set descriptions[96] "Focus the requested workspace" - set descriptions[98] "Switch to the next window in a group" - set descriptions[99] "Output in JSON format" - set descriptions[100] "List all running Hyprland instances and their info" - set descriptions[101] "Execute a raw shell command" - set descriptions[102] "Exit the compositor with no questions asked" - set descriptions[103] "List all windows with their properties" - set descriptions[105] "Execute a batch of commands separated by ;" - set descriptions[106] "Dismiss all or up to amount of notifications" - set descriptions[108] "Set the xkb layout index for a keyboard" - set descriptions[109] "Move window doesnt switch to the workspace" - set descriptions[110] "Apply a tag to the window" - set descriptions[111] "Behave as moveintogroup" - set descriptions[112] "Refresh state after issuing the command" - set descriptions[113] "Move the focus in a direction" - set descriptions[114] "Focus the urgent window or the last window" - set descriptions[116] "Get the active workspace name and its properties" - set descriptions[117] "Issue a dispatch to call a keybind dispatcher with an arg" - set descriptions[119] "Center the active window" - set descriptions[120] "HINT" - set descriptions[121] "Interact with hyprpaper if present" - set descriptions[122] "No Icon" - set descriptions[123] "Force reload the config" - set descriptions[125] "Print system info" - set descriptions[126] "Interact with a plugin" - set descriptions[128] "Get the active window name and its properties" - set descriptions[129] "Swap the active workspaces between two monitors" - set descriptions[130] "Print the current random splash" - set descriptions[131] "On shortcut X sends shortcut Y to a specified window" - set descriptions[133] "Lock the focused group" - set descriptions[136] "Lock the groups" - set descriptions[137] "Move the cursor to the corner of the active window" - set descriptions[140] "INFO" - set descriptions[141] "Resize a selected window" + set descriptions + set descriptions[1] "Resize the active window" + set descriptions[2] "Fullscreen" + set descriptions[3] "Switch to the next window in a group" + set descriptions[4] "Refresh state after issuing the command" + set descriptions[5] "Move the active window into a group" + set descriptions[7] "CONFUSED" + set descriptions[9] "Print system info" + set descriptions[11] "List all layouts available (including plugin ones)" + set descriptions[12] "Set a property of a window" + set descriptions[14] "Set the xkb layout index for a keyboard" + set descriptions[16] "Prohibit the active window from becoming or being inserted into group" + set descriptions[19] "Execute a shell command" + set descriptions[20] "Set the cursor theme and reloads the cursor manager" + set descriptions[22] "Focus the urgent window or the last window" + set descriptions[23] "Get the list of defined workspace rules" + set descriptions[24] "Move the active workspace to a monitor" + set descriptions[25] "Move window doesnt switch to the workspace" + set descriptions[26] "Interact with hyprpaper if present" + set descriptions[29] "Swap the active window with the next or previous in a group" + set descriptions[30] "Move the cursor to the corner of the active window" + set descriptions[31] "Move a selected window" + set descriptions[33] "Move the active window in a direction or to a monitor" + set descriptions[34] "Lists all global shortcuts" + set descriptions[35] "List all windows with their properties" + set descriptions[37] "Temporarily enable or disable binds:ignore_group_lock" + set descriptions[38] "Print the current random splash" + set descriptions[39] "Execute a raw shell command" + set descriptions[40] "List active outputs with their properties" + set descriptions[43] "Disable output" + set descriptions[44] "Gets the current config info about animations and beziers" + set descriptions[47] "Change the split ratio" + set descriptions[48] "Move the active window" + set descriptions[49] "Pass the key to a specified window" + set descriptions[50] "Swap the focused window with the next window" + set descriptions[51] "List all connected keyboards and mice" + set descriptions[52] "List the layers" + set descriptions[54] "Lock the focused group" + set descriptions[55] "OK" + set descriptions[56] "Move a workspace to a monitor" + set descriptions[58] "Specify the Hyprland instance" + set descriptions[59] "Disable output" + set descriptions[61] "Pin a window" + set descriptions[62] "WARNING" + set descriptions[63] "INFO" + set descriptions[66] "Set the current window's floating state to true" + set descriptions[69] "On shortcut X sends shortcut Y to a specified window" + set descriptions[70] "List all workspaces with their properties" + set descriptions[71] "Focus the next window on a workspace" + set descriptions[72] "Modify the window stack order of the active or specified window" + set descriptions[73] "Toggle the current active window into a group" + set descriptions[74] "Lock the groups" + set descriptions[76] "Set all monitors' DPMS status" + set descriptions[77] "Switch focus from current to previously focused window" + set descriptions[78] "No Icon" + set descriptions[79] "Execute a batch of commands separated by ;" + set descriptions[80] "Send a notification using the built-in Hyprland notification system" + set descriptions[82] "List all running Hyprland instances and their info" + set descriptions[83] "Maximize no fullscreen" + set descriptions[84] "Maximize and fullscreen" + set descriptions[85] "Move the active window out of a group" + set descriptions[86] "Close the active window" + set descriptions[87] "HINT" + set descriptions[88] "Move the focused window to a workspace" + set descriptions[89] "Move the cursor to a specified position" + set descriptions[90] "List all current config parsing errors" + set descriptions[91] "Close a specified window" + set descriptions[92] "Swap the active window with another window" + set descriptions[93] "Apply a tag to the window" + set descriptions[94] "Force the renderer to reload all resources and outputs" + set descriptions[95] "Center the active window" + set descriptions[97] "Focus the first window matching" + set descriptions[98] "Set the hyprctl error string" + set descriptions[101] "List all registered binds" + set descriptions[102] "Print the Hyprland version: flags, commit and branch of build" + set descriptions[103] "Prints the help message" + set descriptions[104] "Toggle a special workspace on/off" + set descriptions[105] "Toggle the focused window's fullscreen state" + set descriptions[107] "None" + set descriptions[108] "Issue a keyword to call a config keyword dynamically" + set descriptions[109] "Toggle the current window to always be opaque" + set descriptions[110] "ERROR" + set descriptions[111] "Specify the Hyprland instance" + set descriptions[112] "Toggle the current window's floating state" + set descriptions[113] "Rename a workspace" + set descriptions[115] "Get the active workspace name and its properties" + set descriptions[117] "Get into a kill mode, where you can kill an app by clicking on it" + set descriptions[119] "Allows adding/removing fake outputs to a specific backend" + set descriptions[120] "Execute a Global Shortcut using the GlobalShortcuts portal" + set descriptions[121] "Issue a dispatch to call a keybind dispatcher with an arg" + set descriptions[122] "Force reload the config" + set descriptions[124] "Output in JSON format" + set descriptions[125] "Emits a custom event to socket2" + set descriptions[126] "Prints the help message" + set descriptions[128] "Current" + set descriptions[129] "Get the active window name and its properties" + set descriptions[131] "Dismiss all or up to amount of notifications" + set descriptions[132] "Focus a monitor" + set descriptions[133] "Move the focus in a direction" + set descriptions[134] "Interact with a plugin" + set descriptions[135] "Exit the compositor with no questions asked" + set descriptions[136] "Change the workspace" + set descriptions[137] "Sets the focused window’s fullscreen mode and the one sent to the client" + set descriptions[138] "Get the config option status (values)" + set descriptions[141] "List all decorations and their info" + set descriptions[142] "Set the current window's floating state to false" + set descriptions[144] "Return a parsable JSON with all the config options, descriptions, value types and ranges" + set descriptions[145] "Resize a selected window" + set descriptions[146] "Toggle the focused window's internal fullscreen state" + set descriptions[147] "Print tail of the log" + set descriptions[148] "Swap the active workspaces between two monitors" + set descriptions[149] "Change the current mapping group" + set descriptions[151] "Behave as moveintogroup" + set descriptions[152] "Get the current cursor pos in global layout coordinates" + set descriptions[154] "Focus the requested workspace" - set --local literal_transitions - set literal_transitions[1] "set inputs 106 76 34 36 2 3 79 108 38 112 42 47 116 86 117 53 89 55 91 121 123 125 16 60 61 18 126 20 128 130 26 69 99 100 28 29 103 105; set tos 2 3 4 5 3 3 3 6 3 5 3 3 3 7 9 3 5 3 10 3 3 3 3 11 3 12 13 3 3 3 14 3 5 3 3 15 3 5" - set literal_transitions[4] "set inputs 74 14 33 56 57 92 107 124 78 17 127 4 6 65 132 134 82 135 85 32 50 13 87 11 88 142; set tos 18 3 18 18 18 18 3 3 2 3 18 2 3 18 3 18 18 18 18 18 3 3 18 18 18 18" - set literal_transitions[8] "set inputs 106 76 34 2 3 79 108 38 42 47 116 86 117 53 55 91 121 123 125 16 60 61 18 126 20 128 130 26 69 100 28 29 103; set tos 2 3 4 3 3 3 6 3 3 3 3 7 9 3 3 10 3 3 3 3 11 3 12 13 3 3 3 14 3 3 3 15 3" - set literal_transitions[9] "set inputs 102 131 133 1 75 37 109 110 39 111 5 80 41 81 114 7 43 44 83 84 48 49 10 51 52 54 12 113 90 119 58 93 59 94 95 62 63 129 96 64 21 98 23 24 66 67 136 137 25 27 70 101 71 141 30 72; set tos 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3" - set literal_transitions[10] "set inputs 118 115; set tos 21 17" - set literal_transitions[12] "set inputs 104; set tos 3" - set literal_transitions[14] "set inputs 22 120 31 140 122 45 73; set tos 2 2 2 2 2 2 2" - set literal_transitions[15] "set inputs 40; set tos 3" - set literal_transitions[16] "set inputs 139 97; set tos 3 3" - set literal_transitions[18] "set inputs 19 8; set tos 3 3" - set literal_transitions[19] "set inputs 77; set tos 20" - set literal_transitions[20] "set inputs 35 46; set tos 5 5" - set literal_transitions[21] "set inputs 9 68 15 138; set tos 3 3 3 3" + set literal_transitions + set literal_transitions[1] "set inputs 121 44 126 82 4 52 51 129 90 59 9 11 12 131 14 98 102 103 134 101 138 23 20 141 26 144 108 147 70 34 35 79 115 38 152 117 122 124 40 43 80 119; set tos 15 3 22 3 22 3 3 3 3 22 3 3 4 5 6 7 3 22 8 3 3 3 3 9 3 3 10 11 3 3 3 22 3 3 3 3 14 22 12 22 16 13" + set literal_transitions[2] "set inputs 82 52 51 129 9 90 11 12 131 14 98 102 134 101 23 20 138 141 26 144 108 147 70 34 35 115 38 152 117 40 119 122 121 80 44; set tos 3 3 3 3 3 3 3 4 5 6 7 3 8 3 3 3 3 9 3 3 10 11 3 3 3 3 3 3 3 12 13 14 15 16 3" + set literal_transitions[4] "set inputs 140 64 65 46 106 28 27 53 6 67 68 130 114 13 75 100 36 153 99 60 118 42 18 139 155 123; set tos 3 17 17 17 17 3 3 5 17 3 17 17 17 3 5 3 17 17 17 17 17 17 3 17 3 17" + set literal_transitions[7] "set inputs 127; set tos 3" + set literal_transitions[11] "set inputs 57; set tos 3" + set literal_transitions[12] "set inputs 10; set tos 3" + set literal_transitions[13] "set inputs 15 81; set tos 20 23" + set literal_transitions[14] "set inputs 143; set tos 3" + set literal_transitions[15] "set inputs 1 85 3 86 5 88 89 91 92 93 94 95 97 16 19 104 22 105 24 25 29 30 31 109 112 33 113 37 39 120 125 47 48 49 50 54 56 132 133 135 136 61 137 142 66 145 146 69 148 71 72 73 74 149 76 77 151 154; set tos 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 21 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3" + set literal_transitions[16] "set inputs 87 7 110 62 78 55 63; set tos 5 5 5 5 5 5 5" + set literal_transitions[17] "set inputs 41 45; set tos 3 3" + set literal_transitions[18] "set inputs 8; set tos 24" + set literal_transitions[19] "set inputs 32 150; set tos 3 3" + set literal_transitions[20] "set inputs 96 17 116 21; set tos 3 3 3 3" + set literal_transitions[21] "set inputs 107 83 128 2 84; set tos 3 3 3 3 3" + set literal_transitions[24] "set inputs 58 111; set tos 22 22" - set --local match_anything_transitions_from 2 1 7 16 11 6 15 8 3 17 13 12 - set --local match_anything_transitions_to 3 8 3 3 3 16 19 8 19 3 3 19 + set match_anything_transitions_from 7 8 1 23 6 5 3 19 12 9 10 14 11 2 + set match_anything_transitions_to 18 3 2 3 19 3 18 3 18 3 3 18 18 2 - set --local state 1 - set --local word_index 2 + set state 1 + set word_index 2 while test $word_index -lt $COMP_CWORD - set --local -- word $COMP_WORDS[$word_index] + set -- word $COMP_WORDS[$word_index] if set --query literal_transitions[$state] && test -n $literal_transitions[$state] - set --local --erase inputs - set --local --erase tos + set --erase inputs + set --erase tos eval $literal_transitions[$state] if contains -- $word $literals - set --local literal_matched 0 + set literal_matched 0 for literal_id in (seq 1 (count $literals)) if test $literals[$literal_id] = $word - set --local index (contains --index -- $literal_id $inputs) + set index (contains --index -- $literal_id $inputs) set state $tos[$index] set word_index (math $word_index + 1) set literal_matched 1 @@ -181,7 +196,7 @@ function _hyprctl end if set --query match_anything_transitions_from[$state] && test -n $match_anything_transitions_from[$state] - set --local index (contains --index -- $state $match_anything_transitions_from) + set index (contains --index -- $state $match_anything_transitions_from) set state $match_anything_transitions_to[$index] set word_index (math $word_index + 1) continue @@ -191,8 +206,8 @@ function _hyprctl end if set --query literal_transitions[$state] && test -n $literal_transitions[$state] - set --local --erase inputs - set --local --erase tos + set --erase inputs + set --erase tos eval $literal_transitions[$state] for literal_id in $inputs if test -n $descriptions[$literal_id] @@ -203,14 +218,14 @@ function _hyprctl end end - set command_states 6 17 13 11 - set command_ids 2 3 4 1 + set command_states 8 23 9 6 + set command_ids 1 2 4 3 if contains $state $command_states - set --local index (contains --index $state $command_states) - set --local function_id $command_ids[$index] - set --local function_name _hyprctl_$function_id - set --local --erase inputs - set --local --erase tos + set index (contains --index $state $command_states) + set function_id $command_ids[$index] + set function_name _hyprctl_$function_id + set --erase inputs + set --erase tos $function_name "$COMP_WORDS[$COMP_CWORD]" end diff --git a/hyprctl/hyprctl.usage b/hyprctl/hyprctl.usage index 298d253e..b2c55682 100644 --- a/hyprctl/hyprctl.usage +++ b/hyprctl/hyprctl.usage @@ -2,13 +2,14 @@ # Repo: https://github.com/adaszko/complgen # Generate completion scripts: "complgen aot --bash-script hyprctl.bash --fish-script hyprctl.fish --zsh-script hyprctl.zsh ./hyprctl.usage" -hyprctl []... +hyprctl []... ::= (-i | --instance) "Specify the Hyprland instance" | (-j) "Output in JSON format" | (-r) "Refresh state after issuing the command" | (--batch) "Execute a batch of commands separated by ;" | (-q | --quiet) "Disable output" + | (-h | --help) "Prints the help message" ; ::= {{{ hyprctl clients | awk '/class/{print $2}' }}}; @@ -59,16 +60,18 @@ hyprctl []... ::= (activewindow) "Get the active window name and its properties" | (activeworkspace) "Get the active workspace name and its properties" + | (animations) "Gets the current config info about animations and beziers" | (binds) "List all registered binds" | (clients) "List all windows with their properties" | (configerrors) "List all current config parsing errors" | (cursorpos) "Get the current cursor pos in global layout coordinates" | (decorations ) "List all decorations and their info" + | (descriptions) "Return a parsable JSON with all the config options, descriptions, value types and ranges" | (devices) "List all connected keyboards and mice" | (dismissnotify ) "Dismiss all or up to amount of notifications" | (dispatch ) "Issue a dispatch to call a keybind dispatcher with an arg" | (getoption) "Get the config option status (values)" - | (globalshortcuts) "" + | (globalshortcuts) "Lists all global shortcuts" | (hyprpaper) "Interact with hyprpaper if present" | (instances) "List all running Hyprland instances and their info" | (keyword ) "Issue a keyword to call a config keyword dynamically" @@ -79,8 +82,8 @@ hyprctl []... | (notify ) "Send a notification using the built-in Hyprland notification system" | (output (create (wayland | x11 | headless | auto) | remove )) "Allows adding/removing fake outputs to a specific backend" | (plugin ) "Interact with a plugin" - | (reload) "Force reload the config" - | (rollinglog) "Print tail of the log" + | (reload [config-only]) "Force reload the config" + | (rollinglog [-f]) "Print tail of the log" | (setcursor) "Set the cursor theme and reloads the cursor manager" | (seterror [disable]) "Set the hyprctl error string" | (setprop ) "Set a property of a window" @@ -92,6 +95,13 @@ hyprctl []... | (workspaces) "List all workspaces with their properties" ; + ::= (-1) "Current" + | (0) "None" + | (1) "Maximize no fullscreen" + | (2) "Fullscreen" + | (3) "Maximize and fullscreen" + ; + ::= (exec) "Execute a shell command" | (execr) "Execute a raw shell command" | (pass) "Pass the key to a specified window" @@ -106,6 +116,7 @@ hyprctl []... | (settiled) "Set the current window's floating state to false" | (fullscreen) "Toggle the focused window's fullscreen state" | (fakefullscreen) "Toggle the focused window's internal fullscreen state" + | (fullscreenstate ) "Sets the focused window’s fullscreen mode and the one sent to the client" | (dpms) "Set all monitors' DPMS status" | (pin) "Pin a window" | (movefocus) "Move the focus in a direction" @@ -148,4 +159,5 @@ hyprctl []... | (setignoregrouplock) "Temporarily enable or disable binds:ignore_group_lock" | (global) "Execute a Global Shortcut using the GlobalShortcuts portal" | (submap) "Change the current mapping group" + | (event) "Emits a custom event to socket2" ; diff --git a/hyprctl/hyprctl.zsh b/hyprctl/hyprctl.zsh index aeac9663..9a858100 100644 --- a/hyprctl/hyprctl.zsh +++ b/hyprctl/hyprctl.zsh @@ -1,145 +1,160 @@ #compdef hyprctl -_hyprctl_cmd_2 () { +_hyprctl_cmd_1 () { hyprctl monitors | awk '/Monitor/{ print $2 }' } _hyprctl_cmd_3 () { - hyprpm list | awk '/Plugin/{ print $4 }' + hyprctl clients | awk '/class/{print $2}' } -_hyprctl_cmd_0 () { - hyprctl clients | awk '/class/{ print $2 }' -} - -_hyprctl_cmd_1 () { +_hyprctl_cmd_2 () { hyprctl devices | sed -n '/Keyboard at/{n; s/^\s\+//; p}' } +_hyprctl_cmd_0 () { + hyprpm list | awk '/Plugin/{print $4}' +} + _hyprctl () { - local -a literals=("cyclenext" "globalshortcuts" "cursorpos" "bordersize" "renameworkspace" "animationstyle" "focuswindow" "0" "auto" "swapnext" "forceallowsinput" "moveactive" "activebordercolor" "alphafullscreen" "wayland" "layers" "minsize" "monitors" "1" "kill" "settiled" "3" "focusmonitor" "swapwindow" "moveoutofgroup" "notify" "movecursor" "setcursor" "seterror" "movecurrentworkspacetomonitor" "4" "nomaxsize" "forcenoanims" "setprop" "-i" "-q" "togglefloating" "workspacerules" "movetoworkspace" "disable" "setignoregrouplock" "workspaces" "movegroupwindow" "closewindow" "0" "--instance" "binds" "movewindow" "splitratio" "alpha" "denywindowfromgroup" "workspace" "configerrors" "togglegroup" "getoption" "forceopaque" "keepaspectratio" "killactive" "pass" "decorations" "devices" "focuscurrentorlast" "submap" "global" "alphafullscreenoverride" "forcerendererreload" "movewindowpixel" "headless" "version" "dpms" "resizeactive" "moveintogroup" "5" "alphaoverride" "setfloating" "rollinglog" "::=" "rounding" "layouts" "moveworkspacetomonitor" "exec" "alphainactiveoverride" "alterzorder" "fakefullscreen" "nofocus" "keyword" "forcenoborder" "forcenodim" "--quiet" "pin" "output" "forcenoblur" "togglespecialworkspace" "fullscreen" "toggleopaque" "focusworkspaceoncurrentmonitor" "next" "changegroupactive" "-j" "instances" "execr" "exit" "clients" "all" "--batch" "dismissnotify" "inactivebordercolor" "switchxkblayout" "movetoworkspacesilent" "tagwindow" "movewindoworgroup" "-r" "movefocus" "focusurgentorlast" "remove" "activeworkspace" "dispatch" "create" "centerwindow" "2" "hyprpaper" "-1" "reload" "alphainactive" "systeminfo" "plugin" "dimaround" "activewindow" "swapactiveworkspaces" "splash" "sendshortcut" "maxsize" "lockactivegroup" "windowdancecompat" "forceopaqueoverriden" "lockgroups" "movecursortocorner" "x11" "prev" "1" "resizewindowpixel" "forcenoshadow") + local -a literals=("resizeactive" "2" "changegroupactive" "-r" "moveintogroup" "forceallowsinput" "4" "::=" "systeminfo" "all" "layouts" "setprop" "animationstyle" "switchxkblayout" "create" "denywindowfromgroup" "headless" "activebordercolor" "exec" "setcursor" "wayland" "focusurgentorlast" "workspacerules" "movecurrentworkspacetomonitor" "movetoworkspacesilent" "hyprpaper" "alpha" "inactivebordercolor" "movegroupwindow" "movecursortocorner" "movewindowpixel" "prev" "movewindow" "globalshortcuts" "clients" "dimaround" "setignoregrouplock" "splash" "execr" "monitors" "0" "forcenoborder" "-q" "animations" "1" "nomaxsize" "splitratio" "moveactive" "pass" "swapnext" "devices" "layers" "rounding" "lockactivegroup" "5" "moveworkspacetomonitor" "-f" "-i" "--quiet" "forcenodim" "pin" "0" "1" "forceopaque" "forcenoshadow" "setfloating" "minsize" "alphaoverride" "sendshortcut" "workspaces" "cyclenext" "alterzorder" "togglegroup" "lockgroups" "bordersize" "dpms" "focuscurrentorlast" "-1" "--batch" "notify" "remove" "instances" "1" "3" "moveoutofgroup" "killactive" "2" "movetoworkspace" "movecursor" "configerrors" "closewindow" "swapwindow" "tagwindow" "forcerendererreload" "centerwindow" "auto" "focuswindow" "seterror" "nofocus" "alphafullscreen" "binds" "version" "-h" "togglespecialworkspace" "fullscreen" "windowdancecompat" "0" "keyword" "toggleopaque" "3" "--instance" "togglefloating" "renameworkspace" "alphafullscreenoverride" "activeworkspace" "x11" "kill" "forceopaqueoverriden" "output" "global" "dispatch" "reload" "forcenoblur" "-j" "event" "--help" "disable" "-1" "activewindow" "keepaspectratio" "dismissnotify" "focusmonitor" "movefocus" "plugin" "exit" "workspace" "fullscreenstate" "getoption" "alphainactiveoverride" "alphainactive" "decorations" "settiled" "config-only" "descriptions" "resizewindowpixel" "fakefullscreen" "rollinglog" "swapactiveworkspaces" "submap" "next" "movewindoworgroup" "cursorpos" "forcenoanims" "focusworkspaceoncurrentmonitor" "maxsize") local -A descriptions - descriptions[1]="Focus the next window on a workspace" - descriptions[3]="Get the current cursor pos in global layout coordinates" - descriptions[5]="Rename a workspace" - descriptions[7]="Focus the first window matching" - descriptions[10]="Swap the focused window with the next window" - descriptions[12]="Move the active window" - descriptions[16]="List the layers" - descriptions[18]="List active outputs with their properties" - descriptions[20]="Get into a kill mode, where you can kill an app by clicking on it" - descriptions[21]="Set the current window's floating state to false" - descriptions[22]="ERROR" - descriptions[23]="Focus a monitor" - descriptions[24]="Swap the active window with another window" - descriptions[25]="Move the active window out of a group" - descriptions[26]="Send a notification using the built-in Hyprland notification system" - descriptions[27]="Move the cursor to a specified position" - descriptions[28]="Set the cursor theme and reloads the cursor manager" - descriptions[29]="Set the hyprctl error string" - descriptions[30]="Move the active workspace to a monitor" - descriptions[31]="CONFUSED" - descriptions[34]="Set a property of a window" - descriptions[35]="Specify the Hyprland instance" - descriptions[36]="Disable output" - descriptions[37]="Toggle the current window's floating state" - descriptions[38]="Get the list of defined workspace rules" - descriptions[39]="Move the focused window to a workspace" - descriptions[41]="Temporarily enable or disable binds:ignore_group_lock" - descriptions[42]="List all workspaces with their properties" - descriptions[43]="Swap the active window with the next or previous in a group" - descriptions[44]="Close a specified window" - descriptions[45]="WARNING" - descriptions[46]="Specify the Hyprland instance" - descriptions[47]="List all registered binds" - descriptions[48]="Move the active window in a direction or to a monitor" - descriptions[49]="Change the split ratio" - descriptions[51]="Prohibit the active window from becoming or being inserted into group" - descriptions[52]="Change the workspace" - descriptions[53]="List all current config parsing errors" - descriptions[54]="Toggle the current active window into a group" - descriptions[55]="Get the config option status (values)" - descriptions[58]="Close the active window" - descriptions[59]="Pass the key to a specified window" - descriptions[60]="List all decorations and their info" - descriptions[61]="List all connected keyboards and mice" - descriptions[62]="Switch focus from current to previously focused window" - descriptions[63]="Change the current mapping group" - descriptions[64]="Execute a Global Shortcut using the GlobalShortcuts portal" - descriptions[66]="Force the renderer to reload all resources and outputs" - descriptions[67]="Move a selected window" - descriptions[69]="Print the Hyprland version: flags, commit and branch of build" - descriptions[70]="Set all monitors' DPMS status" - descriptions[71]="Resize the active window" - descriptions[72]="Move the active window into a group" - descriptions[73]="OK" - descriptions[75]="Set the current window's floating state to true" - descriptions[76]="Print tail of the log" - descriptions[79]="List all layouts available (including plugin ones)" - descriptions[80]="Move a workspace to a monitor" - descriptions[81]="Execute a shell command" - descriptions[83]="Modify the window stack order of the active or specified window" - descriptions[84]="Toggle the focused window's internal fullscreen state" - descriptions[86]="Issue a keyword to call a config keyword dynamically" - descriptions[89]="Disable output" - descriptions[90]="Pin a window" - descriptions[91]="Allows adding/removing fake outputs to a specific backend" - descriptions[93]="Toggle a special workspace on/off" - descriptions[94]="Toggle the focused window's fullscreen state" - descriptions[95]="Toggle the current window to always be opaque" - descriptions[96]="Focus the requested workspace" - descriptions[98]="Switch to the next window in a group" - descriptions[99]="Output in JSON format" - descriptions[100]="List all running Hyprland instances and their info" - descriptions[101]="Execute a raw shell command" - descriptions[102]="Exit the compositor with no questions asked" - descriptions[103]="List all windows with their properties" - descriptions[105]="Execute a batch of commands separated by ;" - descriptions[106]="Dismiss all or up to amount of notifications" - descriptions[108]="Set the xkb layout index for a keyboard" - descriptions[109]="Move window doesnt switch to the workspace" - descriptions[110]="Apply a tag to the window" - descriptions[111]="Behave as moveintogroup" - descriptions[112]="Refresh state after issuing the command" - descriptions[113]="Move the focus in a direction" - descriptions[114]="Focus the urgent window or the last window" - descriptions[116]="Get the active workspace name and its properties" - descriptions[117]="Issue a dispatch to call a keybind dispatcher with an arg" - descriptions[119]="Center the active window" - descriptions[120]="HINT" - descriptions[121]="Interact with hyprpaper if present" - descriptions[122]="No Icon" - descriptions[123]="Force reload the config" - descriptions[125]="Print system info" - descriptions[126]="Interact with a plugin" - descriptions[128]="Get the active window name and its properties" - descriptions[129]="Swap the active workspaces between two monitors" - descriptions[130]="Print the current random splash" - descriptions[131]="On shortcut X sends shortcut Y to a specified window" - descriptions[133]="Lock the focused group" - descriptions[136]="Lock the groups" - descriptions[137]="Move the cursor to the corner of the active window" - descriptions[140]="INFO" - descriptions[141]="Resize a selected window" + descriptions[1]="Resize the active window" + descriptions[2]="Fullscreen" + descriptions[3]="Switch to the next window in a group" + descriptions[4]="Refresh state after issuing the command" + descriptions[5]="Move the active window into a group" + descriptions[7]="CONFUSED" + descriptions[9]="Print system info" + descriptions[11]="List all layouts available (including plugin ones)" + descriptions[12]="Set a property of a window" + descriptions[14]="Set the xkb layout index for a keyboard" + descriptions[16]="Prohibit the active window from becoming or being inserted into group" + descriptions[19]="Execute a shell command" + descriptions[20]="Set the cursor theme and reloads the cursor manager" + descriptions[22]="Focus the urgent window or the last window" + descriptions[23]="Get the list of defined workspace rules" + descriptions[24]="Move the active workspace to a monitor" + descriptions[25]="Move window doesnt switch to the workspace" + descriptions[26]="Interact with hyprpaper if present" + descriptions[29]="Swap the active window with the next or previous in a group" + descriptions[30]="Move the cursor to the corner of the active window" + descriptions[31]="Move a selected window" + descriptions[33]="Move the active window in a direction or to a monitor" + descriptions[34]="Lists all global shortcuts" + descriptions[35]="List all windows with their properties" + descriptions[37]="Temporarily enable or disable binds:ignore_group_lock" + descriptions[38]="Print the current random splash" + descriptions[39]="Execute a raw shell command" + descriptions[40]="List active outputs with their properties" + descriptions[43]="Disable output" + descriptions[44]="Gets the current config info about animations and beziers" + descriptions[47]="Change the split ratio" + descriptions[48]="Move the active window" + descriptions[49]="Pass the key to a specified window" + descriptions[50]="Swap the focused window with the next window" + descriptions[51]="List all connected keyboards and mice" + descriptions[52]="List the layers" + descriptions[54]="Lock the focused group" + descriptions[55]="OK" + descriptions[56]="Move a workspace to a monitor" + descriptions[58]="Specify the Hyprland instance" + descriptions[59]="Disable output" + descriptions[61]="Pin a window" + descriptions[62]="WARNING" + descriptions[63]="INFO" + descriptions[66]="Set the current window's floating state to true" + descriptions[69]="On shortcut X sends shortcut Y to a specified window" + descriptions[70]="List all workspaces with their properties" + descriptions[71]="Focus the next window on a workspace" + descriptions[72]="Modify the window stack order of the active or specified window" + descriptions[73]="Toggle the current active window into a group" + descriptions[74]="Lock the groups" + descriptions[76]="Set all monitors' DPMS status" + descriptions[77]="Switch focus from current to previously focused window" + descriptions[78]="No Icon" + descriptions[79]="Execute a batch of commands separated by ;" + descriptions[80]="Send a notification using the built-in Hyprland notification system" + descriptions[82]="List all running Hyprland instances and their info" + descriptions[83]="Maximize no fullscreen" + descriptions[84]="Maximize and fullscreen" + descriptions[85]="Move the active window out of a group" + descriptions[86]="Close the active window" + descriptions[87]="HINT" + descriptions[88]="Move the focused window to a workspace" + descriptions[89]="Move the cursor to a specified position" + descriptions[90]="List all current config parsing errors" + descriptions[91]="Close a specified window" + descriptions[92]="Swap the active window with another window" + descriptions[93]="Apply a tag to the window" + descriptions[94]="Force the renderer to reload all resources and outputs" + descriptions[95]="Center the active window" + descriptions[97]="Focus the first window matching" + descriptions[98]="Set the hyprctl error string" + descriptions[101]="List all registered binds" + descriptions[102]="Print the Hyprland version: flags, commit and branch of build" + descriptions[103]="Prints the help message" + descriptions[104]="Toggle a special workspace on/off" + descriptions[105]="Toggle the focused window's fullscreen state" + descriptions[107]="None" + descriptions[108]="Issue a keyword to call a config keyword dynamically" + descriptions[109]="Toggle the current window to always be opaque" + descriptions[110]="ERROR" + descriptions[111]="Specify the Hyprland instance" + descriptions[112]="Toggle the current window's floating state" + descriptions[113]="Rename a workspace" + descriptions[115]="Get the active workspace name and its properties" + descriptions[117]="Get into a kill mode, where you can kill an app by clicking on it" + descriptions[119]="Allows adding/removing fake outputs to a specific backend" + descriptions[120]="Execute a Global Shortcut using the GlobalShortcuts portal" + descriptions[121]="Issue a dispatch to call a keybind dispatcher with an arg" + descriptions[122]="Force reload the config" + descriptions[124]="Output in JSON format" + descriptions[125]="Emits a custom event to socket2" + descriptions[126]="Prints the help message" + descriptions[128]="Current" + descriptions[129]="Get the active window name and its properties" + descriptions[131]="Dismiss all or up to amount of notifications" + descriptions[132]="Focus a monitor" + descriptions[133]="Move the focus in a direction" + descriptions[134]="Interact with a plugin" + descriptions[135]="Exit the compositor with no questions asked" + descriptions[136]="Change the workspace" + descriptions[137]="Sets the focused window’s fullscreen mode and the one sent to the client" + descriptions[138]="Get the config option status (values)" + descriptions[141]="List all decorations and their info" + descriptions[142]="Set the current window's floating state to false" + descriptions[144]="Return a parsable JSON with all the config options, descriptions, value types and ranges" + descriptions[145]="Resize a selected window" + descriptions[146]="Toggle the focused window's internal fullscreen state" + descriptions[147]="Print tail of the log" + descriptions[148]="Swap the active workspaces between two monitors" + descriptions[149]="Change the current mapping group" + descriptions[151]="Behave as moveintogroup" + descriptions[152]="Get the current cursor pos in global layout coordinates" + descriptions[154]="Focus the requested workspace" local -A literal_transitions - literal_transitions[1]="([106]=2 [76]=3 [34]=4 [36]=5 [2]=3 [3]=3 [79]=3 [108]=6 [38]=3 [112]=5 [42]=3 [47]=3 [116]=3 [86]=7 [117]=9 [53]=3 [89]=5 [55]=3 [91]=10 [121]=3 [123]=3 [125]=3 [16]=3 [60]=11 [61]=3 [18]=12 [126]=13 [20]=3 [128]=3 [130]=3 [26]=14 [69]=3 [99]=5 [100]=3 [28]=3 [29]=15 [103]=3 [105]=5)" - literal_transitions[4]="([74]=18 [14]=3 [33]=18 [56]=18 [57]=18 [92]=18 [107]=3 [124]=3 [78]=2 [17]=3 [127]=18 [4]=2 [6]=3 [65]=18 [132]=3 [134]=18 [82]=18 [135]=18 [85]=18 [32]=18 [50]=3 [13]=3 [87]=18 [11]=18 [88]=18 [142]=18)" - literal_transitions[8]="([106]=2 [76]=3 [34]=4 [2]=3 [3]=3 [79]=3 [108]=6 [38]=3 [42]=3 [47]=3 [116]=3 [86]=7 [117]=9 [53]=3 [55]=3 [91]=10 [121]=3 [123]=3 [125]=3 [16]=3 [60]=11 [61]=3 [18]=12 [126]=13 [20]=3 [128]=3 [130]=3 [26]=14 [69]=3 [100]=3 [28]=3 [29]=15 [103]=3)" - literal_transitions[9]="([102]=3 [131]=3 [133]=3 [1]=3 [75]=3 [37]=3 [109]=3 [110]=3 [39]=3 [111]=3 [5]=3 [80]=3 [41]=3 [81]=3 [114]=3 [7]=3 [43]=3 [44]=3 [83]=3 [84]=3 [48]=3 [49]=3 [10]=3 [51]=3 [52]=3 [54]=3 [12]=3 [113]=3 [90]=3 [119]=3 [58]=3 [93]=3 [59]=3 [94]=3 [95]=3 [62]=3 [63]=3 [129]=3 [96]=3 [64]=3 [21]=3 [98]=3 [23]=3 [24]=3 [66]=3 [67]=3 [136]=3 [137]=3 [25]=3 [27]=3 [70]=3 [101]=3 [71]=3 [141]=3 [30]=3 [72]=3)" - literal_transitions[10]="([118]=21 [115]=17)" - literal_transitions[12]="([104]=3)" - literal_transitions[14]="([22]=2 [120]=2 [31]=2 [140]=2 [122]=2 [45]=2 [73]=2)" - literal_transitions[15]="([40]=3)" - literal_transitions[16]="([139]=3 [97]=3)" - literal_transitions[18]="([19]=3 [8]=3)" - literal_transitions[19]="([77]=20)" - literal_transitions[20]="([35]=5 [46]=5)" - literal_transitions[21]="([9]=3 [68]=3 [15]=3 [138]=3)" + literal_transitions[1]="([121]=15 [44]=3 [126]=22 [82]=3 [4]=22 [52]=3 [51]=3 [129]=3 [90]=3 [59]=22 [9]=3 [11]=3 [12]=4 [131]=5 [14]=6 [98]=7 [102]=3 [103]=22 [134]=8 [101]=3 [138]=3 [23]=3 [20]=3 [141]=9 [26]=3 [144]=3 [108]=10 [147]=11 [70]=3 [34]=3 [35]=3 [79]=22 [115]=3 [38]=3 [152]=3 [117]=3 [122]=14 [124]=22 [40]=12 [43]=22 [80]=16 [119]=13)" + literal_transitions[2]="([82]=3 [52]=3 [51]=3 [129]=3 [9]=3 [90]=3 [11]=3 [12]=4 [131]=5 [14]=6 [98]=7 [102]=3 [134]=8 [101]=3 [23]=3 [20]=3 [138]=3 [141]=9 [26]=3 [144]=3 [108]=10 [147]=11 [70]=3 [34]=3 [35]=3 [115]=3 [38]=3 [152]=3 [117]=3 [40]=12 [119]=13 [122]=14 [121]=15 [80]=16 [44]=3)" + literal_transitions[4]="([140]=3 [64]=17 [65]=17 [46]=17 [106]=17 [28]=3 [27]=3 [53]=5 [6]=17 [67]=3 [68]=17 [130]=17 [114]=17 [13]=3 [75]=5 [100]=3 [36]=17 [153]=17 [99]=17 [60]=17 [118]=17 [42]=17 [18]=3 [139]=17 [155]=3 [123]=17)" + literal_transitions[7]="([127]=3)" + literal_transitions[11]="([57]=3)" + literal_transitions[12]="([10]=3)" + literal_transitions[13]="([15]=20 [81]=23)" + literal_transitions[14]="([143]=3)" + literal_transitions[15]="([1]=3 [85]=3 [3]=3 [86]=3 [5]=3 [88]=3 [89]=3 [91]=3 [92]=3 [93]=3 [94]=3 [95]=3 [97]=3 [16]=3 [19]=3 [104]=3 [22]=3 [105]=3 [24]=3 [25]=3 [29]=3 [30]=3 [31]=3 [109]=3 [112]=3 [33]=3 [113]=3 [37]=3 [39]=3 [120]=3 [125]=3 [47]=3 [48]=3 [49]=3 [50]=3 [54]=3 [56]=3 [132]=3 [133]=3 [135]=3 [136]=3 [61]=3 [137]=21 [142]=3 [66]=3 [145]=3 [146]=3 [69]=3 [148]=3 [71]=3 [72]=3 [73]=3 [74]=3 [149]=3 [76]=3 [77]=3 [151]=3 [154]=3)" + literal_transitions[16]="([87]=5 [7]=5 [110]=5 [62]=5 [78]=5 [55]=5 [63]=5)" + literal_transitions[17]="([41]=3 [45]=3)" + literal_transitions[18]="([8]=24)" + literal_transitions[19]="([32]=3 [150]=3)" + literal_transitions[20]="([96]=3 [17]=3 [116]=3 [21]=3)" + literal_transitions[21]="([107]=3 [83]=3 [128]=3 [2]=3 [84]=3)" + literal_transitions[24]="([58]=22 [111]=22)" local -A match_anything_transitions - match_anything_transitions=([2]=3 [1]=8 [7]=3 [16]=3 [11]=3 [6]=16 [15]=19 [8]=8 [3]=19 [17]=3 [13]=3 [12]=19) + match_anything_transitions=([7]=18 [8]=3 [1]=2 [23]=3 [6]=19 [5]=3 [3]=18 [19]=3 [12]=18 [9]=3 [10]=3 [14]=18 [11]=18 [2]=2) declare -A subword_transitions @@ -199,7 +214,7 @@ _hyprctl () { fi done fi - local -A commands=([6]=1 [17]=2 [13]=3 [11]=0) + local -A commands=([8]=0 [23]=1 [9]=3 [6]=2) if [[ -v "commands[$state]" ]]; then local command_id=${commands[$state]} @@ -252,4 +267,8 @@ _hyprctl () { return 0 } -compdef _hyprctl hyprctl +if [[ $ZSH_EVAL_CONTEXT =~ :file$ ]]; then + compdef _hyprctl hyprctl +else + _hyprctl +fi diff --git a/hyprctl/main.cpp b/hyprctl/main.cpp index 336d479e..1f7f8d74 100644 --- a/hyprctl/main.cpp +++ b/hyprctl/main.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -26,6 +27,7 @@ #include #include #include +#include using namespace Hyprutils::String; #include "Strings.hpp" @@ -43,11 +45,11 @@ struct SInstanceData { bool valid = true; }; -void log(std::string str) { +void log(const std::string& str) { if (quiet) return; - std::cout << str << "\n"; + std::println("{}", str); } std::string getRuntimeDir() { @@ -104,7 +106,7 @@ std::vector instances() { static volatile bool sigintReceived = false; void intHandler(int sig) { sigintReceived = true; - std::cout << "[hyprctl] SIGINT received, closing connection" << std::endl; + std::println("[hyprctl] SIGINT received, closing connection"); } int rollingRead(const int socket) { @@ -113,13 +115,13 @@ int rollingRead(const int socket) { constexpr size_t BUFFER_SIZE = 8192; std::array buffer = {0}; - int sizeWritten = 0; - std::cout << "[hyprctl] reading from socket following up log:" << std::endl; + long sizeWritten = 0; + std::println("[hyprctl] reading from socket following up log:"); while (!sigintReceived) { sizeWritten = read(socket, buffer.data(), BUFFER_SIZE); if (sizeWritten < 0 && errno != EAGAIN) { if (errno != EINTR) - std::cout << "Couldn't read (5) " << strerror(errno) << ":" << errno << std::endl; + std::println("Couldn't read (5): {}: {}", strerror(errno), errno); close(socket); return 5; } @@ -128,7 +130,7 @@ int rollingRead(const int socket) { break; if (sizeWritten > 0) { - std::cout << std::string(buffer.data(), sizeWritten); + std::println("{}", std::string(buffer.data(), sizeWritten)); buffer.fill('\0'); } @@ -286,12 +288,12 @@ void instancesRequest(bool json) { std::vector inst = instances(); if (!json) { - for (auto& el : inst) { + for (auto const& el : inst) { result += std::format("instance {}:\n\ttime: {}\n\tpid: {}\n\twl socket: {}\n\n", el.id, el.time, el.pid, el.wlSocket); } } else { result += '['; - for (auto& el : inst) { + for (auto const& el : inst) { result += std::format(R"#( {{ "instance": "{}", @@ -322,7 +324,7 @@ int main(int argc, char** argv) { bool parseArgs = true; if (argc < 2) { - std::cout << USAGE << std::endl; + std::println("{}", USAGE); return 1; } @@ -359,7 +361,7 @@ int main(int argc, char** argv) { ++i; if (i >= ARGS.size()) { - std::cout << USAGE << std::endl; + std::println("{}", USAGE); return 1; } @@ -370,24 +372,24 @@ int main(int argc, char** argv) { const std::string& cmd = ARGS[0]; if (cmd == "hyprpaper") { - std::cout << HYPRPAPER_HELP << std::endl; + std::println("{}", HYPRPAPER_HELP); } else if (cmd == "notify") { - std::cout << NOTIFY_HELP << std::endl; + std::println("{}", NOTIFY_HELP); } else if (cmd == "output") { - std::cout << OUTPUT_HELP << std::endl; + std::println("{}", OUTPUT_HELP); } else if (cmd == "plugin") { - std::cout << PLUGIN_HELP << std::endl; + std::println("{}", PLUGIN_HELP); } else if (cmd == "setprop") { - std::cout << SETPROP_HELP << std::endl; + std::println("{}", SETPROP_HELP); } else if (cmd == "switchxkblayout") { - std::cout << SWITCHXKBLAYOUT_HELP << std::endl; + std::println("{}", SWITCHXKBLAYOUT_HELP); } else { - std::cout << USAGE << std::endl; + std::println("{}", USAGE); } return 1; } else { - std::cout << USAGE << std::endl; + std::println("{}", USAGE); return 1; } @@ -398,7 +400,7 @@ int main(int argc, char** argv) { } if (fullRequest.empty()) { - std::cout << USAGE << std::endl; + std::println("{}", USAGE); return 1; } @@ -475,7 +477,7 @@ int main(int argc, char** argv) { else if (fullRequest.contains("/decorations")) exitStatus = request(fullRequest, 1); else if (fullRequest.contains("/--help")) - std::cout << USAGE << std::endl; + std::println("{}", USAGE); else if (fullRequest.contains("/rollinglog") && needRoll) exitStatus = request(fullRequest, 0, true); else { diff --git a/hyprctl/meson.build b/hyprctl/meson.build index 5488845f..455f5739 100644 --- a/hyprctl/meson.build +++ b/hyprctl/meson.build @@ -1,10 +1,26 @@ -executable('hyprctl', 'main.cpp', +executable( + 'hyprctl', + 'main.cpp', dependencies: [ dependency('hyprutils', version: '>= 0.1.1'), ], - install: true + install: true, ) -install_data('hyprctl.bash', install_dir: join_paths(get_option('datadir'), 'bash-completion/completions'), install_tag: 'runtime', rename: 'hyprctl') -install_data('hyprctl.fish', install_dir: join_paths(get_option('datadir'), 'fish/vendor_completions.d'), install_tag: 'runtime') -install_data('hyprctl.zsh', install_dir: join_paths(get_option('datadir'), 'zsh/site-functions'), install_tag: 'runtime', rename: '_hyprctl') +install_data( + 'hyprctl.bash', + install_dir: join_paths(get_option('datadir'), 'bash-completion/completions'), + install_tag: 'runtime', + rename: 'hyprctl', +) +install_data( + 'hyprctl.fish', + install_dir: join_paths(get_option('datadir'), 'fish/vendor_completions.d'), + install_tag: 'runtime', +) +install_data( + 'hyprctl.zsh', + install_dir: join_paths(get_option('datadir'), 'zsh/site-functions'), + install_tag: 'runtime', + rename: '_hyprctl', +) diff --git a/hyprpm/hyprpm.bash b/hyprpm/hyprpm.bash index ffc33e19..3c2bc90b 100644 --- a/hyprpm/hyprpm.bash +++ b/hyprpm/hyprpm.bash @@ -2,6 +2,10 @@ _hyprpm_cmd_0 () { hyprpm list | awk '/Plugin/{print $4}' } +_hyprpm_cmd_1 () { + hyprpm list | awk '/Repository/{print $4}' | sed 's/:$//' +} + _hyprpm () { if [[ $(type -t _get_comp_words_by_ref) != function ]]; then echo _get_comp_words_by_ref: function not defined. Make sure the bash-completions system package is installed @@ -11,16 +15,13 @@ _hyprpm () { local words cword _get_comp_words_by_ref -n "$COMP_WORDBREAKS" words cword - local -a literals=("-n" "::=" "list" "disable" "--help" "update" "add" "--verbose" "-v" "--force" "remove" "enable" "--notify" "-h" "reload" "-f") - + declare -a literals=(--no-shallow -n ::= disable list --help update add --verbose -v --force -s remove enable --notify -h reload -f) declare -A literal_transitions - literal_transitions[0]="([9]=6 [2]=2 [7]=6 [8]=6 [4]=6 [10]=2 [11]=3 [5]=2 [13]=6 [3]=3 [14]=2 [15]=6 [6]=2)" - literal_transitions[1]="([10]=2 [11]=3 [3]=3 [2]=2 [14]=2 [5]=2 [6]=2)" - literal_transitions[4]="([1]=5)" - literal_transitions[5]="([0]=6 [12]=6)" - - declare -A match_anything_transitions - match_anything_transitions=([3]=2 [2]=4 [0]=1 [1]=1) + literal_transitions[0]="([0]=7 [3]=3 [4]=4 [8]=7 [9]=7 [6]=4 [7]=4 [11]=7 [5]=7 [10]=7 [12]=2 [13]=3 [15]=7 [16]=4 [17]=7)" + literal_transitions[1]="([12]=2 [13]=3 [3]=3 [4]=4 [16]=4 [6]=4 [7]=4)" + literal_transitions[5]="([2]=6)" + literal_transitions[6]="([1]=7 [14]=7)" + declare -A match_anything_transitions=([1]=1 [4]=5 [3]=4 [2]=4 [0]=1) declare -A subword_transitions local state=0 @@ -58,21 +59,9 @@ _hyprpm () { done + local -a matches=() + local prefix="${words[$cword]}" - - local shortest_suffix="$word" - for ((i=0; i < ${#COMP_WORDBREAKS}; i++)); do - local char="${COMP_WORDBREAKS:$i:1}" - local candidate="${word##*$char}" - if [[ ${#candidate} -lt ${#shortest_suffix} ]]; then - shortest_suffix=$candidate - fi - done - local superfluous_prefix="" - if [[ "$shortest_suffix" != "$word" ]]; then - local superfluous_prefix=${word%$shortest_suffix} - fi - if [[ -v "literal_transitions[$state]" ]]; then local state_transitions_initializer=${literal_transitions[$state]} declare -A state_transitions @@ -81,25 +70,38 @@ _hyprpm () { for literal_id in "${!state_transitions[@]}"; do local literal="${literals[$literal_id]}" if [[ $literal = "${prefix}"* ]]; then - local completion=${literal#"$superfluous_prefix"} - COMPREPLY+=("$completion ") + matches+=("$literal ") fi done fi declare -A commands - commands=([3]=0) + commands=([3]=0 [2]=1) if [[ -v "commands[$state]" ]]; then local command_id=${commands[$state]} local completions=() - mapfile -t completions < <(_hyprpm_cmd_${command_id} "$prefix" | cut -f1) + readarray -t completions < <(_hyprpm_cmd_${command_id} "$prefix" | cut -f1) for item in "${completions[@]}"; do if [[ $item = "${prefix}"* ]]; then - COMPREPLY+=("$item") + matches+=("$item") fi done fi + local shortest_suffix="$prefix" + for ((i=0; i < ${#COMP_WORDBREAKS}; i++)); do + local char="${COMP_WORDBREAKS:$i:1}" + local candidate=${prefix##*$char} + if [[ ${#candidate} -lt ${#shortest_suffix} ]]; then + shortest_suffix=$candidate + fi + done + local superfluous_prefix="" + if [[ "$shortest_suffix" != "$prefix" ]]; then + local superfluous_prefix=${prefix%$shortest_suffix} + fi + COMPREPLY=("${matches[@]#$superfluous_prefix}") + return 0 } diff --git a/hyprpm/hyprpm.fish b/hyprpm/hyprpm.fish index 7be4f224..82561bd8 100644 --- a/hyprpm/hyprpm.fish +++ b/hyprpm/hyprpm.fish @@ -3,6 +3,11 @@ function _hyprpm_1 hyprpm list | awk '/Plugin/{print $4}' end +function _hyprpm_2 + set 1 $argv[1] + hyprpm list | awk '/Repository/{print $4}' | sed 's/:$//' +end + function _hyprpm set COMP_LINE (commandline --cut-at-cursor) @@ -14,49 +19,51 @@ function _hyprpm set COMP_CWORD (count $COMP_WORDS) end - set --local literals "-n" "::=" "list" "disable" "--help" "update" "add" "--verbose" "-v" "--force" "remove" "enable" "--notify" "-h" "reload" "-f" + set literals "--no-shallow" "-n" "::=" "disable" "list" "--help" "update" "add" "--verbose" "-v" "--force" "-s" "remove" "enable" "--notify" "-h" "reload" "-f" - set --local descriptions - set descriptions[1] "Send a hyprland notification for important events (e.g. load fail)" - set descriptions[3] "List all installed plugins" + set descriptions + set descriptions[1] "Disable shallow cloning of Hyprland sources" + set descriptions[2] "Send a hyprland notification for important events (e.g. load fail)" set descriptions[4] "Unload a plugin" - set descriptions[5] "Show help menu" - set descriptions[6] "Check and update all plugins if needed" - set descriptions[7] "Install a new plugin repository from git" - set descriptions[8] "Enable too much loggin" + set descriptions[5] "List all installed plugins" + set descriptions[6] "Show help menu" + set descriptions[7] "Check and update all plugins if needed" + set descriptions[8] "Install a new plugin repository from git" set descriptions[9] "Enable too much loggin" - set descriptions[10] "Force an operation ignoring checks (e.g. update -f)" - set descriptions[11] "Remove a plugin repository" - set descriptions[12] "Load a plugin" - set descriptions[13] "Send a hyprland notification for important events (e.g. load fail)" - set descriptions[14] "Show help menu" - set descriptions[15] "Reload all plugins" - set descriptions[16] "Force an operation ignoring checks (e.g. update -f)" + set descriptions[10] "Enable too much loggin" + set descriptions[11] "Force an operation ignoring checks (e.g. update -f)" + set descriptions[12] "Disable shallow cloning of Hyprland sources" + set descriptions[13] "Remove a plugin repository" + set descriptions[14] "Load a plugin" + set descriptions[15] "Send a hyprland notification for important events (e.g. load fail)" + set descriptions[16] "Show help menu" + set descriptions[17] "Reload all plugins" + set descriptions[18] "Force an operation ignoring checks (e.g. update -f)" - set --local literal_transitions - set literal_transitions[1] "set inputs 10 3 8 9 5 11 12 6 14 4 15 16 7; set tos 7 3 7 7 7 3 4 3 7 4 3 7 3" - set literal_transitions[2] "set inputs 11 12 4 3 15 6 7; set tos 3 4 4 3 3 3 3" - set literal_transitions[5] "set inputs 2; set tos 6" - set literal_transitions[6] "set inputs 1 13; set tos 7 7" + set literal_transitions + set literal_transitions[1] "set inputs 1 4 5 9 10 7 8 12 6 11 13 14 16 17 18; set tos 8 4 5 8 8 5 5 8 8 8 3 4 8 5 8" + set literal_transitions[2] "set inputs 13 14 4 5 17 7 8; set tos 3 4 4 5 5 5 5" + set literal_transitions[6] "set inputs 3; set tos 7" + set literal_transitions[7] "set inputs 2 15; set tos 8 8" - set --local match_anything_transitions_from 4 3 1 2 - set --local match_anything_transitions_to 3 5 2 2 + set match_anything_transitions_from 2 5 4 3 1 + set match_anything_transitions_to 2 6 5 5 2 - set --local state 1 - set --local word_index 2 + set state 1 + set word_index 2 while test $word_index -lt $COMP_CWORD - set --local -- word $COMP_WORDS[$word_index] + set -- word $COMP_WORDS[$word_index] if set --query literal_transitions[$state] && test -n $literal_transitions[$state] - set --local --erase inputs - set --local --erase tos + set --erase inputs + set --erase tos eval $literal_transitions[$state] if contains -- $word $literals - set --local literal_matched 0 + set literal_matched 0 for literal_id in (seq 1 (count $literals)) if test $literals[$literal_id] = $word - set --local index (contains --index -- $literal_id $inputs) + set index (contains --index -- $literal_id $inputs) set state $tos[$index] set word_index (math $word_index + 1) set literal_matched 1 @@ -70,7 +77,7 @@ function _hyprpm end if set --query match_anything_transitions_from[$state] && test -n $match_anything_transitions_from[$state] - set --local index (contains --index -- $state $match_anything_transitions_from) + set index (contains --index -- $state $match_anything_transitions_from) set state $match_anything_transitions_to[$index] set word_index (math $word_index + 1) continue @@ -80,8 +87,8 @@ function _hyprpm end if set --query literal_transitions[$state] && test -n $literal_transitions[$state] - set --local --erase inputs - set --local --erase tos + set --erase inputs + set --erase tos eval $literal_transitions[$state] for literal_id in $inputs if test -n $descriptions[$literal_id] @@ -92,14 +99,14 @@ function _hyprpm end end - set command_states 4 - set command_ids 1 + set command_states 4 3 + set command_ids 1 2 if contains $state $command_states - set --local index (contains --index $state $command_states) - set --local function_id $command_ids[$index] - set --local function_name _hyprpm_$function_id - set --local --erase inputs - set --local --erase tos + set index (contains --index $state $command_states) + set function_id $command_ids[$index] + set function_name _hyprpm_$function_id + set --erase inputs + set --erase tos $function_name "$COMP_WORDS[$COMP_CWORD]" end diff --git a/hyprpm/hyprpm.usage b/hyprpm/hyprpm.usage index 369c9d2b..24e631c5 100644 --- a/hyprpm/hyprpm.usage +++ b/hyprpm/hyprpm.usage @@ -5,10 +5,11 @@ hyprpm []... | (--help | -h) "Show help menu" | (--verbose | -v) "Enable too much loggin" | (--force | -f) "Force an operation ignoring checks (e.g. update -f)" + | (--no-shallow | -s) "Disable shallow cloning of Hyprland sources" ; ::= (add) "Install a new plugin repository from git" - | (remove) "Remove a plugin repository" + | (remove ) "Remove a plugin repository" | (update) "Check and update all plugins if needed" | (list) "List all installed plugins" | (enable ) "Load a plugin" @@ -17,3 +18,4 @@ hyprpm []... ; ::= {{{ hyprpm list | awk '/Plugin/{print $4}' }}}; + ::= {{{ hyprpm list | awk '/Repository/{print $4}' | sed 's/:$//' }}}; diff --git a/hyprpm/hyprpm.zsh b/hyprpm/hyprpm.zsh index 854e8426..859c5313 100644 --- a/hyprpm/hyprpm.zsh +++ b/hyprpm/hyprpm.zsh @@ -4,34 +4,40 @@ _hyprpm_cmd_0 () { hyprpm list | awk '/Plugin/{print $4}' } +_hyprpm_cmd_1 () { + hyprpm list | awk '/Repository/{print $4}' | sed 's/:$//' +} + _hyprpm () { - local -a literals=("-n" "::=" "list" "disable" "--help" "update" "add" "--verbose" "-v" "--force" "remove" "enable" "--notify" "-h" "reload" "-f") + local -a literals=("--no-shallow" "-n" "::=" "disable" "list" "--help" "update" "add" "--verbose" "-v" "--force" "-s" "remove" "enable" "--notify" "-h" "reload" "-f") local -A descriptions - descriptions[1]="Send a hyprland notification for important events (e.g. load fail)" - descriptions[3]="List all installed plugins" + descriptions[1]="Disable shallow cloning of Hyprland sources" + descriptions[2]="Send a hyprland notification for important events (e.g. load fail)" descriptions[4]="Unload a plugin" - descriptions[5]="Show help menu" - descriptions[6]="Check and update all plugins if needed" - descriptions[7]="Install a new plugin repository from git" - descriptions[8]="Enable too much loggin" + descriptions[5]="List all installed plugins" + descriptions[6]="Show help menu" + descriptions[7]="Check and update all plugins if needed" + descriptions[8]="Install a new plugin repository from git" descriptions[9]="Enable too much loggin" - descriptions[10]="Force an operation ignoring checks (e.g. update -f)" - descriptions[11]="Remove a plugin repository" - descriptions[12]="Load a plugin" - descriptions[13]="Send a hyprland notification for important events (e.g. load fail)" - descriptions[14]="Show help menu" - descriptions[15]="Reload all plugins" - descriptions[16]="Force an operation ignoring checks (e.g. update -f)" + descriptions[10]="Enable too much loggin" + descriptions[11]="Force an operation ignoring checks (e.g. update -f)" + descriptions[12]="Disable shallow cloning of Hyprland sources" + descriptions[13]="Remove a plugin repository" + descriptions[14]="Load a plugin" + descriptions[15]="Send a hyprland notification for important events (e.g. load fail)" + descriptions[16]="Show help menu" + descriptions[17]="Reload all plugins" + descriptions[18]="Force an operation ignoring checks (e.g. update -f)" local -A literal_transitions - literal_transitions[1]="([10]=7 [3]=3 [8]=7 [9]=7 [5]=7 [11]=3 [12]=4 [6]=3 [14]=7 [4]=4 [15]=3 [16]=7 [7]=3)" - literal_transitions[2]="([11]=3 [12]=4 [4]=4 [3]=3 [15]=3 [6]=3 [7]=3)" - literal_transitions[5]="([2]=6)" - literal_transitions[6]="([1]=7 [13]=7)" + literal_transitions[1]="([1]=8 [4]=4 [5]=5 [9]=8 [10]=8 [7]=5 [8]=5 [12]=8 [6]=8 [11]=8 [13]=3 [14]=4 [16]=8 [17]=5 [18]=8)" + literal_transitions[2]="([13]=3 [14]=4 [4]=4 [5]=5 [17]=5 [7]=5 [8]=5)" + literal_transitions[6]="([3]=7)" + literal_transitions[7]="([2]=8 [15]=8)" local -A match_anything_transitions - match_anything_transitions=([4]=3 [3]=5 [1]=2 [2]=2) + match_anything_transitions=([2]=2 [5]=6 [4]=5 [3]=5 [1]=2) declare -A subword_transitions @@ -91,7 +97,7 @@ _hyprpm () { fi done fi - local -A commands=([4]=0) + local -A commands=([4]=0 [3]=1) if [[ -v "commands[$state]" ]]; then local command_id=${commands[$state]} diff --git a/hyprpm/src/core/DataState.cpp b/hyprpm/src/core/DataState.cpp index 61ad336f..fb8679d6 100644 --- a/hyprpm/src/core/DataState.cpp +++ b/hyprpm/src/core/DataState.cpp @@ -1,6 +1,6 @@ #include "DataState.hpp" #include -#include +#include #include #include #include "PluginManager.hpp" @@ -8,7 +8,7 @@ std::string DataState::getDataStatePath() { const auto HOME = getenv("HOME"); if (!HOME) { - std::cerr << "DataState: no $HOME\n"; + std::println(stderr, "DataState: no $HOME"); throw std::runtime_error("no $HOME"); return ""; } @@ -49,7 +49,7 @@ void DataState::addNewPluginRepo(const SPluginRepository& repo) { {"rev", repo.rev} }} }; - for (auto& p : repo.plugins) { + for (auto const& p : repo.plugins) { // copy .so to the good place if (std::filesystem::exists(p.filename)) std::filesystem::copy_file(p.filename, PATH + "/" + p.name + ".so"); @@ -242,4 +242,4 @@ bool DataState::setPluginEnabled(const std::string& name, bool enabled) { } return false; -} \ No newline at end of file +} diff --git a/hyprpm/src/core/Manifest.cpp b/hyprpm/src/core/Manifest.cpp index 42a8357c..754d9d69 100644 --- a/hyprpm/src/core/Manifest.cpp +++ b/hyprpm/src/core/Manifest.cpp @@ -6,7 +6,7 @@ CManifest::CManifest(const eManifestType type, const std::string& path) { auto manifest = toml::parse_file(path); if (type == MANIFEST_HYPRLOAD) { - for (auto& [key, val] : manifest) { + for (auto const& [key, val] : manifest) { if (key.str().ends_with(".build")) continue; @@ -63,7 +63,7 @@ CManifest::CManifest(const eManifestType type, const std::string& path) { } } - for (auto& [key, val] : manifest) { + for (auto const& [key, val] : manifest) { if (key.str() == "repository") continue; diff --git a/hyprpm/src/core/PluginManager.cpp b/hyprpm/src/core/PluginManager.cpp index 6988547c..57c67641 100644 --- a/hyprpm/src/core/PluginManager.cpp +++ b/hyprpm/src/core/PluginManager.cpp @@ -1,12 +1,15 @@ #include "PluginManager.hpp" #include "../helpers/Colors.hpp" +#include "../helpers/StringUtils.hpp" #include "../progress/CProgressBar.hpp" #include "Manifest.hpp" #include "DataState.hpp" +#include #include #include #include +#include #include #include #include @@ -31,6 +34,7 @@ static std::string execAndGet(std::string cmd) { if (!pipe) return ""; + result.reserve(buffer.size()); while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { result += buffer.data(); } @@ -47,10 +51,10 @@ SHyprlandVersion CPluginManager::getHyprlandVersion() { once = true; const auto HLVERCALL = execAndGet("hyprctl version"); if (m_bVerbose) - std::cout << Colors::BLUE << "[v] " << Colors::RESET << "version returned: " << HLVERCALL << "\n"; + std::println("{}", verboseString("version returned: {}", HLVERCALL)); if (!HLVERCALL.contains("Tag:")) { - std::cerr << "\n" << Colors::RED << "✖" << Colors::RESET << " You don't seem to be running Hyprland."; + std::println(stderr, "\n{}", failureString("You don't seem to be running Hyprland.")); return SHyprlandVersion{}; } @@ -76,7 +80,7 @@ SHyprlandVersion CPluginManager::getHyprlandVersion() { } catch (...) { ; } if (m_bVerbose) - std::cout << Colors::BLUE << "[v] " << Colors::RESET << "parsed commit " << hlcommit << " at branch " << hlbranch << " on " << hldate << ", commits " << commits << "\n"; + std::println("{}", verboseString("parsed commit {} at branch {} on {}, commits {}", hlcommit, hlbranch, hldate, commits)); ver = SHyprlandVersion{hlbranch, hlcommit, hldate, commits}; return ver; @@ -102,20 +106,21 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& const auto HLVER = getHyprlandVersion(); if (!hasDeps()) { - std::cerr << "\n" << Colors::RED << "✖" << Colors::RESET << " Could not clone the plugin repository. Dependencies not satisfied. Hyprpm requires: cmake, meson, cpio\n"; + std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. Dependencies not satisfied. Hyprpm requires: cmake, meson, cpio")); return false; } if (DataState::pluginRepoExists(url)) { - std::cerr << "\n" << Colors::RED << "✖" << Colors::RESET << " Could not clone the plugin repository. Repository already installed.\n"; + std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. Repository already installed.")); return false; } auto GLOBALSTATE = DataState::getGlobalState(); if (!GLOBALSTATE.dontWarnInstall) { - std::cout << Colors::YELLOW << "!" << Colors::RED << " Disclaimer:\n " << Colors::RESET - << "plugins, especially not official, have no guarantee of stability, availablity or security.\n Run them at your own risk.\n " - << "This message will not appear again.\n"; + std::println("{}!{} Disclaimer: {}", Colors::YELLOW, Colors::RED, Colors::RESET); + std::println("plugins, especially not official, have no guarantee of stability, availablity or security.\n" + "Run them at your own risk.\n" + "This message will not appear again."); GLOBALSTATE.dontWarnInstall = true; DataState::updateGlobalState(GLOBALSTATE); } @@ -129,7 +134,7 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& std::getline(std::cin, input); if (input.size() > 0 && input[0] != 'Y' && input[0] != 'y') { - std::cout << "Aborting.\n"; + std::println(stderr, "Aborting."); return false; } @@ -144,7 +149,7 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& std::filesystem::create_directory("/tmp/hyprpm"); std::filesystem::permissions("/tmp/hyprpm", std::filesystem::perms::all, std::filesystem::perm_options::replace); } else if (!std::filesystem::is_directory("/tmp/hyprpm")) { - std::cerr << "\n" << Colors::RED << "✖" << Colors::RESET << " Could not prepare working dir for hyprpm\n"; + std::println(stderr, "\n{}", failureString("Could not prepare working dir for hyprpm")); return false; } @@ -153,60 +158,60 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& m_szWorkingPluginDirectory = "/tmp/hyprpm/" + USERNAME; if (!createSafeDirectory(m_szWorkingPluginDirectory)) { - std::cerr << "\n" << Colors::RED << "✖" << Colors::RESET << " Could not prepare working dir for repo\n"; + std::println(stderr, "\n{}", failureString("Could not prepare working dir for repo")); return false; } - progress.printMessageAbove(std::string{Colors::RESET} + " → Cloning " + url); + progress.printMessageAbove(infoString("Cloning {}", url)); std::string ret = execAndGet("cd /tmp/hyprpm && git clone --recursive " + url + " " + USERNAME); if (!std::filesystem::exists(m_szWorkingPluginDirectory + "/.git")) { - std::cerr << "\n" << Colors::RED << "✖" << Colors::RESET << " Could not clone the plugin repository. shell returned:\n" << ret << "\n"; + std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. shell returned:\n{}", ret)); return false; } if (!rev.empty()) { std::string ret = execAndGet("git -C " + m_szWorkingPluginDirectory + " reset --hard --recurse-submodules " + rev); if (ret.compare(0, 6, "fatal:") == 0) { - std::cerr << "\n" << Colors::RED << "✖" << Colors::RESET << " Could not check out revision " << rev << ". shell returned:\n" << ret << "\n"; + std::println(stderr, "\n{}", failureString("Could not check out revision {}. shell returned:\n{}", rev, ret)); return false; } ret = execAndGet("git -C " + m_szWorkingPluginDirectory + " submodule update --init"); if (m_bVerbose) - std::cout << Colors::BLUE << "[v] " << Colors::RESET << "git submodule update --init returned: " << ret << "\n"; + std::println("{}", verboseString("git submodule update --init returned: {}", ret)); } progress.m_iSteps = 1; - progress.printMessageAbove(std::string{Colors::GREEN} + "✔" + Colors::RESET + " cloned"); + progress.printMessageAbove(successString("cloned")); progress.m_szCurrentMessage = "Reading the manifest"; progress.print(); std::unique_ptr pManifest; if (std::filesystem::exists(m_szWorkingPluginDirectory + "/hyprpm.toml")) { - progress.printMessageAbove(std::string{Colors::GREEN} + "✔" + Colors::RESET + " found hyprpm manifest"); + progress.printMessageAbove(successString("found hyprpm manifest")); pManifest = std::make_unique(MANIFEST_HYPRPM, m_szWorkingPluginDirectory + "/hyprpm.toml"); } else if (std::filesystem::exists(m_szWorkingPluginDirectory + "/hyprload.toml")) { - progress.printMessageAbove(std::string{Colors::GREEN} + "✔" + Colors::RESET + " found hyprload manifest"); + progress.printMessageAbove(successString("found hyprload manifest")); pManifest = std::make_unique(MANIFEST_HYPRLOAD, m_szWorkingPluginDirectory + "/hyprload.toml"); } if (!pManifest) { - std::cerr << "\n" << Colors::RED << "✖" << Colors::RESET << " The provided plugin repository does not have a valid manifest\n"; + std::println(stderr, "\n{}", failureString("The provided plugin repository does not have a valid manifest")); return false; } if (!pManifest->m_bGood) { - std::cerr << "\n" << Colors::RED << "✖" << Colors::RESET << " The provided plugin repository has a corrupted manifest\n"; + std::println(stderr, "\n{}", failureString("The provided plugin repository has a corrupted manifest")); return false; } progress.m_iSteps = 2; - progress.printMessageAbove(std::string{Colors::GREEN} + "✔" + Colors::RESET + " parsed manifest, found " + std::to_string(pManifest->m_vPlugins.size()) + " plugins:"); - for (auto& pl : pManifest->m_vPlugins) { - std::string message = std::string{Colors::RESET} + " → " + pl.name + " by "; - for (auto& a : pl.authors) { + progress.printMessageAbove(successString("parsed manifest, found " + std::to_string(pManifest->m_vPlugins.size()) + " plugins:")); + for (auto const& pl : pManifest->m_vPlugins) { + std::string message = "→ " + pl.name + " by "; + for (auto const& a : pl.authors) { message += a + ", "; } if (pl.authors.size() > 0) { @@ -220,19 +225,19 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& if (!pManifest->m_sRepository.commitPins.empty()) { // check commit pins - progress.printMessageAbove(std::string{Colors::RESET} + " → Manifest has " + std::to_string(pManifest->m_sRepository.commitPins.size()) + " pins, checking"); + progress.printMessageAbove(infoString("Manifest has {} pins, checking", pManifest->m_sRepository.commitPins.size())); - for (auto& [hl, plugin] : pManifest->m_sRepository.commitPins) { + for (auto const& [hl, plugin] : pManifest->m_sRepository.commitPins) { if (hl != HLVER.hash) continue; - progress.printMessageAbove(std::string{Colors::GREEN} + "✔" + Colors::RESET + " commit pin " + plugin + " matched hl, resetting"); + progress.printMessageAbove(successString("commit pin {} matched hl, resetting", plugin)); execAndGet("cd " + m_szWorkingPluginDirectory + " && git reset --hard --recurse-submodules " + plugin); ret = execAndGet("git -C " + m_szWorkingPluginDirectory + " submodule update --init"); if (m_bVerbose) - std::cout << Colors::BLUE << "[v] " << Colors::RESET << "git submodule update --init returned: " << ret << "\n"; + std::println("{}", verboseString("git submodule update --init returned: {}", ret)); break; } @@ -244,12 +249,12 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& const auto HEADERSSTATUS = headersValid(); if (HEADERSSTATUS != HEADERS_OK) { - std::cerr << "\n" << headerError(HEADERSSTATUS); + std::println("\n{}", headerError(HEADERSSTATUS)); return false; } progress.m_iSteps = 3; - progress.printMessageAbove(std::string{Colors::GREEN} + "✔" + Colors::RESET + " Hyprland headers OK"); + progress.printMessageAbove(successString("Hyprland headers OK")); progress.m_szCurrentMessage = "Building plugin(s)"; progress.print(); @@ -257,35 +262,36 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& std::string out; if (p.since > HLVER.commits && HLVER.commits >= 1 /* for --depth 1 clones, we can't check this. */) { - progress.printMessageAbove(std::string{Colors::RED} + "✖" + Colors::RESET + " Not building " + p.name + ": your Hyprland version is too old.\n"); + progress.printMessageAbove(failureString("Not building {}: your Hyprland version is too old.\n", p.name)); p.failed = true; continue; } - progress.printMessageAbove(std::string{Colors::RESET} + " → Building " + p.name); + progress.printMessageAbove(infoString("Building {}", p.name)); - for (auto& bs : p.buildSteps) { - std::string cmd = std::format("cd {} && PKG_CONFIG_PATH=\"{}/share/pkgconfig\" {}", m_szWorkingPluginDirectory, DataState::getHeadersPath(), bs); + for (auto const& bs : p.buildSteps) { + const std::string& cmd = std::format("cd {} && PKG_CONFIG_PATH=\"{}/share/pkgconfig\" {}", m_szWorkingPluginDirectory, DataState::getHeadersPath(), bs); out += " -> " + cmd + "\n" + execAndGet(cmd) + "\n"; } if (m_bVerbose) - std::cout << Colors::BLUE << "[v] " << Colors::RESET << "shell returned: " << out << "\n"; + std::println("{}", verboseString("shell returned: " + out)); if (!std::filesystem::exists(m_szWorkingPluginDirectory + "/" + p.output)) { - progress.printMessageAbove(std::string{Colors::RED} + "✖" + Colors::RESET + " Plugin " + p.name + " failed to build.\n" + - " This likely means that the plugin is either outdated, not yet available for your version, or broken.\n If you are on -git, update " - "first.\n Try re-running with -v to see " - "more verbose output.\n"); + progress.printMessageAbove(failureString("Plugin {} failed to build.\n" + " This likely means that the plugin is either outdated, not yet available for your version, or broken.\n" + " If you are on -git, update first\n" + " Try re-running with -v to see more verbose output.\n", + p.name)); p.failed = true; continue; } - progress.printMessageAbove(std::string{Colors::GREEN} + "✔" + Colors::RESET + " built " + p.name + " into " + p.output); + progress.printMessageAbove(successString("built {} into {}", p.name, p.output)); } - progress.printMessageAbove(std::string{Colors::GREEN} + "✔" + Colors::RESET + " all plugins built"); + progress.printMessageAbove(successString("all plugins built")); progress.m_iSteps = 4; progress.m_szCurrentMessage = "Installing repository"; progress.print(); @@ -299,18 +305,18 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& repo.url = url; repo.rev = rev; repo.hash = repohash; - for (auto& p : pManifest->m_vPlugins) { + for (auto const& p : pManifest->m_vPlugins) { repo.plugins.push_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, false, p.failed}); } DataState::addNewPluginRepo(repo); - progress.printMessageAbove(std::string{Colors::GREEN} + "✔" + Colors::RESET + " installed repository"); - progress.printMessageAbove(std::string{Colors::GREEN} + "✔" + Colors::RESET + " you can now enable the plugin(s) with hyprpm enable"); + progress.printMessageAbove(successString("installed repository")); + progress.printMessageAbove(successString("you can now enable the plugin(s) with hyprpm enable")); progress.m_iSteps = 5; progress.m_szCurrentMessage = "Done!"; progress.print(); - std::cout << "\n"; + std::print("\n"); // remove build files std::filesystem::remove_all(m_szWorkingPluginDirectory); @@ -320,7 +326,7 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& bool CPluginManager::removePluginRepo(const std::string& urlOrName) { if (!DataState::pluginRepoExists(urlOrName)) { - std::cerr << "\n" << Colors::RED << "✖" << Colors::RESET << " Could not remove the repository. Repository is not installed.\n"; + std::println(stderr, "\n{}", failureString("Could not remove the repository. Repository is not installed.")); return false; } @@ -331,7 +337,7 @@ bool CPluginManager::removePluginRepo(const std::string& urlOrName) { std::getline(std::cin, input); if (input.size() > 0 && input[0] != 'Y' && input[0] != 'y') { - std::cout << "Aborting.\n"; + std::println("Aborting."); return false; } @@ -347,15 +353,15 @@ eHeadersErrors CPluginManager::headersValid() { return HEADERS_MISSING; // find headers commit - std::string cmd = std::format("PKG_CONFIG_PATH=\"{}/share/pkgconfig\" pkgconf --cflags --keep-system-cflags hyprland", DataState::getHeadersPath()); - auto headers = execAndGet(cmd.c_str()); + const std::string& cmd = std::format("PKG_CONFIG_PATH=\"{}/share/pkgconfig\" pkgconf --cflags --keep-system-cflags hyprland", DataState::getHeadersPath()); + auto headers = execAndGet(cmd.c_str()); if (!headers.contains("-I/")) return HEADERS_MISSING; headers.pop_back(); // pop newline - std::string verHeader = ""; + std::string verHeader; while (!headers.empty()) { const auto PATH = headers.substr(0, headers.find(" -I/", 3)); @@ -406,7 +412,7 @@ bool CPluginManager::updateHeaders(bool force) { const auto HLVER = getHyprlandVersion(); if (!hasDeps()) { - std::cerr << "\n" << Colors::RED << "✖" << Colors::RESET << " Could not update. Dependencies not satisfied. Hyprpm requires: cmake, meson, cpio\n"; + std::println("\n{}", failureString("Could not update. Dependencies not satisfied. Hyprpm requires: cmake, meson, cpio")); return false; } @@ -416,7 +422,7 @@ bool CPluginManager::updateHeaders(bool force) { } if (!force && headersValid() == HEADERS_OK) { - std::cout << "\n" << std::string{Colors::GREEN} + "✔" + Colors::RESET + " Headers up to date.\n"; + std::println("\n{}", successString("Headers up to date.")); return true; } @@ -430,74 +436,73 @@ bool CPluginManager::updateHeaders(bool force) { const auto WORKINGDIR = "/tmp/hyprpm/hyprland-" + USERNAME; if (!createSafeDirectory(WORKINGDIR)) { - std::cerr << "\n" << Colors::RED << "✖" << Colors::RESET << " Could not prepare working dir for hl\n"; + std::println("\n{}", failureString("Could not prepare working dir for hl")); return false; } - progress.printMessageAbove(std::string{Colors::YELLOW} + "!" + Colors::RESET + " Cloning https://github.com/hyprwm/hyprland, this might take a moment."); + progress.printMessageAbove(statusString("!", Colors::YELLOW, "Cloning https://github.com/hyprwm/Hyprland, this might take a moment.")); - const bool bShallow = (HLVER.branch == "main" || HLVER.branch == "") && !m_bNoShallow; + const bool bShallow = (HLVER.branch == "main") && !m_bNoShallow; // let us give a bit of leg-room for shallowing // due to timezones, etc. - const std::string SHALLOW_DATE = - trim(HLVER.date).empty() ? "" : execAndGet("LC_TIME=\"en_US.UTF-8\" date --date='" + HLVER.date + " - 1 weeks' '+\%a \%b \%d \%H:\%M:\%S \%Y'"); + const std::string SHALLOW_DATE = trim(HLVER.date).empty() ? "" : execAndGet("LC_TIME=\"en_US.UTF-8\" date --date='" + HLVER.date + " - 1 weeks' '+%a %b %d %H:%M:%S %Y'"); if (m_bVerbose && bShallow) - progress.printMessageAbove(std::string{Colors::BLUE} + "[v] " + Colors::RESET + "will shallow since: " + SHALLOW_DATE); + progress.printMessageAbove(verboseString("will shallow since: {}", SHALLOW_DATE)); std::string ret = - execAndGet("cd /tmp/hyprpm && git clone --recursive https://github.com/hyprwm/hyprland hyprland-" + USERNAME + (bShallow ? " --shallow-since='" + SHALLOW_DATE + "'" : "")); + execAndGet("cd /tmp/hyprpm && git clone --recursive https://github.com/hyprwm/Hyprland hyprland-" + USERNAME + (bShallow ? " --shallow-since='" + SHALLOW_DATE + "'" : "")); if (!std::filesystem::exists(WORKINGDIR)) { - progress.printMessageAbove(std::string{Colors::RED} + "✖" + Colors::RESET + " Clone failed. Retrying without shallow."); + progress.printMessageAbove(failureString("Clone failed. Retrying without shallow.")); ret = execAndGet("cd /tmp/hyprpm && git clone --recursive https://github.com/hyprwm/hyprland hyprland-" + USERNAME); } if (!std::filesystem::exists(WORKINGDIR + "/.git")) { - std::cerr << "\n" << Colors::RED << "✖" << Colors::RESET << " Could not clone the hyprland repository. shell returned:\n" << ret << "\n"; + std::println(stderr, "\n{}", failureString("Could not clone the Hyprland repository. shell returned:\n{}", ret)); return false; } - progress.printMessageAbove(std::string{Colors::GREEN} + "✔" + Colors::RESET + " cloned"); + progress.printMessageAbove(successString("Hyprland cloned")); progress.m_iSteps = 2; progress.m_szCurrentMessage = "Checking out sources"; progress.print(); if (m_bVerbose) - progress.printMessageAbove(std::string{Colors::BLUE} + "[v] " + Colors::RESET + "will run: " + "cd " + WORKINGDIR + " && git checkout " + HLVER.hash + " 2>&1"); + progress.printMessageAbove(verboseString("will run: cd {} && git checkout {} 2>&1", WORKINGDIR, HLVER.hash)); ret = execAndGet("cd " + WORKINGDIR + " && git checkout " + HLVER.hash + " 2>&1"); if (ret.contains("fatal: unable to read tree")) { - std::cerr << "\n" - << Colors::RED << "✖" << Colors::RESET - << " Could not checkout the running Hyprland commit. If you are on -git, try updating.\nYou can also try re-running hyprpm update with --no-shallow.\n"; + std::println(stderr, "\n{}", + failureString("Could not checkout the running Hyprland commit. If you are on -git, try updating.\n" + "You can also try re-running hyprpm update with --no-shallow.")); return false; } if (m_bVerbose) - progress.printMessageAbove(std::string{Colors::BLUE} + "[v] " + Colors::RESET + "git returned (co): " + ret); + progress.printMessageAbove(verboseString("git returned (co): {}", ret)); ret = execAndGet("cd " + WORKINGDIR + " ; git rm subprojects/tracy ; git submodule update --init 2>&1 ; git reset --hard --recurse-submodules " + HLVER.hash); if (m_bVerbose) - progress.printMessageAbove(std::string{Colors::BLUE} + "[v] " + Colors::RESET + "git returned (rs): " + ret); + progress.printMessageAbove(verboseString("git returned (rs): {}", ret)); - progress.printMessageAbove(std::string{Colors::GREEN} + "✔" + Colors::RESET + " checked out to running ver"); + progress.printMessageAbove(successString("checked out to running ver")); progress.m_iSteps = 3; progress.m_szCurrentMessage = "Building Hyprland"; progress.print(); - progress.printMessageAbove(std::string{Colors::YELLOW} + "!" + Colors::RESET + " configuring Hyprland"); + progress.printMessageAbove(statusString("!", Colors::YELLOW, "configuring Hyprland")); if (m_bVerbose) - progress.printMessageAbove(std::string{Colors::BLUE} + "[v] " + Colors::RESET + "setting PREFIX for cmake to " + DataState::getHeadersPath()); + progress.printMessageAbove(verboseString("setting PREFIX for cmake to {}", DataState::getHeadersPath())); ret = execAndGet(std::format("cd {} && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:STRING=\"{}\" -S . -B ./build -G Ninja", WORKINGDIR, DataState::getHeadersPath())); if (m_bVerbose) - progress.printMessageAbove(std::string{Colors::BLUE} + "[v] " + Colors::RESET + "cmake returned: " + ret); + progress.printMessageAbove(verboseString("cmake returned: {}", ret)); if (ret.contains("CMake Error at")) { // missing deps, let the user know. @@ -506,48 +511,46 @@ bool CPluginManager::updateHeaders(bool force) { missing = missing.substr(0, missing.find("-- Configuring incomplete")); missing = missing.substr(0, missing.find_last_of('\n')); - std::cerr << "\n" - << Colors::RED << "✖" << Colors::RESET << " Could not configure the hyprland source, cmake complained:\n" - << missing << "\n\nThis likely means that you are missing the above dependencies or they are out of date.\n"; + std::println(stderr, "\n{}", + failureString("Could not configure the hyprland source, cmake complained:\n{}\n\n" + "This likely means that you are missing the above dependencies or they are out of date.", + missing)); return false; } - progress.printMessageAbove(std::string{Colors::GREEN} + "✔" + Colors::RESET + " configured Hyprland"); + progress.printMessageAbove(successString("configured Hyprland")); progress.m_iSteps = 4; progress.m_szCurrentMessage = "Installing sources"; progress.print(); - std::string cmd = + const std::string& cmd = std::format("sed -i -e \"s#PREFIX = /usr/local#PREFIX = {}#\" {}/Makefile && cd {} && make installheaders", DataState::getHeadersPath(), WORKINGDIR, WORKINGDIR); if (m_bVerbose) - progress.printMessageAbove(std::string{Colors::BLUE} + "[v] " + Colors::RESET + "installation will run: " + cmd); + progress.printMessageAbove(verboseString("installation will run: {}", cmd)); ret = execAndGet(cmd); if (m_bVerbose) - std::cout << Colors::BLUE << "[v] " << Colors::RESET << "installer returned: " << ret << "\n"; + std::println("{}", verboseString("installer returned: {}", ret)); // remove build files std::filesystem::remove_all(WORKINGDIR); auto HEADERSVALID = headersValid(); if (HEADERSVALID == HEADERS_OK) { - progress.printMessageAbove(std::string{Colors::GREEN} + "✔" + Colors::RESET + " installed headers"); + progress.printMessageAbove(successString("installed headers")); progress.m_iSteps = 5; progress.m_szCurrentMessage = "Done!"; progress.print(); - std::cout << "\n"; + std::print("\n"); } else { - progress.printMessageAbove(std::string{Colors::RED} + "✖" + Colors::RESET + " failed to install headers with error code " + std::to_string((int)HEADERSVALID) + " (" + - headerErrorShort(HEADERSVALID) + ")"); + progress.printMessageAbove(failureString("failed to install headers with error code {} ({})", (int)HEADERSVALID, headerErrorShort(HEADERSVALID))); progress.m_iSteps = 5; progress.m_szCurrentMessage = "Failed"; progress.print(); - std::cout << "\n"; - - std::cerr << "\n" << headerError(HEADERSVALID); + std::print(stderr, "\n\n{}", headerError(HEADERSVALID)); return false; } @@ -557,14 +560,14 @@ bool CPluginManager::updateHeaders(bool force) { bool CPluginManager::updatePlugins(bool forceUpdateAll) { if (headersValid() != HEADERS_OK) { - std::cout << "\n" << std::string{Colors::RED} + "✖" + Colors::RESET + " headers are not up-to-date, please run hyprpm update.\n"; + std::println("{}", failureString("headers are not up-to-date, please run hyprpm update.")); return false; } const auto REPOS = DataState::getAllRepositories(); if (REPOS.size() < 1) { - std::cout << "\n" << std::string{Colors::RED} + "✖" + Colors::RESET + " No repos to update.\n"; + std::println("{}", failureString("No repos to update.")); return true; } @@ -579,32 +582,33 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) { const std::string USERNAME = getpwuid(getuid())->pw_name; m_szWorkingPluginDirectory = "/tmp/hyprpm/" + USERNAME; - for (auto& repo : REPOS) { + for (auto const& repo : REPOS) { bool update = forceUpdateAll; progress.m_iSteps++; progress.m_szCurrentMessage = "Updating " + repo.name; progress.print(); - progress.printMessageAbove(std::string{Colors::RESET} + " → checking for updates for " + repo.name); + progress.printMessageAbove(infoString("checking for updates for {}", repo.name)); createSafeDirectory(m_szWorkingPluginDirectory); - progress.printMessageAbove(std::string{Colors::RESET} + " → Cloning " + repo.url); + progress.printMessageAbove(infoString("Cloning {}", repo.url)); std::string ret = execAndGet("cd /tmp/hyprpm && git clone --recursive " + repo.url + " " + USERNAME); if (!std::filesystem::exists(m_szWorkingPluginDirectory + "/.git")) { - std::cout << "\n" << std::string{Colors::RED} + "✖" + Colors::RESET + " could not clone repo: shell returned:\n" + ret; + std::println("{}", failureString("could not clone repo: shell returned: {}", ret)); return false; } if (!repo.rev.empty()) { - progress.printMessageAbove(std::string{Colors::RESET} + " → Plugin has revision set, resetting: " + repo.rev); + progress.printMessageAbove(infoString("Plugin has revision set, resetting: {}", repo.rev)); std::string ret = execAndGet("git -C " + m_szWorkingPluginDirectory + " reset --hard --recurse-submodules " + repo.rev); if (ret.compare(0, 6, "fatal:") == 0) { - std::cout << "\n" << std::string{Colors::RED} + "✖" + Colors::RESET + " could not check out revision " + repo.rev + ": shell returned:\n" + ret; + std::println(stderr, "\n{}", failureString("could not check out revision {}: shell returned:\n{}", repo.rev, ret)); + return false; } } @@ -620,7 +624,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) { if (!update) { std::filesystem::remove_all(m_szWorkingPluginDirectory); - progress.printMessageAbove(std::string{Colors::GREEN} + "✔" + Colors::RESET + " repository " + repo.name + " is up-to-date."); + progress.printMessageAbove(successString("repository {} is up-to-date.", repo.name)); progress.m_iSteps++; progress.print(); continue; @@ -628,41 +632,41 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) { // we need to update - progress.printMessageAbove(std::string{Colors::GREEN} + "✔" + Colors::RESET + " repository " + repo.name + " has updates."); - progress.printMessageAbove(std::string{Colors::RESET} + " → Building " + repo.name); + progress.printMessageAbove(successString("repository {} has updates.", repo.name)); + progress.printMessageAbove(infoString("Building {}", repo.name)); progress.m_iSteps++; progress.print(); std::unique_ptr pManifest; if (std::filesystem::exists(m_szWorkingPluginDirectory + "/hyprpm.toml")) { - progress.printMessageAbove(std::string{Colors::GREEN} + "✔" + Colors::RESET + " found hyprpm manifest"); + progress.printMessageAbove(successString("found hyprpm manifest")); pManifest = std::make_unique(MANIFEST_HYPRPM, m_szWorkingPluginDirectory + "/hyprpm.toml"); } else if (std::filesystem::exists(m_szWorkingPluginDirectory + "/hyprload.toml")) { - progress.printMessageAbove(std::string{Colors::GREEN} + "✔" + Colors::RESET + " found hyprload manifest"); + progress.printMessageAbove(successString("found hyprload manifest")); pManifest = std::make_unique(MANIFEST_HYPRLOAD, m_szWorkingPluginDirectory + "/hyprload.toml"); } if (!pManifest) { - std::cerr << "\n" << Colors::RED << "✖" << Colors::RESET << " The provided plugin repository does not have a valid manifest\n"; + std::println(stderr, "\n{}", failureString("The provided plugin repository does not have a valid manifest")); continue; } if (!pManifest->m_bGood) { - std::cerr << "\n" << Colors::RED << "✖" << Colors::RESET << " The provided plugin repository has a corrupted manifest\n"; + std::println(stderr, "\n{}", failureString("The provided plugin repository has a corrupted manifest")); continue; } if (repo.rev.empty() && !pManifest->m_sRepository.commitPins.empty()) { // check commit pins unless a revision is specified - progress.printMessageAbove(std::string{Colors::RESET} + " → Manifest has " + std::to_string(pManifest->m_sRepository.commitPins.size()) + " pins, checking"); + progress.printMessageAbove(infoString("Manifest has {} pins, checking", pManifest->m_sRepository.commitPins.size())); - for (auto& [hl, plugin] : pManifest->m_sRepository.commitPins) { + for (auto const& [hl, plugin] : pManifest->m_sRepository.commitPins) { if (hl != HLVER.hash) continue; - progress.printMessageAbove(std::string{Colors::GREEN} + "✔" + Colors::RESET + " commit pin " + plugin + " matched hl, resetting"); + progress.printMessageAbove(successString("commit pin {} matched hl, resetting", plugin)); execAndGet("cd " + m_szWorkingPluginDirectory + " && git reset --hard --recurse-submodules " + plugin); } @@ -672,32 +676,33 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) { std::string out; if (p.since > HLVER.commits && HLVER.commits >= 1000 /* for shallow clones, we can't check this. 1000 is an arbitrary number I chose. */) { - progress.printMessageAbove(std::string{Colors::RED} + "✖" + Colors::RESET + " Not building " + p.name + ": your Hyprland version is too old.\n"); + progress.printMessageAbove(failureString("Not building {}: your Hyprland version is too old.\n", p.name)); p.failed = true; continue; } - progress.printMessageAbove(std::string{Colors::RESET} + " → Building " + p.name); + progress.printMessageAbove(infoString("Building {}", p.name)); - for (auto& bs : p.buildSteps) { - std::string cmd = std::format("cd {} && PKG_CONFIG_PATH=\"{}/share/pkgconfig\" {}", m_szWorkingPluginDirectory, DataState::getHeadersPath(), bs); + for (auto const& bs : p.buildSteps) { + const std::string& cmd = std::format("cd {} && PKG_CONFIG_PATH=\"{}/share/pkgconfig\" {}", m_szWorkingPluginDirectory, DataState::getHeadersPath(), bs); out += " -> " + cmd + "\n" + execAndGet(cmd) + "\n"; } if (m_bVerbose) - std::cout << Colors::BLUE << "[v] " << Colors::RESET << "shell returned: " << out << "\n"; + std::println("{}", verboseString("shell returned: {}", out)); if (!std::filesystem::exists(m_szWorkingPluginDirectory + "/" + p.output)) { - std::cerr << "\n" - << Colors::RED << "✖" << Colors::RESET << " Plugin " << p.name << " failed to build.\n" - << " This likely means that the plugin is either outdated, not yet available for your version, or broken.\n If you are on -git, update first.\n Try " - "re-running with -v to see more verbose " - "output.\n"; + std::println(stderr, + "\n{}\n" + " This likely means that the plugin is either outdated, not yet available for your version, or broken.\n" + "If you are on -git, update first.\n" + "Try re-running with -v to see more verbose output.", + failureString("Plugin {} failed to build.", p.name)); p.failed = true; continue; } - progress.printMessageAbove(std::string{Colors::GREEN} + "✔" + Colors::RESET + " built " + p.name + " into " + p.output); + progress.printMessageAbove(successString("built {} into {}", p.name, p.output)); } // add repo toml to DataState @@ -709,7 +714,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) { if (repohash.length() > 0) repohash.pop_back(); newrepo.hash = repohash; - for (auto& p : pManifest->m_vPlugins) { + for (auto const& p : pManifest->m_vPlugins) { const auto OLDPLUGINIT = std::find_if(repo.plugins.begin(), repo.plugins.end(), [&](const auto& other) { return other.name == p.name; }); newrepo.plugins.push_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, OLDPLUGINIT != repo.plugins.end() ? OLDPLUGINIT->enabled : false}); } @@ -718,7 +723,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) { std::filesystem::remove_all(m_szWorkingPluginDirectory); - progress.printMessageAbove(std::string{Colors::GREEN} + "✔" + Colors::RESET + " updated " + repo.name); + progress.printMessageAbove(successString("updated {}", repo.name)); } progress.m_iSteps++; @@ -733,7 +738,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) { progress.m_szCurrentMessage = "Done!"; progress.print(); - std::cout << "\n"; + std::print("\n"); return true; } @@ -741,27 +746,27 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) { bool CPluginManager::enablePlugin(const std::string& name) { bool ret = DataState::setPluginEnabled(name, true); if (ret) - std::cout << Colors::GREEN << "✔" << Colors::RESET << " Enabled " << name << "\n"; + std::println("{}", successString("Enabled {}", name)); return ret; } bool CPluginManager::disablePlugin(const std::string& name) { bool ret = DataState::setPluginEnabled(name, false); if (ret) - std::cout << Colors::GREEN << "✔" << Colors::RESET << " Disabled " << name << "\n"; + std::println("{}", successString("Disabled {}", name)); return ret; } ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState() { if (headersValid() != HEADERS_OK) { - std::cerr << "\n" << std::string{Colors::RED} + "✖" + Colors::RESET + " headers are not up-to-date, please run hyprpm update.\n"; + std::println(stderr, "\n{}", failureString("headers are not up-to-date, please run hyprpm update.")); return LOADSTATE_HEADERS_OUTDATED; } const auto HOME = getenv("HOME"); const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE"); if (!HOME || !HIS) { - std::cerr << "PluginManager: no $HOME or HIS\n"; + std::println(stderr, "PluginManager: no $HOME or HIS"); return LOADSTATE_FAIL; } const auto HYPRPMPATH = DataState::getDataStatePath() + "/"; @@ -770,14 +775,14 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState() { std::vector loadedPlugins; - std::cout << Colors::GREEN << "✔" << Colors::RESET << " Ensuring plugin load state\n"; + std::println("{}", successString("Ensuring plugin load state")); // iterate line by line while (!pluginLines.empty()) { - auto plLine = pluginLines.substr(0, pluginLines.find("\n")); + auto plLine = pluginLines.substr(0, pluginLines.find('\n')); - if (pluginLines.find("\n") != std::string::npos) - pluginLines = pluginLines.substr(pluginLines.find("\n") + 1); + if (pluginLines.find('\n') != std::string::npos) + pluginLines = pluginLines.substr(pluginLines.find('\n') + 1); else pluginLines = ""; @@ -794,8 +799,8 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState() { const auto REPOS = DataState::getAllRepositories(); auto enabled = [REPOS](const std::string& plugin) -> bool { - for (auto& r : REPOS) { - for (auto& p : r.plugins) { + for (auto const& r : REPOS) { + for (auto const& p : r.plugins) { if (p.name == plugin && p.enabled) return true; } @@ -805,8 +810,8 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState() { }; auto repoForName = [REPOS](const std::string& name) -> std::string { - for (auto& r : REPOS) { - for (auto& p : r.plugins) { + for (auto const& r : REPOS) { + for (auto const& p : r.plugins) { if (p.name == name) return r.name; } @@ -816,17 +821,17 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState() { }; // unload disabled plugins - for (auto& p : loadedPlugins) { + for (auto const& p : loadedPlugins) { if (!enabled(p)) { // unload loadUnloadPlugin(HYPRPMPATH + repoForName(p) + "/" + p + ".so", false); - std::cout << Colors::GREEN << "✔" << Colors::RESET << " Unloaded " << p << "\n"; + std::println("{}", successString("Unloaded {}", p)); } } // load enabled plugins - for (auto& r : REPOS) { - for (auto& p : r.plugins) { + for (auto const& r : REPOS) { + for (auto const& p : r.plugins) { if (!p.enabled) continue; @@ -834,11 +839,11 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState() { continue; loadUnloadPlugin(HYPRPMPATH + repoForName(p.name) + "/" + p.filename, true); - std::cout << Colors::GREEN << "✔" << Colors::RESET << " Loaded " << p.name << "\n"; + std::println("{}", successString("Loaded {}", p.name)); } } - std::cout << Colors::GREEN << "✔" << Colors::RESET << " Plugin load state ensured\n"; + std::println("{}", successString("Plugin load state ensured")); return LOADSTATE_OK; } @@ -855,17 +860,18 @@ bool CPluginManager::loadUnloadPlugin(const std::string& path, bool load) { void CPluginManager::listAllPlugins() { const auto REPOS = DataState::getAllRepositories(); - for (auto& r : REPOS) { - std::cout << std::string{Colors::RESET} + " → Repository " + r.name + ":\n"; + for (auto const& r : REPOS) { + std::println("{}", infoString("Repository {}:", r.name)); - for (auto& p : r.plugins) { - - std::cout << std::string{Colors::RESET} + " │ Plugin " + p.name; + for (auto const& p : r.plugins) { + std::println(" │ Plugin {}", p.name); if (!p.failed) - std::cout << "\n └─ enabled: " << (p.enabled ? Colors::GREEN : Colors::RED) << (p.enabled ? "true" : "false") << Colors::RESET << "\n"; + std::println(" └─ enabled: {}", (p.enabled ? std::string{Colors::GREEN} + "true" : std::string{Colors::RED} + "false")); else - std::cout << "\n └─ enabled: " << Colors::RED << "Plugin failed to build" << Colors::RESET << "\n"; + std::println(" └─ enabled: {}Plugin failed to build", Colors::RED); + + std::println("{}", Colors::RESET); } } } @@ -876,19 +882,19 @@ void CPluginManager::notify(const eNotifyIcons icon, uint32_t color, int duratio std::string CPluginManager::headerError(const eHeadersErrors err) { switch (err) { - case HEADERS_CORRUPTED: return std::string{Colors::RED} + "✖" + Colors::RESET + " Headers corrupted. Please run hyprpm update to fix those.\n"; - case HEADERS_MISMATCHED: return std::string{Colors::RED} + "✖" + Colors::RESET + " Headers version mismatch. Please run hyprpm update to fix those.\n"; - case HEADERS_NOT_HYPRLAND: return std::string{Colors::RED} + "✖" + Colors::RESET + " It doesn't seem you are running on hyprland.\n"; - case HEADERS_MISSING: return std::string{Colors::RED} + "✖" + Colors::RESET + " Headers missing. Please run hyprpm update to fix those.\n"; + case HEADERS_CORRUPTED: return failureString("Headers corrupted. Please run hyprpm update to fix those.\n"); + case HEADERS_MISMATCHED: return failureString("Headers version mismatch. Please run hyprpm update to fix those.\n"); + case HEADERS_NOT_HYPRLAND: return failureString("It doesn't seem you are running on hyprland.\n"); + case HEADERS_MISSING: return failureString("Headers missing. Please run hyprpm update to fix those.\n"); case HEADERS_DUPLICATED: { - return std::string{Colors::RED} + "✖" + Colors::RESET + " Headers duplicated!!! This is a very bad sign.\n" + - " This could be due to e.g. installing hyprland manually while a system package of hyprland is also installed.\n" + - " If the above doesn't apply, check your /usr/include and /usr/local/include directories\n and remove all the hyprland headers.\n"; + return failureString("Headers duplicated!!! This is a very bad sign.\n" + "This could be due to e.g. installing hyprland manually while a system package of hyprland is also installed.\n" + "If the above doesn't apply, check your /usr/include and /usr/local/include directories\n and remove all the hyprland headers.\n"); } default: break; } - return std::string{Colors::RED} + "✖" + Colors::RESET + " Unknown header error. Please run hyprpm update to fix those.\n"; + return failureString("Unknown header error. Please run hyprpm update to fix those.\n"); } std::string CPluginManager::headerErrorShort(const eHeadersErrors err) { @@ -904,9 +910,9 @@ std::string CPluginManager::headerErrorShort(const eHeadersErrors err) { } bool CPluginManager::hasDeps() { - std::vector deps = {"meson", "cpio", "cmake"}; - for (auto& d : deps) { - if (!execAndGet("which " + d + " 2>&1").contains("/")) + std::vector deps = {"meson", "cpio", "cmake", "pkg-config"}; + for (auto const& d : deps) { + if (!execAndGet("command -v " + d).contains("/")) return false; } diff --git a/hyprpm/src/core/PluginManager.hpp b/hyprpm/src/core/PluginManager.hpp index c16a9d0f..d4603084 100644 --- a/hyprpm/src/core/PluginManager.hpp +++ b/hyprpm/src/core/PluginManager.hpp @@ -2,6 +2,7 @@ #include #include +#include enum eHeadersErrors { HEADERS_OK = 0, @@ -68,7 +69,7 @@ class CPluginManager { std::string headerError(const eHeadersErrors err); std::string headerErrorShort(const eHeadersErrors err); - std::string m_szWorkingPluginDirectory = ""; + std::string m_szWorkingPluginDirectory; }; inline std::unique_ptr g_pPluginManager; diff --git a/hyprpm/src/helpers/StringUtils.hpp b/hyprpm/src/helpers/StringUtils.hpp new file mode 100644 index 00000000..c1a5fd98 --- /dev/null +++ b/hyprpm/src/helpers/StringUtils.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include "Colors.hpp" + +template +std::string statusString(const std::string_view emoji, const std::string_view color, const std::string_view fmt, Args&&... args) { + std::string ret = std::format("{}{}{} ", color, emoji, Colors::RESET); + ret += std::vformat(fmt, std::make_format_args(args...)); + return ret; +} + +template +std::string successString(const std::string_view fmt, Args&&... args) { + return statusString("✔", Colors::GREEN, fmt, args...); +} + +template +std::string failureString(const std::string_view fmt, Args&&... args) { + return statusString("✖", Colors::RED, fmt, args...); +} + +template +std::string verboseString(const std::string_view fmt, Args&&... args) { + return statusString("[v]", Colors::BLUE, fmt, args...); +} + +template +std::string infoString(const std::string_view fmt, Args&&... args) { + return statusString("→", Colors::RESET, fmt, args...); +} diff --git a/hyprpm/src/main.cpp b/hyprpm/src/main.cpp index 4f00f708..67f14aed 100644 --- a/hyprpm/src/main.cpp +++ b/hyprpm/src/main.cpp @@ -1,15 +1,16 @@ -#include "progress/CProgressBar.hpp" #include "helpers/Colors.hpp" +#include "helpers/StringUtils.hpp" #include "core/PluginManager.hpp" #include "core/DataState.hpp" -#include +#include #include #include +#include #include #include -const std::string HELP = R"#(┏ hyprpm, a Hyprland Plugin Manager +constexpr std::string_view HELP = R"#(┏ hyprpm, a Hyprland Plugin Manager ┃ ┣ add [url] [git rev] → Install a new plugin repository from git. Git revision ┃ is optional, when set, commit locks are ignored. @@ -30,14 +31,14 @@ const std::string HELP = R"#(┏ hyprpm, a Hyprland Plugin Manager ┗ )#"; -int main(int argc, char** argv, char** envp) { +int main(int argc, char** argv, char** envp) { std::vector ARGS{argc}; for (int i = 0; i < argc; ++i) { ARGS[i] = std::string{argv[i]}; } if (ARGS.size() < 2) { - std::cout << HELP; + std::println(stderr, "{}", HELP); return 1; } @@ -47,7 +48,7 @@ int main(int argc, char** argv, char** envp) { for (int i = 1; i < argc; ++i) { if (ARGS[i].starts_with("-")) { if (ARGS[i] == "--help" || ARGS[i] == "-h") { - std::cout << HELP; + std::println("{}", HELP); return 0; } else if (ARGS[i] == "--notify" || ARGS[i] == "-n") { notify = true; @@ -57,9 +58,9 @@ int main(int argc, char** argv, char** envp) { noShallow = true; } else if (ARGS[i] == "--force" || ARGS[i] == "-f") { force = true; - std::cout << Colors::RED << "!" << Colors::RESET << " Using --force, I hope you know what you are doing.\n"; + std::println("{}", statusString("!", Colors::RED, "Using --force, I hope you know what you are doing.")); } else { - std::cerr << "Unrecognized option " << ARGS[i] << "\n"; + std::println(stderr, "Unrecognized option {}", ARGS[i]); return 1; } } else { @@ -68,7 +69,7 @@ int main(int argc, char** argv, char** envp) { } if (command.empty()) { - std::cout << HELP; + std::println(stderr, "{}", HELP); return 0; } @@ -78,7 +79,7 @@ int main(int argc, char** argv, char** envp) { if (command[0] == "add") { if (command.size() < 2) { - std::cerr << Colors::RED << "✖" << Colors::RESET << " Not enough args for add.\n"; + std::println(stderr, "{}", failureString("Not enough args for add.")); return 1; } @@ -90,7 +91,7 @@ int main(int argc, char** argv, char** envp) { return g_pPluginManager->addNewPluginRepo(command[1], rev) ? 0 : 1; } else if (command[0] == "remove") { if (ARGS.size() < 2) { - std::cerr << Colors::RED << "✖" << Colors::RESET << " Not enough args for remove.\n"; + std::println(stderr, "{}", failureString("Not enough args for remove.")); return 1; } @@ -116,12 +117,12 @@ int main(int argc, char** argv, char** envp) { g_pPluginManager->notify(ICON_ERROR, 0, 10000, "[hyprpm] Couldn't update headers"); } else if (command[0] == "enable") { if (ARGS.size() < 2) { - std::cerr << Colors::RED << "✖" << Colors::RESET << " Not enough args for enable.\n"; + std::println(stderr, "{}", failureString("Not enough args for enable.")); return 1; } if (!g_pPluginManager->enablePlugin(command[1])) { - std::cerr << Colors::RED << "✖" << Colors::RESET << " Couldn't enable plugin (missing?)\n"; + std::println(stderr, "{}", failureString("Couldn't enable plugin (missing?)")); return 1; } @@ -130,12 +131,12 @@ int main(int argc, char** argv, char** envp) { return 1; } else if (command[0] == "disable") { if (command.size() < 2) { - std::cerr << Colors::RED << "✖" << Colors::RESET << " Not enough args for disable.\n"; + std::println(stderr, "{}", failureString("Not enough args for disable.")); return 1; } if (!g_pPluginManager->disablePlugin(command[1])) { - std::cerr << Colors::RED << "✖" << Colors::RESET << " Couldn't disable plugin (missing?)\n"; + std::println(stderr, "{}", failureString("Couldn't disable plugin (missing?)")); return 1; } @@ -160,9 +161,9 @@ int main(int argc, char** argv, char** envp) { } else if (command[0] == "list") { g_pPluginManager->listAllPlugins(); } else { - std::cout << HELP; + std::println(stderr, "{}", HELP); return 1; } return 0; -} \ No newline at end of file +} diff --git a/hyprpm/src/meson.build b/hyprpm/src/meson.build index e2c512a5..2ef6c323 100644 --- a/hyprpm/src/meson.build +++ b/hyprpm/src/meson.build @@ -1,15 +1,31 @@ globber = run_command('sh', '-c', 'find . -name "*.cpp" | sort', check: true) src = globber.stdout().strip().split('\n') -executable('hyprpm', src, +executable( + 'hyprpm', + src, dependencies: [ dependency('hyprutils', version: '>= 0.1.1'), dependency('threads'), - dependency('tomlplusplus') + dependency('tomlplusplus'), ], - install : true + install: true, ) -install_data('../hyprpm.bash', install_dir: join_paths(get_option('datadir'), 'bash-completion/completions'), install_tag: 'runtime', rename: 'hyprpm') -install_data('../hyprpm.fish', install_dir: join_paths(get_option('datadir'), 'fish/vendor_completions.d'), install_tag: 'runtime') -install_data('../hyprpm.zsh', install_dir: join_paths(get_option('datadir'), 'zsh/site-functions'), install_tag: 'runtime', rename: '_hyprpm') +install_data( + '../hyprpm.bash', + install_dir: join_paths(get_option('datadir'), 'bash-completion/completions'), + install_tag: 'runtime', + rename: 'hyprpm', +) +install_data( + '../hyprpm.fish', + install_dir: join_paths(get_option('datadir'), 'fish/vendor_completions.d'), + install_tag: 'runtime', +) +install_data( + '../hyprpm.zsh', + install_dir: join_paths(get_option('datadir'), 'zsh/site-functions'), + install_tag: 'runtime', + rename: '_hyprpm', +) diff --git a/hyprpm/src/progress/CProgressBar.cpp b/hyprpm/src/progress/CProgressBar.cpp index 45602f82..9f2df08a 100644 --- a/hyprpm/src/progress/CProgressBar.cpp +++ b/hyprpm/src/progress/CProgressBar.cpp @@ -1,11 +1,11 @@ #include "CProgressBar.hpp" -#include +#include #include #include #include -#include +#include #include #include @@ -16,11 +16,12 @@ void CProgressBar::printMessageAbove(const std::string& msg) { ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); std::string spaces; + spaces.reserve(w.ws_col); for (size_t i = 0; i < w.ws_col; ++i) { spaces += ' '; } - std::cout << "\r" << spaces << "\r" << msg << "\n"; + std::println("\r{}\r{}", spaces, msg); print(); } @@ -29,15 +30,16 @@ void CProgressBar::print() { ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); if (m_bFirstPrint) - std::cout << "\n"; + std::print("\n"); m_bFirstPrint = false; std::string spaces; + spaces.reserve(w.ws_col); for (size_t i = 0; i < w.ws_col; ++i) { spaces += ' '; } - std::cout << "\r" << spaces << "\r"; + std::print("\r{}\r", spaces); std::string message = ""; @@ -74,7 +76,7 @@ void CProgressBar::print() { message += " " + std::format("{} / {}", m_iSteps, m_iMaxSteps) + " "; // draw message - std::cout << message + " " + m_szCurrentMessage; + std::print("{} {}", message, m_szCurrentMessage); std::fflush(stdout); -} \ No newline at end of file +} diff --git a/hyprpm/src/progress/CProgressBar.hpp b/hyprpm/src/progress/CProgressBar.hpp index 6ac18f21..dfcb36f7 100644 --- a/hyprpm/src/progress/CProgressBar.hpp +++ b/hyprpm/src/progress/CProgressBar.hpp @@ -14,4 +14,4 @@ class CProgressBar { private: bool m_bFirstPrint = true; -}; \ No newline at end of file +}; diff --git a/meson.build b/meson.build index 886f4f9c..33e8fdbe 100644 --- a/meson.build +++ b/meson.build @@ -1,13 +1,17 @@ -project('Hyprland', 'cpp', 'c', - version : run_command('cat', join_paths(meson.source_root(), 'VERSION'), check: true).stdout().strip(), - default_options : [ +project( + 'Hyprland', + 'cpp', + 'c', + version: run_command('cat', join_paths(meson.project_source_root(), 'VERSION'), check: true).stdout().strip(), + default_options: [ 'warning_level=2', 'default_library=static', 'optimization=3', 'buildtype=release', 'debug=false', - 'cpp_std=c++23', - ]) + 'cpp_std=c++26', + ], +) datarootdir = '-DDATAROOTDIR="' + get_option('prefix') / get_option('datadir') + '"' add_project_arguments( @@ -16,16 +20,20 @@ add_project_arguments( '-Wno-unused-value', '-Wno-missing-field-initializers', '-Wno-narrowing', - '-Wno-pointer-arith', - datarootdir, + '-Wno-pointer-arith', datarootdir, + '-DHYPRLAND_VERSION="' + meson.project_version() + '"', ], - language: 'cpp') + language: 'cpp', +) cpp_compiler = meson.get_compiler('cpp') if cpp_compiler.check_header('execinfo.h') add_project_arguments('-DHAS_EXECINFO', language: 'cpp') endif +aquamarine = dependency('aquamarine', version: '>=0.4.2') +add_project_arguments(['-DAQUAMARINE_VERSION="@0@"'.format(aquamarine.version())], language: 'cpp') + xcb_dep = dependency('xcb', required: get_option('xwayland')) xcb_composite_dep = dependency('xcb-composite', required: get_option('xwayland')) xcb_errors_dep = dependency('xcb-errors', required: get_option('xwayland')) @@ -34,9 +42,7 @@ xcb_render_dep = dependency('xcb-render', required: get_option('xwayland')) xcb_res_dep = dependency('xcb-res', required: get_option('xwayland')) xcb_xfixes_dep = dependency('xcb-xfixes', required: get_option('xwayland')) -cmake = import('cmake') -udis = cmake.subproject('udis86') -udis86 = udis.dependency('libudis86') +gio_dep = dependency('gio-2.0', required: true) if not xcb_dep.found() add_project_arguments('-DNO_XWAYLAND', language: 'cpp') @@ -45,6 +51,7 @@ endif backtrace_dep = cpp_compiler.find_library('execinfo', required: false) epoll_dep = dependency('epoll-shim', required: false) # timerfd on BSDs +# Handle options if get_option('systemd').enabled() add_project_arguments('-DUSES_SYSTEMD', language: 'cpp') endif @@ -57,14 +64,22 @@ if get_option('buildtype') == 'debug' add_project_arguments('-DHYPRLAND_DEBUG', language: 'cpp') endif -version_h = run_command('sh', '-c', 'scripts/generateVersion.sh', check: true) +# Generate hyprland version and populate version.h +run_command('sh', '-c', 'scripts/generateVersion.sh', check: true) +# Install headers globber = run_command('find', 'src', '-name', '*.h*', check: true) headers = globber.stdout().strip().split('\n') foreach file : headers install_headers(file, subdir: 'hyprland', preserve_path: true) endforeach +tracy = dependency('tracy', static: true, required: get_option('tracy_enable')) + +if get_option('tracy_enable') and get_option('buildtype') != 'debugoptimized' + warning('Profiling builds should set -- buildtype = debugoptimized') +endif + subdir('protocols') subdir('src') subdir('hyprctl') @@ -73,6 +88,7 @@ subdir('assets') subdir('example') subdir('docs') +# Generate hyprland.pc pkg_install_dir = join_paths(get_option('datadir'), 'pkgconfig') import('pkgconfig').generate( diff --git a/meson_options.txt b/meson_options.txt index 16a34a54..d8c9d5e6 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,3 +1,4 @@ option('xwayland', type: 'feature', value: 'auto', description: 'Enable support for X11 applications') option('systemd', type: 'feature', value: 'auto', description: 'Enable systemd integration') option('legacy_renderer', type: 'feature', value: 'disabled', description: 'Enable legacy renderer') +option('tracy_enable', type: 'boolean', value: false , description: 'Enable profiling') diff --git a/nix/default.nix b/nix/default.nix index e4e12f43..d463e271 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -1,43 +1,33 @@ { lib, stdenv, + stdenvAdapters, pkg-config, pkgconf, makeWrapper, - cmake, + meson, ninja, aquamarine, binutils, cairo, - expat, - fribidi, git, - hwdata, hyprcursor, + hyprland-protocols, hyprlang, hyprutils, hyprwayland-scanner, - jq, libGL, - libdatrie, - libdisplay-info, libdrm, libexecinfo, libinput, - libliftoff, - libselinux, - libsepol, - libthai, - libuuid, libxkbcommon, + libuuid, mesa, pango, pciutils, - pcre2, - python3, - seatd, systemd, tomlplusplus, + udis86-hyprland, wayland, wayland-protocols, wayland-scanner, @@ -50,134 +40,146 @@ wrapRuntimeDeps ? true, version ? "git", commit, + revCount, date, # deprecated flags enableNvidiaPatches ? false, nvidiaPatches ? false, hidpiXWayland ? false, -}: -assert lib.assertMsg (!nvidiaPatches) "The option `nvidiaPatches` has been removed."; -assert lib.assertMsg (!enableNvidiaPatches) "The option `enableNvidiaPatches` has been removed."; -assert lib.assertMsg (!hidpiXWayland) "The option `hidpiXWayland` has been removed. Please refer https://wiki.hyprland.org/Configuring/XWayland"; - stdenv.mkDerivation { - pname = "hyprland${lib.optionalString debug "-debug"}"; - inherit version; +}: let + inherit (builtins) baseNameOf foldl'; + inherit (lib.asserts) assertMsg; + inherit (lib.attrsets) mapAttrsToList; + inherit (lib.lists) flatten concatLists optional optionals; + inherit (lib.sources) cleanSourceWith cleanSource; + inherit (lib.strings) hasSuffix makeBinPath optionalString mesonBool mesonEnable; - src = lib.cleanSourceWith { - filter = name: type: let - baseName = baseNameOf (toString name); - in - ! (lib.hasSuffix ".nix" baseName); - src = lib.cleanSource ../.; - }; + adapters = flatten [ + stdenvAdapters.useMoldLinker + ]; - postPatch = '' - # Fix hardcoded paths to /usr installation - sed -i "s#/usr#$out#" src/render/OpenGL.cpp + customStdenv = foldl' (acc: adapter: adapter acc) stdenv adapters; +in + assert assertMsg (!nvidiaPatches) "The option `nvidiaPatches` has been removed."; + assert assertMsg (!enableNvidiaPatches) "The option `enableNvidiaPatches` has been removed."; + assert assertMsg (!hidpiXWayland) "The option `hidpiXWayland` has been removed. Please refer https://wiki.hyprland.org/Configuring/XWayland"; + customStdenv.mkDerivation { + pname = "hyprland${optionalString debug "-debug"}"; + inherit version; - # Remove extra @PREFIX@ to fix pkg-config paths - sed -i "s#@PREFIX@/##g" hyprland.pc.in - ''; + src = cleanSourceWith { + filter = name: _type: let + baseName = baseNameOf (toString name); + in + ! (hasSuffix ".nix" baseName); + src = cleanSource ../.; + }; - COMMITS = commit; - DATE = date; - DIRTY = lib.optionalString (commit == "") "dirty"; - HASH = commit; + postPatch = '' + # Fix hardcoded paths to /usr installation + sed -i "s#/usr#$out#" src/render/OpenGL.cpp - nativeBuildInputs = [ - hyprwayland-scanner - jq - makeWrapper - cmake - ninja - pkg-config - python3 # for udis86 - wayland-scanner - ]; + # Remove extra @PREFIX@ to fix pkg-config paths + sed -i "s#@PREFIX@/##g" hyprland.pc.in + ''; - outputs = [ - "out" - "man" - "dev" - ]; + COMMITS = revCount; + DATE = date; + DIRTY = optionalString (commit == "") "dirty"; + HASH = commit; - buildInputs = lib.concatLists [ - [ - aquamarine - cairo - expat - fribidi - git - hwdata - hyprcursor - hyprlang - hyprutils - libdatrie - libdisplay-info - libdrm - libGL - libinput - libliftoff - libselinux - libsepol - libthai - libuuid - libxkbcommon - mesa - pango - pciutils - pcre2 - seatd - tomlplusplus - wayland - wayland-protocols - ] - (lib.optionals stdenv.hostPlatform.isMusl [libexecinfo]) - (lib.optionals enableXWayland [ - xorg.libxcb - xorg.libXcursor - xorg.libXdmcp - xorg.xcbutil - xorg.xcbutilerrors - xorg.xcbutilrenderutil - xorg.xcbutilwm - xwayland - ]) - (lib.optionals withSystemd [systemd]) - ]; + depsBuildBuild = [ + pkg-config + ]; - cmakeBuildType = - if debug - then "Debug" - else "RelWithDebInfo"; + nativeBuildInputs = [ + hyprwayland-scanner + makeWrapper + meson + ninja + pkg-config + ]; - # we want as much debug info as possible - dontStrip = debug; + outputs = [ + "out" + "man" + "dev" + ]; - cmakeFlags = [ - (lib.cmakeBool "NO_XWAYLAND" (!enableXWayland)) - (lib.cmakeBool "LEGACY_RENDERER" legacyRenderer) - (lib.cmakeBool "NO_SYSTEMD" (!withSystemd)) - ]; - - postInstall = '' - ${lib.optionalString wrapRuntimeDeps '' - wrapProgram $out/bin/Hyprland \ - --suffix PATH : ${lib.makeBinPath [ - binutils + buildInputs = concatLists [ + [ + aquamarine + cairo + git + hyprcursor + hyprland-protocols + hyprlang + hyprutils + libdrm + libGL + libinput + libuuid + libxkbcommon + mesa + pango pciutils - pkgconf - ]} - ''} - ''; + tomlplusplus + udis86-hyprland + wayland + wayland-protocols + wayland-scanner + xorg.libXcursor + ] + (optionals customStdenv.hostPlatform.isMusl [libexecinfo]) + (optionals enableXWayland [ + xorg.libxcb + xorg.libXdmcp + xorg.xcbutilerrors + xorg.xcbutilrenderutil + xorg.xcbutilwm + xwayland + ]) + (optional withSystemd systemd) + ]; - passthru.providedSessions = ["hyprland"]; + mesonBuildType = + if debug + then "debugoptimized" + else "release"; - meta = { - homepage = "https://github.com/hyprwm/Hyprland"; - description = "Dynamic tiling Wayland compositor that doesn't sacrifice on its looks"; - license = lib.licenses.bsd3; - platforms = lib.platforms.linux; - mainProgram = "Hyprland"; - }; - } + # we want as much debug info as possible + dontStrip = debug; + + mesonFlags = flatten [ + (mapAttrsToList mesonEnable { + "xwayland" = enableXWayland; + "legacy_renderer" = legacyRenderer; + "systemd" = withSystemd; + }) + (mapAttrsToList mesonBool { + "b_pch" = false; + "tracy_enable" = false; + }) + ]; + + postInstall = '' + ${optionalString wrapRuntimeDeps '' + wrapProgram $out/bin/Hyprland \ + --suffix PATH : ${makeBinPath [ + binutils + pciutils + pkgconf + ]} + ''} + ''; + + passthru.providedSessions = ["hyprland"]; + + meta = { + homepage = "https://github.com/hyprwm/Hyprland"; + description = "Dynamic tiling Wayland compositor that doesn't sacrifice on its looks"; + license = lib.licenses.bsd3; + platforms = lib.platforms.linux; + mainProgram = "Hyprland"; + }; + } diff --git a/nix/formatter.nix b/nix/formatter.nix new file mode 100644 index 00000000..66721c2c --- /dev/null +++ b/nix/formatter.nix @@ -0,0 +1,64 @@ +{ + writeShellApplication, + deadnix, + statix, + alejandra, + llvmPackages_19, + fd, +}: +writeShellApplication { + name = "hyprland-treewide-formatter"; + runtimeInputs = [ + deadnix + statix + alejandra + llvmPackages_19.clang-tools + fd + ]; + text = '' + # shellcheck disable=SC2148 + + # common excludes + excludes="subprojects" + + nix_format() { + if [ "$*" = 0 ]; then + fd '.*\.nix' . -E "$excludes" -x statix fix -- {} \; + fd '.*\.nix' . -E "$excludes" -X deadnix -e -- {} \; -X alejandra {} \; + elif [ -d "$1" ]; then + fd '.*\.nix' "$1" -E "$excludes" -i -x statix fix -- {} \; + fd '.*\.nix' "$1" -E "$excludes" -i -X deadnix -e -- {} \; -X alejandra {} \; + else + statix fix -- "$1" + deadnix -e "$1" + alejandra "$1" + fi + } + + cpp_format() { + if [ "$*" = 0 ] || [ "$1" = "." ]; then + fd '.*\.cpp' . -E "$excludes" | xargs clang-format --verbose -i + elif [ -d "$1" ]; then + fd '.*\.cpp' "$1" -E "$excludes" | xargs clang-format --verbose -i + else + clang-format --verbose -i "$1" + fi + } + + for i in "$@"; do + case ''${i##*.} in + "nix") + nix_format "$i" + ;; + "cpp") + cpp_format "$i" + ;; + *) + nix_format "$i" + cpp_format "$i" + ;; + esac + + done + ''; +} diff --git a/nix/overlays.nix b/nix/overlays.nix index d8979b45..a78f3003 100644 --- a/nix/overlays.nix +++ b/nix/overlays.nix @@ -22,18 +22,21 @@ in { # Dependencies inputs.aquamarine.overlays.default inputs.hyprcursor.overlays.default + inputs.hyprland-protocols.overlays.default inputs.hyprlang.overlays.default inputs.hyprutils.overlays.default inputs.hyprwayland-scanner.overlays.default + self.overlays.udis86 # Hyprland packages themselves - (final: prev: let + (final: _prev: let date = mkDate (self.lastModifiedDate or "19700101"); in { hyprland = final.callPackage ./default.nix { - stdenv = final.gcc13Stdenv; + stdenv = final.gcc14Stdenv; version = "${version}+date=${date}_${self.shortRev or "dirty"}"; commit = self.rev or ""; + revCount = self.sourceInfo.revCount or ""; inherit date; }; hyprland-unwrapped = final.hyprland.override {wrapRuntimeDeps = false;}; @@ -62,4 +65,20 @@ in { hyprland-extras = lib.composeManyExtensions [ inputs.xdph.overlays.xdg-desktop-portal-hyprland ]; + + # udis86 from nixpkgs is too old, and also does not provide a .pc file + # this version is the one used in the git submodule, and allows us to + # fetch the source without '?submodules=1' + udis86 = final: prev: { + udis86-hyprland = prev.udis86.overrideAttrs (_self: _super: { + src = final.fetchFromGitHub { + owner = "canihavesomecoffee"; + repo = "udis86"; + rev = "5336633af70f3917760a6d441ff02d93477b0c86"; + hash = "sha256-HifdUQPGsKQKQprByeIznvRLONdOXeolOsU5nkwIv3g="; + }; + + patches = []; + }); + }; } diff --git a/protocols/meson.build b/protocols/meson.build index 5b807914..2c0d06d1 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -1,76 +1,78 @@ -wayland_protos = dependency('wayland-protocols', +wayland_protos = dependency( + 'wayland-protocols', version: '>=1.32', fallback: 'wayland-protocols', default_options: ['tests=false'], ) -hyprland_protos = dependency('hyprland-protocols', - version: '>=0.2', - fallback: 'hyprland-protocols', +hyprland_protos = dependency( + 'hyprland-protocols', + version: '>=0.4', + fallback: 'hyprland-protocols', ) -wl_protocol_dir = wayland_protos.get_variable('pkgdatadir') -hl_protocol_dir = hyprland_protos.get_variable('pkgdatadir') +wayland_protocol_dir = wayland_protos.get_variable('pkgdatadir') +hyprland_protocol_dir = hyprland_protos.get_variable('pkgdatadir') -hyprwayland_scanner_dep = dependency('hyprwayland-scanner', version: '>=0.3.8', native: true) +hyprwayland_scanner_dep = dependency('hyprwayland-scanner', version: '>=0.3.10', native: true) hyprwayland_scanner = find_program( hyprwayland_scanner_dep.get_variable('hyprwayland_scanner'), native: true, ) -new_protocols = [ - ['wlr-gamma-control-unstable-v1.xml'], - ['wlr-foreign-toplevel-management-unstable-v1.xml'], - ['wlr-output-power-management-unstable-v1.xml'], - ['input-method-unstable-v2.xml'], - ['virtual-keyboard-unstable-v1.xml'], - ['wlr-virtual-pointer-unstable-v1.xml'], - ['wlr-output-management-unstable-v1.xml'], - ['kde-server-decoration.xml'], - ['wlr-layer-shell-unstable-v1.xml'], - ['wayland-drm.xml'], - ['wlr-data-control-unstable-v1.xml'], - ['wlr-screencopy-unstable-v1.xml'], - [hl_protocol_dir, 'protocols/hyprland-global-shortcuts-v1.xml'], - [hl_protocol_dir, 'protocols/hyprland-toplevel-export-v1.xml'], - [hl_protocol_dir, 'protocols/hyprland-focus-grab-v1.xml'], - [wl_protocol_dir, 'staging/tearing-control/tearing-control-v1.xml'], - [wl_protocol_dir, 'staging/fractional-scale/fractional-scale-v1.xml'], - [wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'], - [wl_protocol_dir, 'staging/cursor-shape/cursor-shape-v1.xml'], - [wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'], - [wl_protocol_dir, 'unstable/relative-pointer/relative-pointer-unstable-v1.xml'], - [wl_protocol_dir, 'unstable/xdg-decoration/xdg-decoration-unstable-v1.xml'], - [wl_protocol_dir, 'staging/alpha-modifier/alpha-modifier-v1.xml'], - [wl_protocol_dir, 'staging/ext-foreign-toplevel-list/ext-foreign-toplevel-list-v1.xml'], - [wl_protocol_dir, 'unstable/pointer-gestures/pointer-gestures-unstable-v1.xml'], - [wl_protocol_dir, 'unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml'], - [wl_protocol_dir, 'unstable/text-input/text-input-unstable-v3.xml'], - [wl_protocol_dir, 'unstable/text-input/text-input-unstable-v1.xml'], - [wl_protocol_dir, 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'], - [wl_protocol_dir, 'staging/xdg-activation/xdg-activation-v1.xml'], - [wl_protocol_dir, 'staging/ext-idle-notify/ext-idle-notify-v1.xml'], - [wl_protocol_dir, 'staging/ext-session-lock/ext-session-lock-v1.xml'], - [wl_protocol_dir, 'stable/tablet/tablet-v2.xml'], - [wl_protocol_dir, 'stable/presentation-time/presentation-time.xml'], - [wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'], - [wl_protocol_dir, 'unstable/primary-selection/primary-selection-unstable-v1.xml'], - [wl_protocol_dir, 'staging/xwayland-shell/xwayland-shell-v1.xml'], - [wl_protocol_dir, 'stable/viewporter/viewporter.xml'], - [wl_protocol_dir, 'stable/linux-dmabuf/linux-dmabuf-v1.xml'], - [wl_protocol_dir, 'staging/drm-lease/drm-lease-v1.xml'], - [wl_protocol_dir, 'staging/linux-drm-syncobj/linux-drm-syncobj-v1.xml'], +protocols = [ + 'wlr-gamma-control-unstable-v1.xml', + 'wlr-foreign-toplevel-management-unstable-v1.xml', + 'wlr-output-power-management-unstable-v1.xml', + 'input-method-unstable-v2.xml', + 'virtual-keyboard-unstable-v1.xml', + 'wlr-virtual-pointer-unstable-v1.xml', + 'wlr-output-management-unstable-v1.xml', + 'kde-server-decoration.xml', + 'wlr-layer-shell-unstable-v1.xml', + 'wayland-drm.xml', + 'wlr-data-control-unstable-v1.xml', + 'wlr-screencopy-unstable-v1.xml', + hyprland_protocol_dir / 'protocols/hyprland-global-shortcuts-v1.xml', + hyprland_protocol_dir / 'protocols/hyprland-toplevel-export-v1.xml', + hyprland_protocol_dir / 'protocols/hyprland-focus-grab-v1.xml', + hyprland_protocol_dir / 'protocols/hyprland-ctm-control-v1.xml', + wayland_protocol_dir / 'staging/tearing-control/tearing-control-v1.xml', + wayland_protocol_dir / 'staging/fractional-scale/fractional-scale-v1.xml', + wayland_protocol_dir / 'unstable/xdg-output/xdg-output-unstable-v1.xml', + wayland_protocol_dir / 'staging/cursor-shape/cursor-shape-v1.xml', + wayland_protocol_dir / 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml', + wayland_protocol_dir / 'unstable/relative-pointer/relative-pointer-unstable-v1.xml', + wayland_protocol_dir / 'unstable/xdg-decoration/xdg-decoration-unstable-v1.xml', + wayland_protocol_dir / 'staging/alpha-modifier/alpha-modifier-v1.xml', + wayland_protocol_dir / 'staging/ext-foreign-toplevel-list/ext-foreign-toplevel-list-v1.xml', + wayland_protocol_dir / 'unstable/pointer-gestures/pointer-gestures-unstable-v1.xml', + wayland_protocol_dir / 'unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml', + wayland_protocol_dir / 'unstable/text-input/text-input-unstable-v3.xml', + wayland_protocol_dir / 'unstable/text-input/text-input-unstable-v1.xml', + wayland_protocol_dir / 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml', + wayland_protocol_dir / 'staging/xdg-activation/xdg-activation-v1.xml', + wayland_protocol_dir / 'staging/ext-idle-notify/ext-idle-notify-v1.xml', + wayland_protocol_dir / 'staging/ext-session-lock/ext-session-lock-v1.xml', + wayland_protocol_dir / 'stable/tablet/tablet-v2.xml', + wayland_protocol_dir / 'stable/presentation-time/presentation-time.xml', + wayland_protocol_dir / 'stable/xdg-shell/xdg-shell.xml', + wayland_protocol_dir / 'unstable/primary-selection/primary-selection-unstable-v1.xml', + wayland_protocol_dir / 'staging/xwayland-shell/xwayland-shell-v1.xml', + wayland_protocol_dir / 'stable/viewporter/viewporter.xml', + wayland_protocol_dir / 'stable/linux-dmabuf/linux-dmabuf-v1.xml', + wayland_protocol_dir / 'staging/drm-lease/drm-lease-v1.xml', + wayland_protocol_dir / 'staging/linux-drm-syncobj/linux-drm-syncobj-v1.xml', + wayland_protocol_dir / 'staging/xdg-dialog/xdg-dialog-v1.xml', + wayland_protocol_dir / 'staging/single-pixel-buffer/single-pixel-buffer-v1.xml', + wayland_protocol_dir / 'staging/security-context/security-context-v1.xml', ] -wl_protos_src = [] -wl_protos_headers = [] - -new_wl_protos = [] -foreach p : new_protocols - xml = join_paths(p) - new_wl_protos += custom_target( - xml.underscorify(), - input: xml, +wl_protocols = [] +foreach protocol : protocols + wl_protocols += custom_target( + protocol.underscorify(), + input: protocol, install: true, install_dir: [false, join_paths(get_option('includedir'), 'hyprland/protocols')], output: ['@BASENAME@.cpp', '@BASENAME@.hpp'], @@ -78,31 +80,26 @@ foreach p : new_protocols ) endforeach -wayland_server_dep = dependency('wayland-server', version: '>=1.20.0') -wayland_server_dir = wayland_server_dep.get_variable('pkgdatadir') +# wayland.xml generation +wayland_scanner = dependency('wayland-scanner') +wayland_scanner_datadir = wayland_scanner.get_variable('pkgdatadir') -wl_server_protos = [ - wayland_server_dir / 'wayland.xml' -] -wl_server_protos_gen = [] -foreach p : wl_server_protos - wl_server_protos_gen += custom_target( - p.underscorify(), - input: p, - install: true, - install_dir: [false, join_paths(get_option('includedir'), 'hyprland/protocols')], - output: ['@BASENAME@.cpp', '@BASENAME@.hpp'], - command: [hyprwayland_scanner, '--wayland-enums', '@INPUT@', '@OUTDIR@'], - ) -endforeach +wayland_xml = wayland_scanner_datadir / 'wayland.xml' +wayland_protocol = custom_target( + wayland_xml.underscorify(), + input: wayland_xml, + install: true, + install_dir: [false, join_paths(get_option('includedir'), 'hyprland/protocols')], + output: ['@BASENAME@.cpp', '@BASENAME@.hpp'], + command: [hyprwayland_scanner, '--wayland-enums', '@INPUT@', '@OUTDIR@'], +) lib_server_protos = static_library( 'server_protos', - wl_protos_src + wl_protos_headers + new_wl_protos + wl_server_protos_gen, - dependencies: wayland_server_dep.partial_dependency(compile_args: true), + wl_protocols + wayland_protocol, ) server_protos = declare_dependency( link_with: lib_server_protos, - sources: wl_protos_headers + new_wl_protos, + sources: wl_protocols + wayland_protocol, ) diff --git a/scripts/hyprlandStaticAsan.diff b/scripts/hyprlandStaticAsan.diff index e74eb6ab..b352d65d 100644 --- a/scripts/hyprlandStaticAsan.diff +++ b/scripts/hyprlandStaticAsan.diff @@ -1,13 +1,13 @@ diff --git a/CMakeLists.txt b/CMakeLists.txt -index f54cdf5d..ad7c3e73 100755 +index f26a5c3c..3dfef333 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt -@@ -130,6 +130,8 @@ if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) - message(STATUS "Enabling ASan") +@@ -143,6 +143,8 @@ if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) + message(STATUS "Enabling ASan") - target_link_libraries(Hyprland asan) -+ pkg_check_modules(ffidep REQUIRED IMPORTED_TARGET libffi) -+ target_link_libraries(Hyprland ${CMAKE_SOURCE_DIR}/libwayland-server.a PkgConfig::ffidep) - target_compile_options(Hyprland PUBLIC -fsanitize=address) - endif() + target_link_libraries(Hyprland asan) ++ pkg_check_modules(ffidep REQUIRED IMPORTED_TARGET libffi) ++ target_link_libraries(Hyprland ${CMAKE_SOURCE_DIR}/libwayland-server.a PkgConfig::ffidep) + target_compile_options(Hyprland PUBLIC -fsanitize=address) + endif() diff --git a/src/Compositor.cpp b/src/Compositor.cpp index a139742c..fd4979b0 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -1,4 +1,5 @@ #include "Compositor.hpp" +#include "debug/Log.hpp" #include "helpers/Splashes.hpp" #include "config/ConfigValue.hpp" #include "managers/CursorManager.hpp" @@ -8,7 +9,9 @@ #include "managers/eventLoop/EventLoopManager.hpp" #include #include +#include #include +#include #include #include #include @@ -23,11 +26,14 @@ #include "protocols/PointerConstraints.hpp" #include "protocols/LayerShell.hpp" #include "protocols/XDGShell.hpp" +#include "protocols/XDGOutput.hpp" +#include "protocols/SecurityContext.hpp" #include "protocols/core/Compositor.hpp" #include "protocols/core/Subcompositor.hpp" #include "desktop/LayerSurface.hpp" #include "render/Renderer.hpp" #include "xwayland/XWayland.hpp" +#include "helpers/ByteOperations.hpp" #include #include @@ -134,37 +140,37 @@ CCompositor::CCompositor() { m_szHyprTempDataRoot = std::string{getenv("XDG_RUNTIME_DIR")} + "/hypr"; if (m_szHyprTempDataRoot.starts_with("/hypr")) { - std::cout << "Bailing out, XDG_RUNTIME_DIR is invalid\n"; + std::println("Bailing out, $XDG_RUNTIME_DIR is invalid"); throw std::runtime_error("CCompositor() failed"); } if (!m_szHyprTempDataRoot.starts_with("/run/user")) - std::cout << "[!!WARNING!!] XDG_RUNTIME_DIR looks non-standard. Proceeding anyways...\n"; + std::println("[!!WARNING!!] XDG_RUNTIME_DIR looks non-standard. Proceeding anyways..."); std::random_device dev; std::mt19937 engine(dev()); std::uniform_int_distribution<> distribution(0, INT32_MAX); - m_szInstanceSignature = GIT_COMMIT_HASH + std::string("_") + std::to_string(time(NULL)) + "_" + std::to_string(distribution(engine)); + m_szInstanceSignature = std::format("{}_{}_{}", GIT_COMMIT_HASH, std::time(NULL), distribution(engine)); setenv("HYPRLAND_INSTANCE_SIGNATURE", m_szInstanceSignature.c_str(), true); if (!std::filesystem::exists(m_szHyprTempDataRoot)) mkdir(m_szHyprTempDataRoot.c_str(), S_IRWXU); else if (!std::filesystem::is_directory(m_szHyprTempDataRoot)) { - std::cout << "Bailing out, " << m_szHyprTempDataRoot << " is not a directory\n"; + std::println("Bailing out, {} is not a directory", m_szHyprTempDataRoot); throw std::runtime_error("CCompositor() failed"); } m_szInstancePath = m_szHyprTempDataRoot + "/" + m_szInstanceSignature; if (std::filesystem::exists(m_szInstancePath)) { - std::cout << "Bailing out, " << m_szInstancePath << " exists??\n"; + std::println("Bailing out, {} exists??", m_szInstancePath); throw std::runtime_error("CCompositor() failed"); } if (mkdir(m_szInstancePath.c_str(), S_IRWXU) < 0) { - std::cout << "Bailing out, couldn't create " << m_szInstancePath << "\n"; + std::println("Bailing out, couldn't create {}", m_szInstancePath); throw std::runtime_error("CCompositor() failed"); } @@ -208,11 +214,22 @@ void CCompositor::setRandomSplash() { static std::vector> pendingOutputs; +// + +static bool filterGlobals(const wl_client* client, const wl_global* global, void* data) { + if (!PROTO::securityContext->isClientSandboxed(client)) + return true; + + return !g_pProtocolManager || !g_pProtocolManager->isGlobalPrivileged(global); +} + // void CCompositor::initServer(std::string socketName, int socketFd) { m_sWLDisplay = wl_display_create(); + wl_display_set_global_filter(m_sWLDisplay, ::filterGlobals, nullptr); + m_sWLEventLoop = wl_display_get_event_loop(m_sWLDisplay); // register crit signal handler @@ -229,6 +246,9 @@ void CCompositor::initServer(std::string socketName, int socketFd) { if (envEnabled("HYPRLAND_TRACE")) Debug::trace = true; + // set the buffer size to 1MB to avoid disconnects due to an app hanging for a short while + wl_display_set_default_max_buffer_size(m_sWLDisplay, 1_MB); + Aquamarine::SBackendOptions options; options.logFunction = aqLog; @@ -305,12 +325,16 @@ void CCompositor::initServer(std::string socketName, int socketFd) { setenv("WAYLAND_DISPLAY", m_szWLDisplaySocket.c_str(), 1); setenv("XDG_SESSION_TYPE", "wayland", 1); + if (!getenv("XDG_CURRENT_DESKTOP")) { + setenv("XDG_CURRENT_DESKTOP", "Hyprland", 1); + m_bDesktopEnvSet = true; + } initManagers(STAGE_BASICINIT); initManagers(STAGE_LATE); - for (auto& o : pendingOutputs) { + for (auto const& o : pendingOutputs) { onNewMonitor(o); } pendingOutputs.clear(); @@ -387,18 +411,19 @@ void CCompositor::initAllSignals() { m_bSessionActive = true; - for (auto& m : m_vMonitors) { + for (auto const& m : m_vMonitors) { scheduleFrameForMonitor(m.get()); g_pHyprRenderer->applyMonitorRule(m.get(), &m->activeMonitorRule, true); } g_pConfigManager->m_bWantsMonitorReload = true; + g_pCursorManager->syncGsettings(); } else { Debug::log(LOG, "Session got deactivated!"); m_bSessionActive = false; - for (auto& m : m_vMonitors) { + for (auto const& m : m_vMonitors) { m->noFrameSchedule = true; m->framesToSkip = 1; } @@ -421,9 +446,10 @@ void CCompositor::cleanEnvironment() { // in main unsetenv("HYPRLAND_CMD"); unsetenv("XDG_BACKEND"); - unsetenv("XDG_CURRENT_DESKTOP"); + if (m_bDesktopEnvSet) + unsetenv("XDG_CURRENT_DESKTOP"); - if (m_pAqBackend->hasSession()) { + if (m_pAqBackend->hasSession() && !envEnabled("HYPRLAND_NO_SD_VARS")) { const auto CMD = #ifdef USES_SYSTEMD "systemctl --user unset-environment DISPLAY WAYLAND_DISPLAY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP QT_QPA_PLATFORMTHEME PATH XDG_DATA_DIRS && hash " @@ -471,7 +497,7 @@ void CCompositor::cleanup() { m_vWorkspaces.clear(); m_vWindows.clear(); - for (auto& m : m_vMonitors) { + for (auto const& m : m_vMonitors) { g_pHyprOpenGL->destroyMonitorResources(m.get()); m->output->state->setEnabled(false); @@ -552,6 +578,10 @@ void CCompositor::initManagers(eManagersInitStage stage) { g_pConfigManager->init(); g_pWatchdog = std::make_unique(); // requires config + // wait for watchdog to initialize to not hit data races in reading config values. + while (!g_pWatchdog->m_bWatchdogInitialized) { + std::this_thread::yield(); + } Debug::log(LOG, "Creating the PointerManager!"); g_pPointerManager = std::make_unique(); @@ -605,7 +635,7 @@ void CCompositor::initManagers(eManagersInitStage stage) { g_pCursorManager = std::make_unique(); Debug::log(LOG, "Starting XWayland"); - g_pXWayland = std::make_unique(); + g_pXWayland = std::make_unique(g_pCompositor->m_bEnableXwayland); } break; default: UNREACHABLE(); } @@ -631,7 +661,7 @@ void CCompositor::removeLockFile() { void CCompositor::prepareFallbackOutput() { // create a backup monitor SP headless; - for (auto& impl : m_pAqBackend->getImplementations()) { + for (auto const& impl : m_pAqBackend->getImplementations()) { if (impl->type() == Aquamarine::AQ_BACKEND_HEADLESS) { headless = impl; break; @@ -649,7 +679,11 @@ void CCompositor::prepareFallbackOutput() { void CCompositor::startCompositor() { signal(SIGPIPE, SIG_IGN); - if (m_pAqBackend->hasSession() /* Session-less Hyprland usually means a nest, don't update the env in that case */) { + if ( + /* Session-less Hyprland usually means a nest, don't update the env in that case */ + m_pAqBackend->hasSession() && + /* Activation environment management is not disabled */ + !envEnabled("HYPRLAND_NO_SD_VARS")) { const auto CMD = #ifdef USES_SYSTEMD "systemctl --user import-environment DISPLAY WAYLAND_DISPLAY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP QT_QPA_PLATFORMTHEME PATH XDG_DATA_DIRS && hash " @@ -683,9 +717,9 @@ void CCompositor::startCompositor() { g_pEventLoopManager->enterLoop(); } -CMonitor* CCompositor::getMonitorFromID(const int& id) { - for (auto& m : m_vMonitors) { - if (m->ID == (uint64_t)id) { +CMonitor* CCompositor::getMonitorFromID(const MONITORID& id) { + for (auto const& m : m_vMonitors) { + if (m->ID == id) { return m.get(); } } @@ -694,7 +728,7 @@ CMonitor* CCompositor::getMonitorFromID(const int& id) { } CMonitor* CCompositor::getMonitorFromName(const std::string& name) { - for (auto& m : m_vMonitors) { + for (auto const& m : m_vMonitors) { if (m->szName == name) { return m.get(); } @@ -703,7 +737,7 @@ CMonitor* CCompositor::getMonitorFromName(const std::string& name) { } CMonitor* CCompositor::getMonitorFromDesc(const std::string& desc) { - for (auto& m : m_vMonitors) { + for (auto const& m : m_vMonitors) { if (m->szDescription.starts_with(desc)) return m.get(); } @@ -716,7 +750,7 @@ CMonitor* CCompositor::getMonitorFromCursor() { CMonitor* CCompositor::getMonitorFromVector(const Vector2D& point) { SP mon; - for (auto& m : m_vMonitors) { + for (auto const& m : m_vMonitors) { if (CBox{m->vecPosition, m->vecSize}.containsPoint(point)) { mon = m; break; @@ -727,7 +761,7 @@ CMonitor* CCompositor::getMonitorFromVector(const Vector2D& point) { float bestDistance = 0.f; SP pBestMon; - for (auto& m : m_vMonitors) { + for (auto const& m : m_vMonitors) { float dist = vecToRectDistanceSquared(point, m->vecPosition, m->vecPosition + m->vecSize); if (dist < bestDistance || !pBestMon) { @@ -757,7 +791,7 @@ void CCompositor::removeWindowFromVectorSafe(PHLWINDOW pWindow) { } bool CCompositor::monitorExists(CMonitor* pMonitor) { - for (auto& m : m_vRealMonitors) { + for (auto const& m : m_vRealMonitors) { if (m.get() == pMonitor) return true; } @@ -775,9 +809,9 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t proper // pinned windows on top of floating regardless if (properties & ALLOW_FLOATING) { - for (auto& w : m_vWindows | std::views::reverse) { + for (auto const& w : m_vWindows | std::views::reverse) { const auto BB = w->getWindowBoxUnified(properties); - CBox box = BB.copy().expand(w->m_iX11Type == 2 ? BORDER_GRAB_AREA : 0); + CBox box = BB.copy().expand(!w->isX11OverrideRedirect() ? BORDER_GRAB_AREA : 0); if (w->m_bIsFloating && w->m_bIsMapped && !w->isHidden() && !w->m_bX11ShouldntFocus && w->m_bPinned && !w->m_sWindowData.noFocus.valueOrDefault() && w != pIgnoreWindow) { if (box.containsPoint(g_pPointerManager->position())) @@ -793,7 +827,7 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t proper auto windowForWorkspace = [&](bool special) -> PHLWINDOW { auto floating = [&](bool aboveFullscreen) -> PHLWINDOW { - for (auto& w : m_vWindows | std::views::reverse) { + for (auto const& w : m_vWindows | std::views::reverse) { if (special && !w->onSpecialWorkspace()) // because special floating may creep up into regular continue; @@ -807,16 +841,16 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t proper BB.x + BB.width <= PWINDOWMONITOR->vecPosition.x + PWINDOWMONITOR->vecSize.x && BB.y + BB.height <= PWINDOWMONITOR->vecPosition.y + PWINDOWMONITOR->vecSize.y) continue; - CBox box = BB.copy().expand(w->m_iX11Type == 2 ? BORDER_GRAB_AREA : 0); + CBox box = BB.copy().expand(!w->isX11OverrideRedirect() ? BORDER_GRAB_AREA : 0); if (w->m_bIsFloating && w->m_bIsMapped && isWorkspaceVisible(w->m_pWorkspace) && !w->isHidden() && !w->m_bPinned && !w->m_sWindowData.noFocus.valueOrDefault() && w != pIgnoreWindow && (!aboveFullscreen || w->m_bCreatedOverFullscreen)) { // OR windows should add focus to parent - if (w->m_bX11ShouldntFocus && w->m_iX11Type != 2) + if (w->m_bX11ShouldntFocus && !w->isX11OverrideRedirect()) continue; if (box.containsPoint(g_pPointerManager->position())) { - if (w->m_bIsX11 && w->m_iX11Type == 2 && !w->m_pXWaylandSurface->wantsFocus()) { + if (w->m_bIsX11 && w->isX11OverrideRedirect() && !w->m_pXWaylandSurface->wantsFocus()) { // Override Redirect return g_pCompositor->m_pLastWindow.lock(); // we kinda trick everything here. // TODO: this is wrong, we should focus the parent, but idk how to get it considering it's nullptr in most cases. @@ -845,8 +879,8 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t proper if (properties & FLOATING_ONLY) return floating(false); - const int64_t WORKSPACEID = special ? PMONITOR->activeSpecialWorkspaceID() : PMONITOR->activeWorkspaceID(); - const auto PWORKSPACE = getWorkspaceByID(WORKSPACEID); + const WORKSPACEID WSPID = special ? PMONITOR->activeSpecialWorkspaceID() : PMONITOR->activeWorkspaceID(); + const auto PWORKSPACE = getWorkspaceByID(WSPID); if (PWORKSPACE->m_bHasFullscreenWindow) return getFullscreenWindowOnWorkspace(PWORKSPACE->m_iID); @@ -856,23 +890,23 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t proper return found; // for windows, we need to check their extensions too, first. - for (auto& w : m_vWindows) { + for (auto const& w : m_vWindows) { if (special != w->onSpecialWorkspace()) continue; - if (!w->m_bIsX11 && !w->m_bIsFloating && w->m_bIsMapped && w->workspaceID() == WORKSPACEID && !w->isHidden() && !w->m_bX11ShouldntFocus && + if (!w->m_bIsX11 && !w->m_bIsFloating && w->m_bIsMapped && w->workspaceID() == WSPID && !w->isHidden() && !w->m_bX11ShouldntFocus && !w->m_sWindowData.noFocus.valueOrDefault() && w != pIgnoreWindow) { if (w->hasPopupAt(pos)) return w; } } - for (auto& w : m_vWindows) { + for (auto const& w : m_vWindows) { if (special != w->onSpecialWorkspace()) continue; CBox box = (properties & USE_PROP_TILED) ? w->getWindowBoxUnified(properties) : CBox{w->m_vPosition, w->m_vSize}; - if (!w->m_bIsFloating && w->m_bIsMapped && box.containsPoint(pos) && w->workspaceID() == WORKSPACEID && !w->isHidden() && !w->m_bX11ShouldntFocus && + if (!w->m_bIsFloating && w->m_bIsMapped && box.containsPoint(pos) && w->workspaceID() == WSPID && !w->isHidden() && !w->m_bX11ShouldntFocus && !w->m_sWindowData.noFocus.valueOrDefault() && w != pIgnoreWindow) return w; } @@ -949,7 +983,7 @@ Vector2D CCompositor::vectorToSurfaceLocal(const Vector2D& vec, PHLWINDOW pWindo } CMonitor* CCompositor::getMonitorFromOutput(SP out) { - for (auto& m : m_vMonitors) { + for (auto const& m : m_vMonitors) { if (m->output == out) { return m.get(); } @@ -959,7 +993,7 @@ CMonitor* CCompositor::getMonitorFromOutput(SP out) { } CMonitor* CCompositor::getRealMonitorFromOutput(SP out) { - for (auto& m : m_vRealMonitors) { + for (auto const& m : m_vRealMonitors) { if (m->output == out) { return m.get(); } @@ -983,7 +1017,7 @@ void CCompositor::focusWindow(PHLWINDOW pWindow, SP pSurface return; } - if (pWindow && pWindow->m_bIsX11 && pWindow->m_iX11Type == 2 && !pWindow->m_pXWaylandSurface->wantsFocus()) + if (pWindow && pWindow->m_bIsX11 && pWindow->isX11OverrideRedirect() && !pWindow->m_pXWaylandSurface->wantsFocus()) return; g_pLayoutManager->getCurrentLayout()->bringWindowToTop(pWindow); @@ -1057,7 +1091,7 @@ void CCompositor::focusWindow(PHLWINDOW pWindow, SP pSurface updateWindowAnimatedDecorationValues(PLASTWINDOW); - if (!pWindow->m_bIsX11 || pWindow->m_iX11Type == 1) + if (!pWindow->m_bIsX11 || !pWindow->isX11OverrideRedirect()) g_pXWaylandManager->activateWindow(PLASTWINDOW, false); } @@ -1103,7 +1137,7 @@ void CCompositor::focusSurface(SP pSurface, PHLWINDOW pWindo if (g_pSeatManager->state.keyboardFocus == pSurface || (pWindowOwner && g_pSeatManager->state.keyboardFocus == pWindowOwner->m_pWLSurface->resource())) return; // Don't focus when already focused on this. - if (g_pSessionLockManager->isSessionLocked() && !g_pSessionLockManager->isSurfaceSessionLock(pSurface)) + if (g_pSessionLockManager->isSessionLocked() && pSurface && !g_pSessionLockManager->isSurfaceSessionLock(pSurface)) return; if (g_pSeatManager->seatGrab && !g_pSeatManager->seatGrab->accepts(pSurface)) { @@ -1150,8 +1184,8 @@ void CCompositor::focusSurface(SP pSurface, PHLWINDOW pWindo } SP CCompositor::vectorToLayerPopupSurface(const Vector2D& pos, CMonitor* monitor, Vector2D* sCoords, PHLLS* ppLayerSurfaceFound) { - for (auto& lsl : monitor->m_aLayerSurfaceLayers | std::views::reverse) { - for (auto& ls : lsl | std::views::reverse) { + for (auto const& lsl : monitor->m_aLayerSurfaceLayers | std::views::reverse) { + for (auto const& ls : lsl | std::views::reverse) { if (ls->fadingOut || !ls->layerSurface || (ls->layerSurface && !ls->layerSurface->mapped) || ls->alpha.value() == 0.f) continue; @@ -1169,7 +1203,7 @@ SP CCompositor::vectorToLayerPopupSurface(const Vector2D& po } SP CCompositor::vectorToLayerSurface(const Vector2D& pos, std::vector* layerSurfaces, Vector2D* sCoords, PHLLS* ppLayerSurfaceFound) { - for (auto& ls : *layerSurfaces | std::views::reverse) { + for (auto const& ls : *layerSurfaces | std::views::reverse) { if (ls->fadingOut || !ls->layerSurface || (ls->layerSurface && !ls->layerSurface->surface->mapped) || ls->alpha.value() == 0.f) continue; @@ -1198,7 +1232,7 @@ PHLWINDOW CCompositor::getWindowFromSurface(SP pSurface) { } PHLWINDOW CCompositor::getWindowFromHandle(uint32_t handle) { - for (auto& w : m_vWindows) { + for (auto const& w : m_vWindows) { if ((uint32_t)(((uint64_t)w.get()) & 0xFFFFFFFF) == handle) { return w; } @@ -1207,8 +1241,8 @@ PHLWINDOW CCompositor::getWindowFromHandle(uint32_t handle) { return nullptr; } -PHLWINDOW CCompositor::getFullscreenWindowOnWorkspace(const int& ID) { - for (auto& w : m_vWindows) { +PHLWINDOW CCompositor::getFullscreenWindowOnWorkspace(const WORKSPACEID& ID) { + for (auto const& w : m_vWindows) { if (w->workspaceID() == ID && w->isFullscreen()) return w; } @@ -1231,8 +1265,8 @@ bool CCompositor::isWorkspaceVisibleNotCovered(PHLWORKSPACE w) { return PMONITOR->activeWorkspace->m_iID == w->m_iID; } -PHLWORKSPACE CCompositor::getWorkspaceByID(const int& id) { - for (auto& w : m_vWorkspaces) { +PHLWORKSPACE CCompositor::getWorkspaceByID(const WORKSPACEID& id) { + for (auto const& w : m_vWorkspaces) { if (w->m_iID == id && !w->inert()) return w; } @@ -1255,9 +1289,9 @@ void CCompositor::sanityCheckWorkspaces() { } } -int CCompositor::getWindowsOnWorkspace(const int& id, std::optional onlyTiled, std::optional onlyVisible) { +int CCompositor::getWindowsOnWorkspace(const WORKSPACEID& id, std::optional onlyTiled, std::optional onlyVisible) { int no = 0; - for (auto& w : m_vWindows) { + for (auto const& w : m_vWindows) { if (w->workspaceID() != id || !w->m_bIsMapped) continue; if (onlyTiled.has_value() && w->m_bIsFloating == onlyTiled.value()) @@ -1270,9 +1304,9 @@ int CCompositor::getWindowsOnWorkspace(const int& id, std::optional onlyTi return no; } -int CCompositor::getGroupsOnWorkspace(const int& id, std::optional onlyTiled, std::optional onlyVisible) { +int CCompositor::getGroupsOnWorkspace(const WORKSPACEID& id, std::optional onlyTiled, std::optional onlyVisible) { int no = 0; - for (auto& w : m_vWindows) { + for (auto const& w : m_vWindows) { if (w->workspaceID() != id || !w->m_bIsMapped) continue; if (!w->m_sGroupData.head) @@ -1287,7 +1321,7 @@ int CCompositor::getGroupsOnWorkspace(const int& id, std::optional onlyTil } PHLWINDOW CCompositor::getUrgentWindow() { - for (auto& w : m_vWindows) { + for (auto const& w : m_vWindows) { if (w->m_bIsMapped && w->m_bIsUrgent) return w; } @@ -1295,8 +1329,8 @@ PHLWINDOW CCompositor::getUrgentWindow() { return nullptr; } -bool CCompositor::hasUrgentWindowOnWorkspace(const int& id) { - for (auto& w : m_vWindows) { +bool CCompositor::hasUrgentWindowOnWorkspace(const WORKSPACEID& id) { + for (auto const& w : m_vWindows) { if (w->workspaceID() == id && w->m_bIsMapped && w->m_bIsUrgent) return true; } @@ -1304,8 +1338,8 @@ bool CCompositor::hasUrgentWindowOnWorkspace(const int& id) { return false; } -PHLWINDOW CCompositor::getFirstWindowOnWorkspace(const int& id) { - for (auto& w : m_vWindows) { +PHLWINDOW CCompositor::getFirstWindowOnWorkspace(const WORKSPACEID& id) { + for (auto const& w : m_vWindows) { if (w->workspaceID() == id && w->m_bIsMapped && !w->isHidden()) return w; } @@ -1313,7 +1347,7 @@ PHLWINDOW CCompositor::getFirstWindowOnWorkspace(const int& id) { return nullptr; } -PHLWINDOW CCompositor::getTopLeftWindowOnWorkspace(const int& id) { +PHLWINDOW CCompositor::getTopLeftWindowOnWorkspace(const WORKSPACEID& id) { const auto PWORKSPACE = getWorkspaceByID(id); if (!PWORKSPACE) @@ -1321,7 +1355,7 @@ PHLWINDOW CCompositor::getTopLeftWindowOnWorkspace(const int& id) { const auto PMONITOR = getMonitorFromID(PWORKSPACE->m_iMonitorID); - for (auto& w : m_vWindows) { + for (auto const& w : m_vWindows) { if (w->workspaceID() != id || !w->m_bIsMapped || w->isHidden()) continue; @@ -1349,6 +1383,9 @@ void CCompositor::changeWindowZOrder(PHLWINDOW pWindow, bool top) { if (!validMapped(pWindow)) return; + if (pWindow == (top ? m_vWindows.back() : m_vWindows.front())) + return; + auto moveToZ = [&](PHLWINDOW pw, bool top) -> void { if (top) { for (auto it = m_vWindows.begin(); it != m_vWindows.end(); ++it) { @@ -1386,7 +1423,7 @@ void CCompositor::changeWindowZOrder(PHLWINDOW pWindow, bool top) { else toMove.emplace_front(pw); - for (auto& w : m_vWindows) { + for (auto const& w : m_vWindows) { if (w->m_bIsMapped && !w->isHidden() && w->m_bIsX11 && w->X11TransientFor() == pw && w != pw && std::find(toMove.begin(), toMove.end(), w) == toMove.end()) { x11Stack(w, top, x11Stack); } @@ -1401,12 +1438,12 @@ void CCompositor::changeWindowZOrder(PHLWINDOW pWindow, bool top) { } } -void CCompositor::cleanupFadingOut(const int& monid) { - for (auto& ww : m_vWindowsFadingOut) { +void CCompositor::cleanupFadingOut(const MONITORID& monid) { + for (auto const& ww : m_vWindowsFadingOut) { auto w = ww.lock(); - if (w->m_iMonitorID != (long unsigned int)monid) + if (w->m_iMonitorID != monid) continue; if (!w->m_bFadingOut || w->m_fAlpha.value() == 0.f) { @@ -1425,12 +1462,16 @@ void CCompositor::cleanupFadingOut(const int& monid) { } } - for (auto& lsr : m_vSurfacesFadingOut) { + bool layersDirty = false; + + for (auto const& lsr : m_vSurfacesFadingOut) { auto ls = lsr.lock(); - if (!ls) + if (!ls) { + layersDirty = true; continue; + } if (ls->monitorID != monid) continue; @@ -1440,10 +1481,10 @@ void CCompositor::cleanupFadingOut(const int& monid) { g_pHyprOpenGL->markBlurDirtyForMonitor(getMonitorFromID(monid)); if (ls->fadingOut && ls->readyToDelete && ls->isFadedOut()) { - for (auto& m : m_vMonitors) { + for (auto const& m : m_vMonitors) { for (auto& lsl : m->m_aLayerSurfaceLayers) { if (!lsl.empty() && std::find_if(lsl.begin(), lsl.end(), [&](auto& other) { return other == ls; }) != lsl.end()) { - std::erase_if(lsl, [&](auto& other) { return other == ls; }); + std::erase_if(lsl, [&](auto& other) { return other == ls || !other; }); } } } @@ -1459,6 +1500,9 @@ void CCompositor::cleanupFadingOut(const int& monid) { return; } } + + if (layersDirty) + std::erase_if(m_vSurfacesFadingOut, [](const auto& el) { return el.expired(); }); } void CCompositor::addToFadingOutSafe(PHLLS pLS) { @@ -1509,7 +1553,7 @@ PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, char dir) { if (!pWindow->m_bIsFloating) { // for tiled windows, we calc edges - for (auto& w : m_vWindows) { + for (auto const& w : m_vWindows) { if (w == pWindow || !w->m_bIsMapped || w->isHidden() || (!w->isFullscreen() && w->m_bIsFloating) || !isWorkspaceVisible(w->m_pWorkspace)) continue; @@ -1601,7 +1645,7 @@ PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, char dir) { float bestAngleAbs = 2.0 * M_PI; constexpr float THRESHOLD = 0.3 * M_PI; - for (auto& w : m_vWindows) { + for (auto const& w : m_vWindows) { if (w == pWindow || !w->m_bIsMapped || w->isHidden() || (!w->isFullscreen() && !w->m_bIsFloating) || !isWorkspaceVisible(w->m_pWorkspace)) continue; @@ -1639,7 +1683,7 @@ PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, char dir) { PHLWINDOW CCompositor::getNextWindowOnWorkspace(PHLWINDOW pWindow, bool focusableOnly, std::optional floating) { bool gotToWindow = false; - for (auto& w : m_vWindows) { + for (auto const& w : m_vWindows) { if (w != pWindow && !gotToWindow) continue; @@ -1655,7 +1699,7 @@ PHLWINDOW CCompositor::getNextWindowOnWorkspace(PHLWINDOW pWindow, bool focusabl return w; } - for (auto& w : m_vWindows) { + for (auto const& w : m_vWindows) { if (floating.has_value() && w->m_bIsFloating != floating.value()) continue; @@ -1668,7 +1712,7 @@ PHLWINDOW CCompositor::getNextWindowOnWorkspace(PHLWINDOW pWindow, bool focusabl PHLWINDOW CCompositor::getPrevWindowOnWorkspace(PHLWINDOW pWindow, bool focusableOnly, std::optional floating) { bool gotToWindow = false; - for (auto& w : m_vWindows | std::views::reverse) { + for (auto const& w : m_vWindows | std::views::reverse) { if (w != pWindow && !gotToWindow) continue; @@ -1684,7 +1728,7 @@ PHLWINDOW CCompositor::getPrevWindowOnWorkspace(PHLWINDOW pWindow, bool focusabl return w; } - for (auto& w : m_vWindows | std::views::reverse) { + for (auto const& w : m_vWindows | std::views::reverse) { if (floating.has_value() && w->m_bIsFloating != floating.value()) continue; @@ -1695,9 +1739,9 @@ PHLWINDOW CCompositor::getPrevWindowOnWorkspace(PHLWINDOW pWindow, bool focusabl return nullptr; } -int CCompositor::getNextAvailableNamedWorkspace() { - int lowest = -1337 + 1; - for (auto& w : m_vWorkspaces) { +WORKSPACEID CCompositor::getNextAvailableNamedWorkspace() { + WORKSPACEID lowest = -1337 + 1; + for (auto const& w : m_vWorkspaces) { if (w->m_iID < -1 && w->m_iID < lowest) lowest = w->m_iID; } @@ -1706,7 +1750,7 @@ int CCompositor::getNextAvailableNamedWorkspace() { } PHLWORKSPACE CCompositor::getWorkspaceByName(const std::string& name) { - for (auto& w : m_vWorkspaces) { + for (auto const& w : m_vWorkspaces) { if (w->m_szName == name && !w->inert()) return w; } @@ -1727,7 +1771,7 @@ PHLWORKSPACE CCompositor::getWorkspaceByString(const std::string& str) { } bool CCompositor::isPointOnAnyMonitor(const Vector2D& point) { - for (auto& m : m_vMonitors) { + for (auto const& m : m_vMonitors) { if (VECINRECT(point, m->vecPosition.x, m->vecPosition.y, m->vecSize.x + m->vecPosition.x, m->vecSize.y + m->vecPosition.y)) return true; } @@ -1758,7 +1802,7 @@ CMonitor* CCompositor::getMonitorInDirection(CMonitor* pSourceMonitor, const cha auto longestIntersect = -1; CMonitor* longestIntersectMonitor = nullptr; - for (auto& m : m_vMonitors) { + for (auto const& m : m_vMonitors) { if (m == m_pLastMonitor) continue; @@ -1813,7 +1857,7 @@ CMonitor* CCompositor::getMonitorInDirection(CMonitor* pSourceMonitor, const cha } void CCompositor::updateAllWindowsAnimatedDecorationValues() { - for (auto& w : m_vWindows) { + for (auto const& w : m_vWindows) { if (!w->m_bIsMapped) continue; @@ -1822,7 +1866,7 @@ void CCompositor::updateAllWindowsAnimatedDecorationValues() { } void CCompositor::updateWorkspaceWindows(const int64_t& id) { - for (auto& w : m_vWindows) { + for (auto const& w : m_vWindows) { if (!w->m_bIsMapped || w->workspaceID() != id) continue; @@ -1867,6 +1911,8 @@ void CCompositor::updateWindowAnimatedDecorationValues(PHLWINDOW pWindow) { pWindow->m_fBorderFadeAnimationProgress = 1.f; }; + const bool IS_SHADOWED_BY_MODAL = pWindow->m_pXDGSurface && pWindow->m_pXDGSurface->toplevel && pWindow->m_pXDGSurface->toplevel->anyChildModal(); + // border const auto RENDERDATA = g_pLayoutManager->getCurrentLayout()->requestRenderHints(pWindow); if (RENDERDATA.isBorderGradient) @@ -1900,14 +1946,19 @@ void CCompositor::updateWindowAnimatedDecorationValues(PHLWINDOW pWindow) { } // dim - if (pWindow == m_pLastWindow.lock() || pWindow->m_sWindowData.noDim.valueOrDefault() || !*PDIMENABLED) { - pWindow->m_fDimPercent = 0; - } else { - pWindow->m_fDimPercent = *PDIMSTRENGTH; - } + float goalDim = 1.F; + if (pWindow == m_pLastWindow.lock() || pWindow->m_sWindowData.noDim.valueOrDefault() || !*PDIMENABLED) + goalDim = 0; + else + goalDim = *PDIMSTRENGTH; + + if (IS_SHADOWED_BY_MODAL) + goalDim += (1.F - goalDim) / 2.F; + + pWindow->m_fDimPercent = goalDim; // shadow - if (pWindow->m_iX11Type != 2 && !pWindow->m_bX11DoesntWantBorders) { + if (!pWindow->isX11OverrideRedirect() && !pWindow->m_bX11DoesntWantBorders) { if (pWindow == m_pLastWindow) { pWindow->m_cRealShadowColor = CColor(*PSHADOWCOL); } else { @@ -1920,18 +1971,18 @@ void CCompositor::updateWindowAnimatedDecorationValues(PHLWINDOW pWindow) { pWindow->updateWindowDecos(); } -int CCompositor::getNextAvailableMonitorID(std::string const& name) { +MONITORID CCompositor::getNextAvailableMonitorID(std::string const& name) { // reuse ID if it's already in the map, and the monitor with that ID is not being used by another monitor if (m_mMonitorIDMap.contains(name) && !std::any_of(m_vRealMonitors.begin(), m_vRealMonitors.end(), [&](auto m) { return m->ID == m_mMonitorIDMap[name]; })) return m_mMonitorIDMap[name]; // otherwise, find minimum available ID that is not in the map - std::unordered_set usedIDs; + std::unordered_set usedIDs; for (auto const& monitor : m_vRealMonitors) { usedIDs.insert(monitor->ID); } - uint64_t nextID = 0; + MONITORID nextID = 0; while (usedIDs.count(nextID) > 0) { nextID++; } @@ -1947,7 +1998,7 @@ void CCompositor::swapActiveWorkspaces(CMonitor* pMonitorA, CMonitor* pMonitorB) PWORKSPACEA->m_iMonitorID = pMonitorB->ID; PWORKSPACEA->moveToMonitor(pMonitorB->ID); - for (auto& w : m_vWindows) { + for (auto const& w : m_vWindows) { if (w->m_pWorkspace == PWORKSPACEA) { if (w->m_bPinned) { w->m_pWorkspace = PWORKSPACEB; @@ -1972,7 +2023,7 @@ void CCompositor::swapActiveWorkspaces(CMonitor* pMonitorA, CMonitor* pMonitorB) PWORKSPACEB->m_iMonitorID = pMonitorA->ID; PWORKSPACEB->moveToMonitor(pMonitorA->ID); - for (auto& w : m_vWindows) { + for (auto const& w : m_vWindows) { if (w->m_pWorkspace == PWORKSPACEB) { if (w->m_bPinned) { w->m_pWorkspace = PWORKSPACEA; @@ -2071,7 +2122,7 @@ CMonitor* CCompositor::getMonitorFromString(const std::string& name) { return m_vMonitors[currentPlace].get(); } else if (isNumber(name)) { // change by ID - int monID = -1; + MONITORID monID = MONITOR_INVALID; try { monID = std::stoi(name); } catch (std::exception& e) { @@ -2080,14 +2131,14 @@ CMonitor* CCompositor::getMonitorFromString(const std::string& name) { return nullptr; } - if (monID > -1 && monID < (int)m_vMonitors.size()) { + if (monID > -1 && monID < (MONITORID)m_vMonitors.size()) { return getMonitorFromID(monID); } else { Debug::log(ERR, "Error in getMonitorFromString: invalid arg 1"); return nullptr; } } else { - for (auto& m : m_vMonitors) { + for (auto const& m : m_vMonitors) { if (!m->output) continue; @@ -2114,18 +2165,18 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, CMonitor* pMon const bool SWITCHINGISACTIVE = POLDMON ? POLDMON->activeWorkspace == pWorkspace : false; // fix old mon - int nextWorkspaceOnMonitorID = -1; + WORKSPACEID nextWorkspaceOnMonitorID = WORKSPACE_INVALID; if (!SWITCHINGISACTIVE) nextWorkspaceOnMonitorID = pWorkspace->m_iID; else { - for (auto& w : m_vWorkspaces) { + for (auto const& w : m_vWorkspaces) { if (w->m_iMonitorID == POLDMON->ID && w->m_iID != pWorkspace->m_iID && !w->m_bIsSpecialWorkspace) { nextWorkspaceOnMonitorID = w->m_iID; break; } } - if (nextWorkspaceOnMonitorID == -1) { + if (nextWorkspaceOnMonitorID == WORKSPACE_INVALID) { nextWorkspaceOnMonitorID = 1; while (getWorkspaceByID(nextWorkspaceOnMonitorID) || [&]() -> bool { @@ -2147,7 +2198,7 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, CMonitor* pMon pWorkspace->m_iMonitorID = pMonitor->ID; pWorkspace->moveToMonitor(pMonitor->ID); - for (auto& w : m_vWindows) { + for (auto const& w : m_vWindows) { if (w->m_pWorkspace == pWorkspace) { if (w->m_bPinned) { w->m_pWorkspace = g_pCompositor->getWorkspaceByID(nextWorkspaceOnMonitorID); @@ -2212,11 +2263,11 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, CMonitor* pMon EMIT_HOOK_EVENT("moveWorkspace", (std::vector{pWorkspace, pMonitor})); } -bool CCompositor::workspaceIDOutOfBounds(const int64_t& id) { - int64_t lowestID = INT64_MAX; - int64_t highestID = INT64_MIN; +bool CCompositor::workspaceIDOutOfBounds(const WORKSPACEID& id) { + WORKSPACEID lowestID = INT64_MAX; + WORKSPACEID highestID = INT64_MIN; - for (auto& w : m_vWorkspaces) { + for (auto const& w : m_vWorkspaces) { if (w->m_bIsSpecialWorkspace) continue; @@ -2234,7 +2285,7 @@ void CCompositor::updateFullscreenFadeOnWorkspace(PHLWORKSPACE pWorkspace) { const auto FULLSCREEN = pWorkspace->m_bHasFullscreenWindow; - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (w->m_pWorkspace == pWorkspace) { if (w->m_bFadingOut || w->m_bPinned || w->isFullscreen()) @@ -2250,7 +2301,7 @@ void CCompositor::updateFullscreenFadeOnWorkspace(PHLWORKSPACE pWorkspace) { const auto PMONITOR = getMonitorFromID(pWorkspace->m_iMonitorID); if (pWorkspace->m_iID == PMONITOR->activeWorkspaceID() || pWorkspace->m_iID == PMONITOR->activeSpecialWorkspaceID()) { - for (auto& ls : PMONITOR->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) { + for (auto const& ls : PMONITOR->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) { if (!ls->fadingOut) ls->alpha = FULLSCREEN && pWorkspace->m_efFullscreenMode == FSMODE_FULLSCREEN ? 0.f : 1.f; } @@ -2282,7 +2333,7 @@ void CCompositor::setWindowFullscreenClient(const PHLWINDOW PWINDOW, const eFull } void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, sFullscreenState state) { - static auto PNODIRECTSCANOUT = CConfigValue("misc:no_direct_scanout"); + static auto PDIRECTSCANOUT = CConfigValue("render:direct_scanout"); if (!validMapped(PWINDOW) || g_pCompositor->m_bUnsafeState) return; @@ -2305,8 +2356,12 @@ void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, sFullscreenS PWINDOW->m_sFullscreenState.client = state.client; g_pXWaylandManager->setWindowFullscreen(PWINDOW, state.client & FSMODE_FULLSCREEN); - if (!CHANGEINTERNAL) + if (!CHANGEINTERNAL) { + PWINDOW->updateDynamicRules(); + updateWindowAnimatedDecorationValues(PWINDOW); + g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->m_iMonitorID); return; + } g_pLayoutManager->getCurrentLayout()->fullscreenRequestForWindow(PWINDOW, CURRENT_EFFECTIVE_MODE, EFFECTIVE_MODE); @@ -2318,12 +2373,11 @@ void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, sFullscreenS EMIT_HOOK_EVENT("fullscreen", PWINDOW); PWINDOW->updateDynamicRules(); - PWINDOW->updateWindowDecos(); updateWindowAnimatedDecorationValues(PWINDOW); g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->m_iMonitorID); // make all windows on the same workspace under the fullscreen window - for (auto& w : m_vWindows) { + for (auto const& w : m_vWindows) { if (w->m_pWorkspace == PWORKSPACE && !w->isFullscreen() && !w->m_bFadingOut && !w->m_bPinned) w->m_bCreatedOverFullscreen = false; } @@ -2342,7 +2396,7 @@ void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, sFullscreenS // send a scanout tranche if we are entering fullscreen, and send a regular one if we aren't. // ignore if DS is disabled. - if (!*PNODIRECTSCANOUT) + if (*PDIRECTSCANOUT) g_pHyprRenderer->setSurfaceScanoutMode(PWINDOW->m_pWLSurface->resource(), EFFECTIVE_MODE != FSMODE_NONE ? PMONITOR->self.lock() : nullptr); g_pConfigManager->ensureVRR(PMONITOR); @@ -2352,7 +2406,7 @@ PHLWINDOW CCompositor::getX11Parent(PHLWINDOW pWindow) { if (!pWindow->m_bIsX11) return nullptr; - for (auto& w : m_vWindows) { + for (auto const& w : m_vWindows) { if (!w->m_bIsX11) continue; @@ -2363,8 +2417,8 @@ PHLWINDOW CCompositor::getX11Parent(PHLWINDOW pWindow) { return nullptr; } -void CCompositor::updateWorkspaceWindowDecos(const int& id) { - for (auto& w : m_vWindows) { +void CCompositor::updateWorkspaceWindowDecos(const WORKSPACEID& id) { + for (auto const& w : m_vWindows) { if (w->workspaceID() != id) continue; @@ -2372,11 +2426,11 @@ void CCompositor::updateWorkspaceWindowDecos(const int& id) { } } -void CCompositor::updateWorkspaceWindowData(const int& id) { +void CCompositor::updateWorkspaceWindowData(const WORKSPACEID& id) { const auto PWORKSPACE = getWorkspaceByID(id); const auto WORKSPACERULE = PWORKSPACE ? g_pConfigManager->getWorkspaceRuleFor(PWORKSPACE) : SWorkspaceRule{}; - for (auto& w : m_vWindows) { + for (auto const& w : m_vWindows) { if (w->workspaceID() != id) continue; @@ -2397,13 +2451,31 @@ void CCompositor::scheduleFrameForMonitor(CMonitor* pMonitor, IOutput::scheduleF pMonitor->output->scheduleFrame(reason); } -PHLWINDOW CCompositor::getWindowByRegex(const std::string& regexp) { +PHLWINDOW CCompositor::getWindowByRegex(const std::string& regexp_) { + auto regexp = trim(regexp_); + if (regexp.starts_with("active")) return m_pLastWindow.lock(); + else if (regexp.starts_with("floating") || regexp.starts_with("tiled")) { + // first floating on the current ws + if (!valid(m_pLastWindow)) + return nullptr; + + const bool FLOAT = regexp.starts_with("floating"); + + for (auto const& w : m_vWindows) { + if (!w->m_bIsMapped || w->m_bIsFloating != FLOAT || w->m_pWorkspace != m_pLastWindow->m_pWorkspace || w->isHidden()) + continue; + + return w; + } + + return nullptr; + } eFocusWindowMode mode = MODE_CLASS_REGEX; - std::regex regexCheck(regexp); + std::regex regexCheck(regexp_); std::string matchCheck; if (regexp.starts_with("class:")) { regexCheck = std::regex(regexp.substr(6)); @@ -2422,24 +2494,9 @@ PHLWINDOW CCompositor::getWindowByRegex(const std::string& regexp) { } else if (regexp.starts_with("pid:")) { mode = MODE_PID; matchCheck = regexp.substr(4); - } else if (regexp.starts_with("floating") || regexp.starts_with("tiled")) { - // first floating on the current ws - if (!valid(m_pLastWindow)) - return nullptr; - - const bool FLOAT = regexp.starts_with("floating"); - - for (auto& w : m_vWindows) { - if (!w->m_bIsMapped || w->m_bIsFloating != FLOAT || w->m_pWorkspace != m_pLastWindow->m_pWorkspace || w->isHidden()) - continue; - - return w; - } - - return nullptr; } - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (!w->m_bIsMapped || (w->isHidden() && !g_pLayoutManager->getCurrentLayout()->isWindowReachable(w))) continue; @@ -2519,7 +2576,7 @@ void CCompositor::closeWindow(PHLWINDOW pWindow) { PHLLS CCompositor::getLayerSurfaceFromSurface(SP pSurface) { std::pair, bool> result = {pSurface, false}; - for (auto& ls : m_vLayers) { + for (auto const& ls : m_vLayers) { if (ls->layerSurface && ls->layerSurface->surface == pSurface) return ls; @@ -2592,15 +2649,15 @@ Vector2D CCompositor::parseWindowVectorArgsRelative(const std::string& args, con return Vector2D(X, Y); } -void CCompositor::forceReportSizesToWindowsOnWorkspace(const int& wid) { - for (auto& w : m_vWindows) { +void CCompositor::forceReportSizesToWindowsOnWorkspace(const WORKSPACEID& wid) { + for (auto const& w : m_vWindows) { if (w->workspaceID() == wid && w->m_bIsMapped && !w->isHidden()) { g_pXWaylandManager->setWindowSize(w, w->m_vRealSize.value(), true); } } } -PHLWORKSPACE CCompositor::createNewWorkspace(const int& id, const int& monid, const std::string& name, bool isEmtpy) { +PHLWORKSPACE CCompositor::createNewWorkspace(const WORKSPACEID& id, const MONITORID& monid, const std::string& name, bool isEmpty) { const auto NAME = name == "" ? std::to_string(id) : name; auto monID = monid; @@ -2611,14 +2668,14 @@ PHLWORKSPACE CCompositor::createNewWorkspace(const int& id, const int& monid, co const bool SPECIAL = id >= SPECIAL_WORKSPACE_START && id <= -2; - const auto PWORKSPACE = m_vWorkspaces.emplace_back(CWorkspace::create(id, monID, NAME, SPECIAL, isEmtpy)); + const auto PWORKSPACE = m_vWorkspaces.emplace_back(CWorkspace::create(id, monID, NAME, SPECIAL, isEmpty)); PWORKSPACE->m_fAlpha.setValueAndWarp(0); return PWORKSPACE; } -void CCompositor::renameWorkspace(const int& id, const std::string& name) { +void CCompositor::renameWorkspace(const WORKSPACEID& id, const std::string& name) { const auto PWORKSPACE = getWorkspaceByID(id); if (!PWORKSPACE) @@ -2649,13 +2706,13 @@ void CCompositor::setActiveMonitor(CMonitor* pMonitor) { m_pLastMonitor = pMonitor->self; } -bool CCompositor::isWorkspaceSpecial(const int& id) { +bool CCompositor::isWorkspaceSpecial(const WORKSPACEID& id) { return id >= SPECIAL_WORKSPACE_START && id <= -2; } -int CCompositor::getNewSpecialID() { - int highest = SPECIAL_WORKSPACE_START; - for (auto& ws : m_vWorkspaces) { +WORKSPACEID CCompositor::getNewSpecialID() { + WORKSPACEID highest = SPECIAL_WORKSPACE_START; + for (auto const& ws : m_vWorkspaces) { if (ws->m_bIsSpecialWorkspace && ws->m_iID > highest) { highest = ws->m_iID; } @@ -2665,7 +2722,17 @@ int CCompositor::getNewSpecialID() { } void CCompositor::performUserChecks() { - ; // intentional + static auto PNOCHECKXDG = CConfigValue("misc:disable_xdg_env_checks"); + + if (!*PNOCHECKXDG) { + const auto CURRENT_DESKTOP_ENV = getenv("XDG_CURRENT_DESKTOP"); + if (!CURRENT_DESKTOP_ENV || std::string{CURRENT_DESKTOP_ENV} != "Hyprland") { + g_pHyprNotificationOverlay->addNotification( + std::format("Your XDG_CURRENT_DESKTOP environment seems to be managed externally, and the current value is {}.\nThis might cause issues unless it's intentional.", + CURRENT_DESKTOP_ENV ? CURRENT_DESKTOP_ENV : "unset"), + CColor{}, 15000, ICON_WARNING); + } + } } void CCompositor::moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWorkspace) { @@ -2716,10 +2783,11 @@ void CCompositor::moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWor g_pCompositor->updateWorkspaceWindows(pWorkspace->m_iID); g_pCompositor->updateWorkspaceWindows(pWindow->workspaceID()); + g_pCompositor->updateSuspendedStates(); } PHLWINDOW CCompositor::getForceFocus() { - for (auto& w : m_vWindows) { + for (auto const& w : m_vWindows) { if (!w->m_bIsMapped || w->isHidden() || !isWorkspaceVisible(w->m_pWorkspace)) continue; @@ -2738,7 +2806,7 @@ void CCompositor::arrangeMonitors() { std::vector toArrange; std::vector arranged; - for (auto& m : m_vMonitors) + for (auto const& m : m_vMonitors) toArrange.push_back(m.get()); Debug::log(LOG, "arrangeMonitors: {} to arrange", toArrange.size()); @@ -2770,7 +2838,7 @@ void CCompositor::arrangeMonitors() { int maxYOffsetDown = 0; // Finds the max and min values of explicitely placed monitors. - for (auto& m : arranged) { + for (auto const& m : arranged) { if (m->vecPosition.x + m->vecSize.x > maxXOffsetRight) maxXOffsetRight = m->vecPosition.x + m->vecSize.x; if (m->vecPosition.x < maxXOffsetLeft) @@ -2782,7 +2850,7 @@ void CCompositor::arrangeMonitors() { } // Iterates through all non-explicitly placed monitors. - for (auto& m : toArrange) { + for (auto const& m : toArrange) { // Moves the monitor to their appropriate position on the x/y axis and // increments/decrements the corresponding max offset. Vector2D newPosition = {0, 0}; @@ -2813,7 +2881,7 @@ void CCompositor::arrangeMonitors() { // reset maxXOffsetRight (reuse) // and set xwayland positions aka auto for all maxXOffsetRight = 0; - for (auto& m : m_vMonitors) { + for (auto const& m : m_vMonitors) { Debug::log(LOG, "arrangeMonitors: {} xwayland [{}, {}]", m->szName, maxXOffsetRight, 0); m->vecXWaylandPosition = {maxXOffsetRight, 0}; maxXOffsetRight += (*PXWLFORCESCALEZERO ? m->vecTransformedSize.x : m->vecSize.x); @@ -2823,6 +2891,8 @@ void CCompositor::arrangeMonitors() { else m->xwaylandScale = 1.f; } + + PROTO::xdgOutput->updateAllOutputs(); } void CCompositor::enterUnsafeState() { @@ -2848,7 +2918,7 @@ void CCompositor::leaveUnsafeState() { m_bUnsafeState = false; CMonitor* pNewMonitor = nullptr; - for (auto& pMonitor : m_vMonitors) { + for (auto const& pMonitor : m_vMonitors) { if (pMonitor->output != m_pUnsafeOutput->output) { pNewMonitor = pMonitor.get(); break; @@ -2860,7 +2930,7 @@ void CCompositor::leaveUnsafeState() { if (m_pUnsafeOutput->m_bEnabled) m_pUnsafeOutput->onDisconnect(); - for (auto& m : m_vMonitors) { + for (auto const& m : m_vMonitors) { scheduleFrameForMonitor(m.get()); } } @@ -2892,7 +2962,7 @@ void CCompositor::setPreferredTransformForSurface(SP pSurfac } void CCompositor::updateSuspendedStates() { - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (!w->m_bIsMapped) continue; @@ -2901,7 +2971,7 @@ void CCompositor::updateSuspendedStates() { } PHLWINDOW CCompositor::windowForCPointer(CWindow* pWindow) { - for (auto& w : m_vWindows) { + for (auto const& w : m_vWindows) { if (w.get() != pWindow) continue; @@ -2911,13 +2981,12 @@ PHLWINDOW CCompositor::windowForCPointer(CWindow* pWindow) { return {}; } -static void checkDefaultCursorWarp(SP PNEWMONITOR, std::string monitorName) { +static void checkDefaultCursorWarp(SP monitor) { static auto PCURSORMONITOR = CConfigValue("cursor:default_monitor"); - static auto firstMonitorAdded = std::chrono::system_clock::now(); static bool cursorDefaultDone = false; static bool firstLaunch = true; - const auto POS = PNEWMONITOR->middle(); + const auto POS = monitor->middle(); // by default, cursor should be set to first monitor detected // this is needed as a default if the monitor given in config above doesn't exist @@ -2925,20 +2994,20 @@ static void checkDefaultCursorWarp(SP PNEWMONITOR, std::string monitor firstLaunch = false; g_pCompositor->warpCursorTo(POS, true); g_pInputManager->refocus(); - } - - if (cursorDefaultDone || *PCURSORMONITOR == STRVAL_EMPTY) - return; - - // after 10s, don't set cursor to default monitor - auto timePassedSec = std::chrono::duration_cast(std::chrono::system_clock::now() - firstMonitorAdded); - if (timePassedSec.count() > 10) { - cursorDefaultDone = true; return; } - if (*PCURSORMONITOR == monitorName) { - cursorDefaultDone = true; + if (!cursorDefaultDone && *PCURSORMONITOR != STRVAL_EMPTY) { + if (*PCURSORMONITOR == monitor->szName) { + cursorDefaultDone = true; + g_pCompositor->warpCursorTo(POS, true); + g_pInputManager->refocus(); + return; + } + } + + // modechange happend check if cursor is on that monitor and warp it to middle to not place it out of bounds if resolution changed. + if (g_pCompositor->getMonitorFromCursor() == monitor.get()) { g_pCompositor->warpCursorTo(POS, true); g_pInputManager->refocus(); } @@ -2946,7 +3015,7 @@ static void checkDefaultCursorWarp(SP PNEWMONITOR, std::string monitor void CCompositor::onNewMonitor(SP output) { // add it to real - auto PNEWMONITOR = g_pCompositor->m_vRealMonitors.emplace_back(makeShared()); + auto PNEWMONITOR = g_pCompositor->m_vRealMonitors.emplace_back(makeShared(output)); if (std::string("HEADLESS-1") == output->name) { g_pCompositor->m_pUnsafeOutput = PNEWMONITOR.get(); output->name = "FALLBACK"; // we are allowed to do this :) @@ -2955,10 +3024,9 @@ void CCompositor::onNewMonitor(SP output) { Debug::log(LOG, "New output with name {}", output->name); PNEWMONITOR->szName = output->name; - PNEWMONITOR->output = output; PNEWMONITOR->self = PNEWMONITOR; const bool FALLBACK = g_pCompositor->m_pUnsafeOutput ? output == g_pCompositor->m_pUnsafeOutput->output : false; - PNEWMONITOR->ID = FALLBACK ? -1 : g_pCompositor->getNextAvailableMonitorID(output->name); + PNEWMONITOR->ID = FALLBACK ? MONITOR_INVALID : g_pCompositor->getNextAvailableMonitorID(output->name); PNEWMONITOR->isUnsafeFallback = FALLBACK; EMIT_HOOK_EVENT("newMonitor", PNEWMONITOR); @@ -2979,11 +3047,11 @@ void CCompositor::onNewMonitor(SP output) { g_pConfigManager->m_bWantsMonitorReload = true; g_pCompositor->scheduleFrameForMonitor(PNEWMONITOR.get(), IOutput::AQ_SCHEDULE_NEW_MONITOR); - checkDefaultCursorWarp(PNEWMONITOR, output->name); + checkDefaultCursorWarp(PNEWMONITOR); - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (w->m_iMonitorID == PNEWMONITOR->ID) { - w->m_iLastSurfaceMonitorID = -1; + w->m_iLastSurfaceMonitorID = MONITOR_INVALID; w->updateSurfaceScaleTransformDetails(); } } diff --git a/src/Compositor.hpp b/src/Compositor.hpp index 295935c4..a57450f1 100644 --- a/src/Compositor.hpp +++ b/src/Compositor.hpp @@ -46,55 +46,58 @@ class CCompositor { CCompositor(); ~CCompositor(); - wl_display* m_sWLDisplay; - wl_event_loop* m_sWLEventLoop; - int m_iDRMFD = -1; - bool m_bInitialized = false; - SP m_pAqBackend; + wl_display* m_sWLDisplay; + wl_event_loop* m_sWLEventLoop; + int m_iDRMFD = -1; + bool m_bInitialized = false; + SP m_pAqBackend; - std::string m_szHyprTempDataRoot = ""; + std::string m_szHyprTempDataRoot = ""; - std::string m_szWLDisplaySocket = ""; - std::string m_szInstanceSignature = ""; - std::string m_szInstancePath = ""; - std::string m_szCurrentSplash = "error"; + std::string m_szWLDisplaySocket = ""; + std::string m_szInstanceSignature = ""; + std::string m_szInstancePath = ""; + std::string m_szCurrentSplash = "error"; - std::vector> m_vMonitors; - std::vector> m_vRealMonitors; // for all monitors, even those turned off - std::vector m_vWindows; - std::vector m_vLayers; - std::vector m_vWorkspaces; - std::vector m_vWindowsFadingOut; - std::vector m_vSurfacesFadingOut; + std::vector> m_vMonitors; + std::vector> m_vRealMonitors; // for all monitors, even those turned off + std::vector m_vWindows; + std::vector m_vLayers; + std::vector m_vWorkspaces; + std::vector m_vWindowsFadingOut; + std::vector m_vSurfacesFadingOut; - std::unordered_map m_mMonitorIDMap; + std::unordered_map m_mMonitorIDMap; - void initServer(std::string socketName, int socketFd); - void startCompositor(); - void stopCompositor(); - void cleanup(); - void createLockFile(); - void removeLockFile(); - void bumpNofile(); - void restoreNofile(); + void initServer(std::string socketName, int socketFd); + void startCompositor(); + void stopCompositor(); + void cleanup(); + void createLockFile(); + void removeLockFile(); + void bumpNofile(); + void restoreNofile(); - WP m_pLastFocus; - PHLWINDOWREF m_pLastWindow; - WP m_pLastMonitor; + WP m_pLastFocus; + PHLWINDOWREF m_pLastWindow; + WP m_pLastMonitor; - std::vector m_vWindowFocusHistory; // first element is the most recently focused. + std::vector m_vWindowFocusHistory; // first element is the most recently focused. - bool m_bReadyToProcess = false; - bool m_bSessionActive = true; - bool m_bDPMSStateON = true; - bool m_bUnsafeState = false; // unsafe state is when there is no monitors. - bool m_bNextIsUnsafe = false; - CMonitor* m_pUnsafeOutput = nullptr; // fallback output for the unsafe state - bool m_bIsShuttingDown = false; + bool m_bReadyToProcess = false; + bool m_bSessionActive = true; + bool m_bDPMSStateON = true; + bool m_bUnsafeState = false; // unsafe state is when there is no monitors. + bool m_bNextIsUnsafe = false; + CMonitor* m_pUnsafeOutput = nullptr; // fallback output for the unsafe state + bool m_bIsShuttingDown = false; + bool m_bFinalRequests = false; + bool m_bDesktopEnvSet = false; + bool m_bEnableXwayland = true; // ------------------------------------------------- // - CMonitor* getMonitorFromID(const int&); + CMonitor* getMonitorFromID(const MONITORID&); CMonitor* getMonitorFromName(const std::string&); CMonitor* getMonitorFromDesc(const std::string&); CMonitor* getMonitorFromCursor(); @@ -114,38 +117,38 @@ class CCompositor { PHLWINDOW getWindowFromHandle(uint32_t); bool isWorkspaceVisible(PHLWORKSPACE); bool isWorkspaceVisibleNotCovered(PHLWORKSPACE); - PHLWORKSPACE getWorkspaceByID(const int&); + PHLWORKSPACE getWorkspaceByID(const WORKSPACEID&); PHLWORKSPACE getWorkspaceByName(const std::string&); PHLWORKSPACE getWorkspaceByString(const std::string&); void sanityCheckWorkspaces(); - void updateWorkspaceWindowDecos(const int&); - void updateWorkspaceWindowData(const int&); - int getWindowsOnWorkspace(const int& id, std::optional onlyTiled = {}, std::optional onlyVisible = {}); - int getGroupsOnWorkspace(const int& id, std::optional onlyTiled = {}, std::optional onlyVisible = {}); + void updateWorkspaceWindowDecos(const WORKSPACEID&); + void updateWorkspaceWindowData(const WORKSPACEID&); + int getWindowsOnWorkspace(const WORKSPACEID& id, std::optional onlyTiled = {}, std::optional onlyVisible = {}); + int getGroupsOnWorkspace(const WORKSPACEID& id, std::optional onlyTiled = {}, std::optional onlyVisible = {}); PHLWINDOW getUrgentWindow(); - bool hasUrgentWindowOnWorkspace(const int&); - PHLWINDOW getFirstWindowOnWorkspace(const int&); - PHLWINDOW getTopLeftWindowOnWorkspace(const int&); - PHLWINDOW getFullscreenWindowOnWorkspace(const int&); + bool hasUrgentWindowOnWorkspace(const WORKSPACEID&); + PHLWINDOW getFirstWindowOnWorkspace(const WORKSPACEID&); + PHLWINDOW getTopLeftWindowOnWorkspace(const WORKSPACEID&); + PHLWINDOW getFullscreenWindowOnWorkspace(const WORKSPACEID&); bool isWindowActive(PHLWINDOW); void changeWindowZOrder(PHLWINDOW, bool); - void cleanupFadingOut(const int& monid); + void cleanupFadingOut(const MONITORID& monid); PHLWINDOW getWindowInDirection(PHLWINDOW, char); PHLWINDOW getNextWindowOnWorkspace(PHLWINDOW, bool focusableOnly = false, std::optional floating = {}); PHLWINDOW getPrevWindowOnWorkspace(PHLWINDOW, bool focusableOnly = false, std::optional floating = {}); - int getNextAvailableNamedWorkspace(); + WORKSPACEID getNextAvailableNamedWorkspace(); bool isPointOnAnyMonitor(const Vector2D&); bool isPointOnReservedArea(const Vector2D& point, const CMonitor* monitor = nullptr); CMonitor* getMonitorInDirection(const char&); CMonitor* getMonitorInDirection(CMonitor*, const char&); void updateAllWindowsAnimatedDecorationValues(); - void updateWorkspaceWindows(const int64_t& id); + void updateWorkspaceWindows(const WORKSPACEID& id); void updateWindowAnimatedDecorationValues(PHLWINDOW); - int getNextAvailableMonitorID(std::string const& name); + MONITORID getNextAvailableMonitorID(std::string const& name); void moveWorkspaceToMonitor(PHLWORKSPACE, CMonitor*, bool noWarpCursor = false); void swapActiveWorkspaces(CMonitor*, CMonitor*); CMonitor* getMonitorFromString(const std::string&); - bool workspaceIDOutOfBounds(const int64_t&); + bool workspaceIDOutOfBounds(const WORKSPACEID&); void setWindowFullscreenInternal(const PHLWINDOW PWINDOW, const eFullscreenMode MODE); void setWindowFullscreenClient(const PHLWINDOW PWINDOW, const eFullscreenMode MODE); void setWindowFullscreenState(const PHLWINDOW PWINDOW, const sFullscreenState state); @@ -162,12 +165,13 @@ class CCompositor { PHLLS getLayerSurfaceFromSurface(SP); void closeWindow(PHLWINDOW); Vector2D parseWindowVectorArgsRelative(const std::string&, const Vector2D&); - void forceReportSizesToWindowsOnWorkspace(const int&); - PHLWORKSPACE createNewWorkspace(const int&, const int&, const std::string& name = "", bool isEmtpy = true); // will be deleted next frame if left empty and unfocused! - void renameWorkspace(const int&, const std::string& name = ""); + void forceReportSizesToWindowsOnWorkspace(const WORKSPACEID&); + PHLWORKSPACE createNewWorkspace(const WORKSPACEID&, const MONITORID&, const std::string& name = "", + bool isEmpty = true); // will be deleted next frame if left empty and unfocused! + void renameWorkspace(const WORKSPACEID&, const std::string& name = ""); void setActiveMonitor(CMonitor*); - bool isWorkspaceSpecial(const int&); - int getNewSpecialID(); + bool isWorkspaceSpecial(const WORKSPACEID&); + WORKSPACEID getNewSpecialID(); void performUserChecks(); void moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWorkspace); PHLWINDOW getForceFocus(); diff --git a/src/SharedDefs.hpp b/src/SharedDefs.hpp index 2a1546c6..9bee7150 100644 --- a/src/SharedDefs.hpp +++ b/src/SharedDefs.hpp @@ -52,4 +52,8 @@ struct SHyprCtlCommand { std::function fn; }; +typedef int64_t WINDOWID; +typedef int64_t MONITORID; +typedef int64_t WORKSPACEID; + typedef std::function HOOK_CALLBACK_FN; diff --git a/src/config/ConfigDescriptions.hpp b/src/config/ConfigDescriptions.hpp new file mode 100644 index 00000000..bef5ee53 --- /dev/null +++ b/src/config/ConfigDescriptions.hpp @@ -0,0 +1,1541 @@ +#pragma once + +#include "ConfigManager.hpp" + +inline static const std::vector CONFIG_OPTIONS = { + + /* + * general: + */ + + SConfigOptionDescription{ + .value = "general:border_size", + .description = "size of the border around windows", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{1, 0, 20}, + }, + SConfigOptionDescription{ + .value = "general:no_border_on_floating", + .description = "disable borders for floating windows", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "general:gaps_in", + .description = "gaps between windows\n\nsupports css style gaps (top, right, bottom, left -> 5 10 15 20)", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{"5"}, + }, + SConfigOptionDescription{ + .value = "general:gaps_out", + .description = "gaps between windows and monitor edges\n\nsupports css style gaps (top, right, bottom, left -> 5 10 15 20)", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{"20"}, + }, + SConfigOptionDescription{ + .value = "general:gaps_workspaces", + .description = "gaps between workspaces. Stacks with gaps_out.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 100}, + }, + SConfigOptionDescription{ + .value = "general:col.inactive_border", + .description = "border color for inactive windows", + .type = CONFIG_OPTION_GRADIENT, + .data = SConfigOptionDescription::SGradientData{"0xff444444"}, + }, + SConfigOptionDescription{ + .value = "general:col.active_border", + .description = "border color for the active window", + .type = CONFIG_OPTION_GRADIENT, + .data = SConfigOptionDescription::SGradientData{"0xffffffff"}, + }, + SConfigOptionDescription{ + .value = "general:col.nogroup_border", + .description = "inactive border color for window that cannot be added to a group (see denywindowfromgroup dispatcher)", + .type = CONFIG_OPTION_GRADIENT, + .data = SConfigOptionDescription::SGradientData{"0xffffaaff"}, + }, + SConfigOptionDescription{ + .value = "general:col.nogroup_border_active", + .description = "active border color for window that cannot be added to a group", + .type = CONFIG_OPTION_GRADIENT, + .data = SConfigOptionDescription::SGradientData{"0xffff00ff"}, + }, + SConfigOptionDescription{ + .value = "general:layout", + .description = "which layout to use. [dwindle/master]", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{"dwindle"}, + }, + SConfigOptionDescription{ + .value = "general:no_focus_fallback", + .description = "if true, will not fall back to the next available window when moving focus in a direction where no window was found", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "general:resize_on_border", + .description = "enables resizing windows by clicking and dragging on borders and gaps", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "general:extend_border_grab_area", + .description = "extends the area around the border where you can click and drag on, only used when general:resize_on_border is on.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{15, 0, 100}, + }, + SConfigOptionDescription{ + .value = "general:hover_icon_on_border", + .description = "show a cursor icon when hovering over borders, only used when general:resize_on_border is on.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "general:allow_tearing", + .description = "master switch for allowing tearing to occur. See the Tearing page.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "general:resize_corner", + .description = "force floating windows to use a specific corner when being resized (1-4 going clockwise from top left, 0 to disable)", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 4}, + }, + + /* + * decoration: + */ + + SConfigOptionDescription{ + .value = "decoration:rounding", + .description = "rounded corners' radius (in layout px)", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 20}, + }, + SConfigOptionDescription{ + .value = "decoration:active_opacity", + .description = "opacity of active windows. [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{1, 0, 1}, + }, + SConfigOptionDescription{ + .value = "decoration:inactive_opacity", + .description = "opacity of inactive windows. [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{1, 0, 1}, + }, + SConfigOptionDescription{ + .value = "decoration:fullscreen_opacity", + .description = "opacity of fullscreen windows. [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{1, 0, 1}, + }, + SConfigOptionDescription{ + .value = "decoration:drop_shadow", + .description = "enable drop shadows on windows", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "decoration:shadow_range", + .description = "Shadow range (size) in layout px", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{4, 0, 100}, + }, + SConfigOptionDescription{ + .value = "decoration:shadow_render_power", + .description = "in what power to render the falloff (more power, the faster the falloff) [1 - 4]", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{3, 1, 4}, + }, + SConfigOptionDescription{ + .value = "decoration:shadow_ignore_window", + .description = "if true, the shadow will not be rendered behind the window itself, only around it.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "decoration:col.shadow", + .description = "shadow's color. Alpha dictates shadow's opacity.", + .type = CONFIG_OPTION_COLOR, + .data = SConfigOptionDescription::SColorData{0xee1a1a1a}, + }, + SConfigOptionDescription{ + .value = "decoration:col.shadow_inactive", + .description = "inactive shadow color. (if not set, will fall back to col.shadow)", + .type = CONFIG_OPTION_COLOR, + .data = SConfigOptionDescription::SColorData{}, //##TODO UNSET? + }, + SConfigOptionDescription{ + .value = "decoration:shadow_offset", + .description = "shadow's rendering offset.", + .type = CONFIG_OPTION_VECTOR, + .data = SConfigOptionDescription::SVectorData{{}, {-250, -250}, {250, 250}}, + }, + SConfigOptionDescription{ + .value = "decoration:shadow_scale", + .description = "shadow's scale. [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{1, 0, 1}, + }, + SConfigOptionDescription{ + .value = "decoration:dim_inactive", + .description = "enables dimming of inactive windows", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "decoration:dim_strength", + .description = "how much inactive windows should be dimmed [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{0.5, 0, 1}, + }, + SConfigOptionDescription{ + .value = "decoration:dim_special", + .description = "how much to dim the rest of the screen by when a special workspace is open. [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{0.2, 0, 1}, + }, + SConfigOptionDescription{ + .value = "decoration:dim_around", + .description = "how much the dimaround window rule should dim by. [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{0.4, 0, 1}, + }, + SConfigOptionDescription{ + .value = "decoration:screen_shader", + .description = "screen_shader a path to a custom shader to be applied at the end of rendering. See examples/screenShader.frag for an example.", + .type = CONFIG_OPTION_STRING_LONG, + .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? + }, + + /* + * blur: + */ + + SConfigOptionDescription{ + .value = "blur:enabled", + .description = "enable kawase window background blur", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "blur:size", + .description = "blur size (distance)", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{8, 0, 100}, + }, + SConfigOptionDescription{ + .value = "blur:passes", + .description = "the amount of passes to perform", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{1, 0, 10}, + }, + SConfigOptionDescription{ + .value = "blur:ignore_opacity", + .description = "make the blur layer ignore the opacity of the window", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "blur:new_optimizations", + .description = "whether to enable further optimizations to the blur. Recommended to leave on, as it will massively improve performance.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "blur:xray", + .description = "if enabled, floating windows will ignore tiled windows in their blur. Only available if blur_new_optimizations is true. Will reduce overhead on floating " + "blur significantly.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "blur:noise", + .description = "how much noise to apply. [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{0.0117, 0, 1}, + }, + SConfigOptionDescription{ + .value = "blur:contrast", + .description = "contrast modulation for blur. [0.0 - 2.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{0.8916, 0, 2}, + }, + SConfigOptionDescription{ + .value = "blur:brightness", + .description = "brightness modulation for blur. [0.0 - 2.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{0.8172, 0, 2}, + }, + SConfigOptionDescription{ + .value = "blur:vibrancy", + .description = "Increase saturation of blurred colors. [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{0.1696, 0, 1}, + }, + SConfigOptionDescription{ + .value = "blur:vibrancy_darkness", + .description = "How strong the effect of vibrancy is on dark areas . [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{0, 0, 1}, + }, + SConfigOptionDescription{ + .value = "blur:special", + .description = "whether to blur behind the special workspace (note: expensive)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "blur:popups", + .description = "whether to blur popups (e.g. right-click menus)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "blur:popups_ignorealpha", + .description = "works like ignorealpha in layer rules. If pixel opacity is below set value, will not blur. [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{0.2, 0, 1}, + }, + + /* + * animations: + */ + + SConfigOptionDescription{ + .value = "animations:enabled", + .description = "enable animations", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "animations:first_launch_animation", + .description = "enable first launch animation", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + + /* + * input: + */ + + SConfigOptionDescription{ + .value = "input:kb_model", + .description = "Appropriate XKB keymap parameter. See the note below.", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{STRVAL_EMPTY}, + }, + SConfigOptionDescription{ + .value = "input:kb_layout", + .description = "Appropriate XKB keymap parameter", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{"us"}, + }, + SConfigOptionDescription{ + .value = "input:kb_variant", + .description = "Appropriate XKB keymap parameter", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{""}, + }, + SConfigOptionDescription{ + .value = "input:kb_options", + .description = "Appropriate XKB keymap parameter", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{""}, + }, + SConfigOptionDescription{ + .value = "input:kb_rules", + .description = "Appropriate XKB keymap parameter", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{""}, + }, + SConfigOptionDescription{ + .value = "input:kb_file", + .description = "Appropriate XKB keymap parameter", + .type = CONFIG_OPTION_STRING_LONG, + .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? + }, + SConfigOptionDescription{ + .value = "input:numlock_by_default", + .description = "Engage numlock by default.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:resolve_binds_by_sym", + .description = "Determines how keybinds act when multiple layouts are used. If false, keybinds will always act as if the first specified layout is active. If true, " + "keybinds specified by symbols are activated when you type the respective symbol with the current layout.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:repeat_rate", + .description = "The repeat rate for held-down keys, in repeats per second.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{25, 0, 200}, + }, + SConfigOptionDescription{ + .value = "input:repeat_delay", + .description = "Delay before a held-down key is repeated, in milliseconds.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{600, 0, 2000}, + }, + SConfigOptionDescription{ + .value = "input:sensitivity", + .description = "Sets the mouse input sensitivity. Value is clamped to the range -1.0 to 1.0.", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{0, -1, 1}, + }, + SConfigOptionDescription{ + .value = "input:accel_profile", + .description = "Sets the cursor acceleration profile. Can be one of adaptive, flat. Can also be custom, see below. Leave empty to use libinput's default mode for your " + "input device. [adaptive/flat/custom]", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? + }, + SConfigOptionDescription{ + .value = "input:force_no_accel", + .description = "Force no cursor acceleration. This bypasses most of your pointer settings to get as raw of a signal as possible. Enabling this is not recommended due to " + "potential cursor desynchronization.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:left_handed", + .description = "Switches RMB and LMB", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:scroll_points", + .description = "Sets the scroll acceleration profile, when accel_profile is set to custom. Has to be in the form . Leave empty to have a flat scroll curve.", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? + }, + SConfigOptionDescription{ + .value = "input:scroll_method", + .description = "Sets the scroll method. Can be one of 2fg (2 fingers), edge, on_button_down, no_scroll. [2fg/edge/on_button_down/no_scroll]", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? + }, + SConfigOptionDescription{ + .value = "input:scroll_button", + .description = "Sets the scroll button. Has to be an int, cannot be a string. Check wev if you have any doubts regarding the ID. 0 means default.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 300}, + }, + SConfigOptionDescription{ + .value = "input:scroll_button_lock", + .description = "If the scroll button lock is enabled, the button does not need to be held down. Pressing and releasing the button toggles the button lock, which logically " + "holds the button down or releases it. While the button is logically held down, motion events are converted to scroll events.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:scroll_factor", + .description = "Multiplier added to scroll movement for external mice. Note that there is a separate setting for touchpad scroll_factor.", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{1, 0, 2}, + }, + SConfigOptionDescription{ + .value = "input:natural_scroll", + .description = "Inverts scrolling direction. When enabled, scrolling moves content directly, rather than manipulating a scrollbar.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:follow_mouse", + .description = "Specify if and how cursor movement should affect window focus. See the note below. [0/1/2/3]", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{1, 0, 3}, + }, + SConfigOptionDescription{ + .value = "input:focus_on_close", + .description = "Controls the window focus behavior when a window is closed. When set to 0, focus will shift to the next window candidate. When set to 1, focus will shift " + "to the window under the cursor.", + .type = CONFIG_OPTION_CHOICE, + .data = SConfigOptionDescription::SChoiceData{0, "next,cursor"}, + }, + SConfigOptionDescription{ + .value = "input:mouse_refocus", + .description = "if disabled, mouse focus won't switch to the hovered window unless the mouse crosses a window boundary when follow_mouse=1.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "input:float_switch_override_focus", + .description = "If enabled (1 or 2), focus will change to the window under the cursor when changing from tiled-to-floating and vice versa. If 2, focus will also follow " + "mouse on float-to-float switches.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{1, 0, 2}, + }, + SConfigOptionDescription{ + .value = "input:special_fallthrough", + .description = "if enabled, having only floating windows in the special workspace will not block focusing windows in the regular workspace.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:off_window_axis_events", + .description = "Handles axis events around (gaps/border for tiled, dragarea/border for floated) a focused window. 0 ignores axis events 1 sends out-of-bound coordinates 2 " + "fakes pointer coordinates to the closest point inside the window 3 warps the cursor to the closest point inside the window", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{1, 0, 3}, + }, + SConfigOptionDescription{ + .value = "input:emulate_discrete_scroll", + .description = "Emulates discrete scrolling from high resolution scrolling events. 0 disables it, 1 enables handling of non-standard events only, and 2 force enables all " + "scroll wheel events to be handled", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{1, 0, 2}, + }, + + /* + * input:touchpad: + */ + + SConfigOptionDescription{ + .value = "input:touchpad:disable_while_typing", + .description = "Disable the touchpad while typing.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "input:touchpad:natural_scroll", + .description = "Inverts scrolling direction. When enabled, scrolling moves content directly, rather than manipulating a scrollbar.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:touchpad:scroll_factor", + .description = "Multiplier applied to the amount of scroll movement.", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{1, 0, 2}, + }, + SConfigOptionDescription{ + .value = "input:touchpad:middle_button_emulation", + .description = + "Sending LMB and RMB simultaneously will be interpreted as a middle click. This disables any touchpad area that would normally send a middle click based on location.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:touchpad:tap_button_map", + .description = "Sets the tap button mapping for touchpad button emulation. Can be one of lrm (default) or lmr (Left, Middle, Right Buttons). [lrm/lmr]", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? + }, + SConfigOptionDescription{ + .value = "input:touchpad:clickfinger_behavior", + .description = + "Button presses with 1, 2, or 3 fingers will be mapped to LMB, RMB, and MMB respectively. This disables interpretation of clicks based on location on the touchpad.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:touchpad:tap-to-click", + .description = "Tapping on the touchpad with 1, 2, or 3 fingers will send LMB, RMB, and MMB respectively.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "input:touchpad:drag_lock", + .description = "When enabled, lifting the finger off for a short time while dragging will not drop the dragged item.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:touchpad:tap-and-drag", + .description = "Sets the tap and drag mode for the touchpad", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + + /* + * input:touchdevice: + */ + + SConfigOptionDescription{ + .value = "input:touchdevice:transform", + .description = "Transform the input from touchdevices. The possible transformations are the same as those of the monitors", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 6}, // ##TODO RANGE? + }, + SConfigOptionDescription{ + .value = "input:touchdevice:output", + .description = "The monitor to bind touch devices. The default is auto-detection. To stop auto-detection, use an empty string or the [[Empty]] value.", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? + }, + SConfigOptionDescription{ + .value = "input:touchdevice:enabled", + .description = "Whether input is enabled for touch devices.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + + /* + * input:tablet: + */ + + SConfigOptionDescription{ + .value = "input:tablet:transform", + .description = "transform the input from tablets. The possible transformations are the same as those of the monitors", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 6}, // ##TODO RANGE? + }, + SConfigOptionDescription{ + .value = "input:tablet:output", + .description = "the monitor to bind tablets. Empty means unbound..", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? + }, + SConfigOptionDescription{ + .value = "input:tablet:region_position", + .description = "position of the mapped region in monitor layout.", + .type = CONFIG_OPTION_VECTOR, + .data = SConfigOptionDescription::SVectorData{{}, {-20000, -20000}, {20000, 20000}}, + }, + SConfigOptionDescription{ + .value = "input:tablet:region_size", + .description = "size of the mapped region. When this variable is set, tablet input will be mapped to the region. [0, 0] or invalid size means unset.", + .type = CONFIG_OPTION_VECTOR, + .data = SConfigOptionDescription::SVectorData{{}, {-100, -100}, {4000, 4000}}, + }, + SConfigOptionDescription{ + .value = "input:tablet:relative_input", + .description = "whether the input should be relative", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:tablet:left_handed", + .description = "if enabled, the tablet will be rotated 180 degrees", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "input:tablet:active_area_size", + .description = "size of tablet's active area in mm", + .type = CONFIG_OPTION_VECTOR, + .data = SConfigOptionDescription::SVectorData{{}, {}, {500, 500}}, + }, + SConfigOptionDescription{ + .value = "input:tablet:active_area_position", + .description = "position of the active area in mm", + .type = CONFIG_OPTION_VECTOR, + .data = SConfigOptionDescription::SVectorData{{}, {}, {500, 500}}, + }, + + /* ##TODO + * + * PER DEVICE SETTINGS? + * + * */ + + /* + * gestures: + */ + + SConfigOptionDescription{ + .value = "gestures:workspace_swipe", + .description = "enable workspace swipe gesture on touchpad", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "gestures:workspace_swipe_fingers", + .description = "how many fingers for the touchpad gesture", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{3, 0, 5}, //##TODO RANGE? + }, + SConfigOptionDescription{ + .value = "gestures:workspace_swipe_min_fingers", + .description = "if enabled, workspace_swipe_fingers is considered the minimum number of fingers to swipe", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "gestures:workspace_swipe_distance", + .description = "in px, the distance of the touchpad gesture", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{300, 0, 2000}, //##TODO RANGE? + }, + SConfigOptionDescription{ + .value = "gestures:workspace_swipe_touch", + .description = "enable workspace swiping from the edge of a touchscreen", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "gestures:workspace_swipe_invert", + .description = "invert the direction (touchpad only)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "gestures:workspace_swipe_touch_invert", + .description = "invert the direction (touchscreen only)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "gestures:workspace_swipe_min_speed_to_force", + .description = "minimum speed in px per timepoint to force the change ignoring cancel_ratio. Setting to 0 will disable this mechanic.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{30, 0, 200}, //##TODO RANGE? + }, + SConfigOptionDescription{ + .value = "gestures:workspace_swipe_cancel_ratio", + .description = "how much the swipe has to proceed in order to commence it. (0.7 -> if > 0.7 * distance, switch, if less, revert) [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{0.5, 0, 1}, + }, + SConfigOptionDescription{ + .value = "gestures:workspace_swipe_create_new", + .description = "whether a swipe right on the last workspace should create a new one.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "gestures:workspace_swipe_direction_lock", + .description = "if enabled, switching direction will be locked when you swipe past the direction_lock_threshold (touchpad only).", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "gestures:workspace_swipe_direction_lock_threshold", + .description = "in px, the distance to swipe before direction lock activates (touchpad only).", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{10, 0, 200}, //##TODO RANGE? + }, + SConfigOptionDescription{ + .value = "gestures:workspace_swipe_forever", + .description = "if enabled, swiping will not clamp at the neighboring workspaces but continue to the further ones.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "gestures:workspace_swipe_use_r", + .description = "if enabled, swiping will use the r prefix instead of the m prefix for finding workspaces.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + + /* + * group: + */ + + SConfigOptionDescription{ + .value = "group:insert_after_current", + .description = "whether new windows in a group spawn after current or at group tail", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "group:focus_removed_window", + .description = "whether Hyprland should focus on the window that has just been moved out of the group", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "group:merge_groups_on_drag", + .description = "whether window groups can be dragged into other groups", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "general:col.border_active", + .description = "border color for inactive windows", + .type = CONFIG_OPTION_GRADIENT, + .data = SConfigOptionDescription::SGradientData{"0x66ffff00"}, + }, + SConfigOptionDescription{ + .value = "general:col.border_inactive", + .description = "border color for the active window", + .type = CONFIG_OPTION_GRADIENT, + .data = SConfigOptionDescription::SGradientData{"0x66777700"}, + }, + SConfigOptionDescription{ + .value = "general:col.border_locked_active", + .description = "inactive border color for window that cannot be added to a group (see denywindowfromgroup dispatcher)", + .type = CONFIG_OPTION_GRADIENT, + .data = SConfigOptionDescription::SGradientData{"0x66ff5500"}, + }, + SConfigOptionDescription{ + .value = "general:col.border_locked_inactive", + .description = "active border color for window that cannot be added to a group", + .type = CONFIG_OPTION_GRADIENT, + .data = SConfigOptionDescription::SGradientData{"0x66775500"}, + }, + SConfigOptionDescription{ + .value = "group:auto_group", + .description = "automatically group new windows", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "group:drag_into_group", + .description = "whether dragging a window into a unlocked group will merge them. Options: 0 (disabled), 1 (enabled), 2 (only when dragging into the groupbar)", + .type = CONFIG_OPTION_CHOICE, + .data = SConfigOptionDescription::SChoiceData{0, "disabled,enabled,only when dragging into the groupbar"}, + }, + SConfigOptionDescription{ + .value = "group:merge_floated_into_tiled_on_groupbar", + .description = "whether dragging a floating window into a tiled window groupbar will merge them", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + + /* + * group:groupbar: + */ + + SConfigOptionDescription{ + .value = "group:groupbar:enabled", + .description = "enables groupbars", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:font_family", + .description = "font used to display groupbar titles, use misc:font_family if not specified", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{STRVAL_EMPTY}, //##TODO UNSET? + }, + SConfigOptionDescription{ + .value = "group:groupbar:font_size", + .description = "font size of groupbar title", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{8, 2, 64}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:gradients", + .description = "enables gradients", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:height", + .description = "height of the groupbar", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{14, 1, 64}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:stacked", + .description = "render the groupbar as a vertical stack", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:priority", + .description = "sets the decoration priority for groupbars", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{3, 0, 6}, //##TODO RANGE? + }, + SConfigOptionDescription{ + .value = "group:groupbar:render_titles", + .description = "whether to render titles in the group bar decoration", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:scrolling", + .description = "whether scrolling in the groupbar changes group active window", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:text_color", + .description = "controls the group bar text color", + .type = CONFIG_OPTION_COLOR, + .data = SConfigOptionDescription::SColorData{0xffffffff}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:col.active", + .description = "active group border color", + .type = CONFIG_OPTION_COLOR, + .data = SConfigOptionDescription::SColorData{0x66ffff00}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:col.inactive", + .description = "inactive (out of focus) group border color", + .type = CONFIG_OPTION_COLOR, + .data = SConfigOptionDescription::SColorData{0x66777700}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:col.locked_active", + .description = "active locked group border color", + .type = CONFIG_OPTION_COLOR, + .data = SConfigOptionDescription::SColorData{0x66ff5500}, + }, + SConfigOptionDescription{ + .value = "group:groupbar:col.locked_inactive", + .description = "controls the group bar text color", + .type = CONFIG_OPTION_COLOR, + .data = SConfigOptionDescription::SColorData{0x66775500}, + }, + + /* + * misc: + */ + + SConfigOptionDescription{ + .value = "misc:disable_hyprland_logo", + .description = "disables the random Hyprland logo / anime girl background. :(", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:disable_splash_rendering", + .description = "disables the Hyprland splash rendering. (requires a monitor reload to take effect)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:col.splash", + .description = "Changes the color of the splash text (requires a monitor reload to take effect).", + .type = CONFIG_OPTION_COLOR, + .data = SConfigOptionDescription::SColorData{0xffffffff}, + }, + SConfigOptionDescription{ + .value = "misc:font_family", + .description = "Set the global default font to render the text including debug fps/notification, config error messages and etc., selected from system fonts.", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{"Sans"}, + }, + SConfigOptionDescription{ + .value = "misc:splash_font_family", + .description = "Changes the font used to render the splash text, selected from system fonts (requires a monitor reload to take effect).", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{STRVAL_EMPTY}, //##TODO UNSET? + }, + SConfigOptionDescription{ + .value = "misc:force_default_wallpaper", + .description = "Enforce any of the 3 default wallpapers. Setting this to 0 or 1 disables the anime background. -1 means “random”. [-1/0/1/2]", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{-1, -1, 2}, + }, + SConfigOptionDescription{ + .value = "misc:vfr", + .description = "controls the VFR status of Hyprland. Heavily recommended to leave enabled to conserve resources.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "misc:vrr", + .description = " controls the VRR (Adaptive Sync) of your monitors. 0 - off, 1 - on, 2 - fullscreen only [0/1/2]", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 2}, + }, + SConfigOptionDescription{ + .value = "misc:mouse_move_enables_dpms", + .description = "If DPMS is set to off, wake up the monitors if the mouse move", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:key_press_enables_dpms", + .description = "If DPMS is set to off, wake up the monitors if a key is pressed.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:always_follow_on_dnd", + .description = "Will make mouse focus follow the mouse when drag and dropping. Recommended to leave it enabled, especially for people using focus follows mouse at 0.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "misc:layers_hog_keyboard_focus", + .description = "If true, will make keyboard-interactive layers keep their focus on mouse move (e.g. wofi, bemenu)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "misc:animate_manual_resizes", + .description = "If true, will animate manual window resizes/moves", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:animate_mouse_windowdragging", + .description = "If true, will animate windows being dragged by mouse, note that this can cause weird behavior on some curves", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:disable_autoreload", + .description = "If true, the config will not reload automatically on save, and instead needs to be reloaded with hyprctl reload. Might save on battery.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:enable_swallow", + .description = "Enable window swallowing", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:swallow_regex", + .description = + "The class regex to be used for windows that should be swallowed (usually, a terminal). To know more about the list of regex which can be used use this cheatsheet.", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? + }, + SConfigOptionDescription{ + .value = "misc:swallow_exception_regex", + .description = "The title regex to be used for windows that should not be swallowed by the windows specified in swallow_regex (e.g. wev). The regex is matched against the " + "parent (e.g. Kitty) window’s title on the assumption that it changes to whatever process it’s running.", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? + }, + SConfigOptionDescription{ + .value = "misc:focus_on_activate", + .description = "Whether Hyprland should focus an app that requests to be focused (an activate request)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:mouse_move_focuses_monitor", + .description = "Whether mouse moving into a different monitor should focus it", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "misc:render_ahead_of_time", + .description = "[Warning: buggy] starts rendering before your monitor displays a frame in order to lower latency", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:render_ahead_safezone", + .description = "how many ms of safezone to add to rendering ahead of time. Recommended 1-2.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{1, 1, 10}, + }, + SConfigOptionDescription{ + .value = "misc:allow_session_lock_restore", + .description = "if true, will allow you to restart a lockscreen app in case it crashes (red screen of death)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:background_color", + .description = "change the background color. (requires enabled disable_hyprland_logo)", + .type = CONFIG_OPTION_COLOR, + .data = SConfigOptionDescription::SColorData{0x111111}, + }, + SConfigOptionDescription{ + .value = "misc:close_special_on_empty", + .description = "close the special workspace if the last window is removed", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "misc:new_window_takes_over_fullscreen", + .description = "if there is a fullscreen or maximized window, decide whether a new tiled window opened should replace it, stay behind or disable the fullscreen/maximized " + "state. 0 - behind, 1 - takes over, 2 - unfullscreen/unmaxize [0/1/2]", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 2}, + }, + SConfigOptionDescription{ + .value = "misc:exit_window_retains_fullscreen", + .description = "if true, closing a fullscreen window makes the next focused window fullscreen", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "misc:initial_workspace_tracking", + .description = "if enabled, windows will open on the workspace they were invoked on. 0 - disabled, 1 - single-shot, 2 - persistent (all children too)", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{1, 0, 2}, + }, + SConfigOptionDescription{ + .value = "misc:middle_click_paste", + .description = "whether to enable middle-click-paste (aka primary selection)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "misc:render_unfocused_fps", + .description = "the maximum limit for renderunfocused windows' fps in the background", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{15, 1, 120}, + }, + SConfigOptionDescription{ + .value = "misc:disable_xdg_env_checks", + .description = "disable the warning if XDG environment is externally managed", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + + /* + * binds: + */ + + SConfigOptionDescription{ + .value = "binds:pass_mouse_when_bound", + .description = "if disabled, will not pass the mouse events to apps / dragging windows around if a keybind has been triggered.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "binds:scroll_event_delay", + .description = "in ms, how many ms to wait after a scroll event to allow passing another one for the binds.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{300, 0, 2000}, + }, + SConfigOptionDescription{ + .value = "binds:workspace_back_and_forth", + .description = "If enabled, an attempt to switch to the currently focused workspace will instead switch to the previous workspace. Akin to i3’s auto_back_and_forth.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "binds:allow_workspace_cycles", + .description = "If enabled, workspaces don’t forget their previous workspace, so cycles can be created by switching to the first workspace in a sequence, then endlessly " + "going to the previous workspace.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "binds:workspace_center_on", + .description = "Whether switching workspaces should center the cursor on the workspace (0) or on the last active window for that workspace (1)", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 1}, + }, + SConfigOptionDescription{ + .value = "binds:focus_preferred_method", + .description = "sets the preferred focus finding method when using focuswindow/movewindow/etc with a direction. 0 - history (recent have priority), 1 - length (longer " + "shared edges have priority)", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 1}, + }, + SConfigOptionDescription{ + .value = "binds:ignore_group_lock", + .description = "If enabled, dispatchers like moveintogroup, moveoutofgroup and movewindoworgroup will ignore lock per group.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "binds:movefocus_cycles_fullscreen", + .description = "If enabled, when on a fullscreen window, movefocus will cycle fullscreen, if not, it will move the focus in a direction.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "binds:disable_keybind_grabbing", + .description = "If enabled, apps that request keybinds to be disabled (e.g. VMs) will not be able to do so.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "binds:window_direction_monitor_fallback", + .description = "If enabled, moving a window or focus over the edge of a monitor with a direction will move it to the next monitor in that direction.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + + /* + * xwayland: + */ + + SConfigOptionDescription{ + .value = "xwayland:enabled", + .description = "allow running applications using X11", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "xwayland:use_nearest_neighbor", + .description = "uses the nearest neighbor filtering for xwayland apps, making them pixelated rather than blurry", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "xwayland:force_zero_scaling", + .description = "forces a scale of 1 on xwayland windows on scaled displays.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + + /* + * opengl: + */ + + SConfigOptionDescription{ + .value = "opengl:nvidia_anti_flicker", + .description = "reduces flickering on nvidia at the cost of possible frame drops on lower-end GPUs. On non-nvidia, this is ignored.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "opengl:force_introspection", + .description = "forces introspection at all times. Introspection is aimed at reducing GPU usage in certain cases, but might cause graphical glitches on nvidia. 0 - " + "nothing, 1 - force always on, 2 - force always on if nvidia", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{2, 0, 2}, + }, + + /* + * render: + */ + + SConfigOptionDescription{ + .value = "render:explicit_sync", + .description = "Whether to enable explicit sync support. Requires a hyprland restart. 0 - no, 1 - yes, 2 - auto based on the gpu driver", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{2, 0, 2}, + }, + SConfigOptionDescription{ + .value = "render:explicit_sync_kms", + .description = "Whether to enable explicit sync support for the KMS layer. Requires explicit_sync to be enabled. 0 - no, 1 - yes, 2 - auto based on the gpu driver", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{2, 0, 2}, + }, + SConfigOptionDescription{ + .value = "render:direct_scanout", + .description = "Enables direct scanout. Direct scanout attempts to reduce lag when there is only one fullscreen application on a screen (e.g. game). It is also " + "recommended to set this to false if the fullscreen application shows graphical glitches.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + + /* + * cursor: + */ + + SConfigOptionDescription{ + .value = "cursor:use_nearest_neighbor", + .description = "sync xcursor theme with gsettings, it applies cursor-theme and cursor-size on theme load to gsettings making most CSD gtk based clients use same xcursor " + "theme and size.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "cursor:no_hardware_cursors", + .description = "disables hardware cursors", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "cursor:no_break_fs_vrr", + .description = "disables scheduling new frames on cursor movement for fullscreen apps with VRR enabled to avoid framerate spikes (requires no_hardware_cursors = true)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "cursor:min_refresh_rate", + .description = "minimum refresh rate for cursor movement when no_break_fs_vrr is active. Set to minimum supported refresh rate or higher", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{24, 10, 500}, + }, + SConfigOptionDescription{ + .value = "cursor:hotspot_padding", + .description = "the padding, in logical px, between screen edges and the cursor", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{1, 0, 20}, + }, + SConfigOptionDescription{ + .value = "cursor:inactive_timeout", + .description = "in seconds, after how many seconds of cursor’s inactivity to hide it. Set to 0 for never.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 20}, + }, + SConfigOptionDescription{ + .value = "cursor:no_warps", + .description = "if true, will not warp the cursor in many cases (focusing, keybinds, etc)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "cursor:persistent_warps", + .description = "When a window is refocused, the cursor returns to its last position relative to that window, rather than to the centre.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "cursor:warp_on_change_workspace", + .description = "If true, move the cursor to the last focused window after changing the workspace.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "cursor:default_monitor", + .description = "the name of a default monitor for the cursor to be set to on startup (see hyprctl monitors for names)", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{""}, //##TODO UNSET? + }, + SConfigOptionDescription{ + .value = "cursor:zoom_factor", + .description = "the factor to zoom by around the cursor. Like a magnifying glass. Minimum 1.0 (meaning no zoom)", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{1, 1, 10}, + }, + SConfigOptionDescription{ + .value = "cursor:zoom_rigid", + .description = "whether the zoom should follow the cursor rigidly (cursor is always centered if it can be) or loosely", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "cursor:enable_hyprcursor", + .description = "whether to enable hyprcursor support", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "cursor:hide_on_key_press", + .description = "Hides the cursor when you press any key until the mouse is moved.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "cursor:hide_on_touch", + .description = "Hides the cursor when the last input was a touch input until a mouse input is done.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "cursor:allow_dumb_copy", + .description = "Makes HW cursors work on Nvidia, at the cost of a possible hitch whenever the image changes", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + + /* + * debug: + */ + + SConfigOptionDescription{ + .value = "debug:overlay", + .description = "print the debug performance overlay. Disable VFR for accurate results.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "debug:damage_blink", + .description = "disable logging to a file", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "debug:disable_logs", + .description = "disable logging to a file", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "debug:disable_time", + .description = "disables time logging", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "debug:damage_tracking", + .description = "redraw only the needed bits of the display. Do not change. (default: full - 2) monitor - 1, none - 0", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{2, 0, 2}, + }, + SConfigOptionDescription{ + .value = "debug:enable_stdout_logs", + .description = "enables logging to stdout", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "debug:manual_crash", + .description = "set to 1 and then back to 0 to crash Hyprland.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 1}, + }, + SConfigOptionDescription{ + .value = "debug:suppress_errors", + .description = "if true, do not display config file parsing errors.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "debug:watchdog_timeout", + .description = "sets the timeout in seconds for watchdog to abort processing of a signal of the main thread. Set to 0 to disable.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{5, 0, 20}, + }, + SConfigOptionDescription{ + .value = "debug:disable_scale_checks", + .description = "disables verification of the scale factors. Will result in pixel alignment and rounding errors.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "debug:error_limit", + .description = "limits the number of displayed config file parsing errors.", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{5, 0, 20}, + }, + SConfigOptionDescription{ + .value = "debug:error_position", + .description = "sets the position of the error bar. top - 0, bottom - 1", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{0, 0, 1}, + }, + SConfigOptionDescription{ + .value = "debug:colored_stdout_logs", + .description = "enables colors in the stdout logs.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + + /* + * dwindle: + */ + + SConfigOptionDescription{ + .value = "dwindle:pseudotile", + .description = "enable pseudotiling. Pseudotiled windows retain their floating size when tiled.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "dwindle:force_split", + .description = "0 -> split follows mouse, 1 -> always split to the left (new = left or top) 2 -> always split to the right (new = right or bottom)", + .type = CONFIG_OPTION_CHOICE, + .data = SConfigOptionDescription::SChoiceData{0, "follow mouse,left or top,right or bottom"}, + }, + SConfigOptionDescription{ + .value = "dwindle:preserve_split", + .description = "if enabled, the split (side/top) will not change regardless of what happens to the container.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "dwindle:smart_split", + .description = "if enabled, allows a more precise control over the window split direction based on the cursor's position. The window is conceptually divided into four " + "triangles, and cursor's triangle determines the split direction. This feature also turns on preserve_split.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "dwindle:smart_resizing", + .description = + "if enabled, resizing direction will be determined by the mouse's position on the window (nearest to which corner). Else, it is based on the window's tiling position.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "dwindle:permanent_direction_override", + .description = "if enabled, makes the preselect direction persist until either this mode is turned off, another direction is specified, or a non-direction is specified " + "(anything other than l,r,u/t,d/b)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "dwindle:special_scale_factor", + .description = "specifies the scale factor of windows on the special workspace [0 - 1]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{1, 0, 1}, + }, + SConfigOptionDescription{ + .value = "dwindle:split_width_multiplier", + .description = "specifies the auto-split width multiplier", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{1, 0.1, 3}, + }, + SConfigOptionDescription{ + .value = "dwindle:use_active_for_splits", + .description = "whether to prefer the active window or the mouse position for splits", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "dwindle:default_split_ratio", + .description = "the default split ratio on window open. 1 means even 50/50 split. [0.1 - 1.9]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{1, 0.1, 1.9}, + }, + SConfigOptionDescription{ + .value = "dwindle:split_bias", + .description = "specifies which window will receive the larger half of a split. positional - 0, current window - 1, opening window - 2 [0/1/2]", + .type = CONFIG_OPTION_CHOICE, + .data = SConfigOptionDescription::SChoiceData{0, "positional,current,opening"}, + }, + + /* + * master: + */ + + SConfigOptionDescription{ + .value = "master:allow_small_split", + .description = "enable adding additional master windows in a horizontal split style", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "master:special_scale_factor", + .description = "the scale of the special workspace windows. [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{1, 0, 1}, + }, + SConfigOptionDescription{ + .value = "master:mfact", + .description = + "the size as a percentage of the master window, for example `mfact = 0.70` would mean 70% of the screen will be the master window, and 30% the slave [0.0 - 1.0]", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{0.55, 0, 1}, + }, + SConfigOptionDescription{ + .value = "master:new_status", + .description = "`master`: new window becomes master; `slave`: new windows are added to slave stack; `inherit`: inherit from focused window", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{"slave"}, + }, + SConfigOptionDescription{ + .value = "master:new_on_top", + .description = "whether a newly open window should be on the top of the stack", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "master:new_on_active", + .description = "`before`, `after`: place new window relative to the focused window; `none`: place new window according to the value of `new_on_top`. ", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{"none"}, + }, + SConfigOptionDescription{ + .value = "master:orientation", + .description = "default placement of the master area, can be left, right, top, bottom or center", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{"left"}, + }, + SConfigOptionDescription{ + .value = "master:inherit_fullscreen", + .description = "inherit fullscreen status when cycling/swapping to another window (e.g. monocle layout)", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "master:always_center_master", + .description = "when using orientation=center, keep the master window centered, even when it is the only window in the workspace.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "master:smart_resizing", + .description = + "if enabled, resizing direction will be determined by the mouse's position on the window (nearest to which corner). Else, it is based on the window's tiling position.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "master:drop_at_cursor", + .description = "when enabled, dragging and dropping windows will put them at the cursor position. Otherwise, when dropped at the stack side, they will go to the " + "top/bottom of the stack depending on new_on_top.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, +}; diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index fe3af8c0..6ec63d4c 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -3,8 +3,11 @@ #include "../render/decorations/CHyprGroupBarDecoration.hpp" #include "config/ConfigDataValues.hpp" +#include "config/ConfigValue.hpp" #include "helpers/varlist/VarList.hpp" #include "../protocols/LayerShell.hpp" +#include "../xwayland/XWayland.hpp" +#include "../protocols/OutputManagement.hpp" #include #include @@ -27,7 +30,9 @@ #include using namespace Hyprutils::String; -extern "C" char** environ; +extern "C" char** environ; + +#include "ConfigDescriptions.hpp" static Hyprlang::CParseResult configHandleGradientSet(const char* VALUE, void** data) { std::string V = VALUE; @@ -42,7 +47,7 @@ static Hyprlang::CParseResult configHandleGradientSet(const char* VALUE, void** std::string parseError = ""; - for (auto& var : varlist) { + for (auto const& var : varlist) { if (var.find("deg") != std::string::npos) { // last arg try { @@ -137,6 +142,18 @@ static Hyprlang::CParseResult handleExecOnce(const char* c, const char* v) { return result; } +static Hyprlang::CParseResult handleExecShutdown(const char* c, const char* v) { + const std::string VALUE = v; + const std::string COMMAND = c; + + const auto RESULT = g_pConfigManager->handleExecShutdown(COMMAND, VALUE); + + Hyprlang::CParseResult result; + if (RESULT.has_value()) + result.setError(RESULT.value().c_str()); + return result; +} + static Hyprlang::CParseResult handleMonitor(const char* c, const char* v) { const std::string VALUE = v; const std::string COMMAND = c; @@ -311,8 +328,6 @@ CConfigManager::CConfigManager() { configPaths.emplace_back(getMainConfigPath()); m_pConfig = std::make_unique(configPaths.begin()->c_str(), Hyprlang::SConfigOptions{.throwAllErrors = true, .allowMissingConfig = true}); - m_pConfig->addConfigValue("general:sensitivity", {1.0f}); - m_pConfig->addConfigValue("general:apply_sens_to_raw", Hyprlang::INT{0}); m_pConfig->addConfigValue("general:border_size", Hyprlang::INT{1}); m_pConfig->addConfigValue("general:no_border_on_floating", Hyprlang::INT{0}); m_pConfig->addConfigValue("general:border_part_of_window", Hyprlang::INT{1}); @@ -346,7 +361,6 @@ CConfigManager::CConfigManager() { m_pConfig->addConfigValue("misc:swallow_regex", {STRVAL_EMPTY}); m_pConfig->addConfigValue("misc:swallow_exception_regex", {STRVAL_EMPTY}); m_pConfig->addConfigValue("misc:focus_on_activate", Hyprlang::INT{0}); - m_pConfig->addConfigValue("misc:no_direct_scanout", Hyprlang::INT{1}); m_pConfig->addConfigValue("misc:mouse_move_focuses_monitor", Hyprlang::INT{1}); m_pConfig->addConfigValue("misc:render_ahead_of_time", Hyprlang::INT{0}); m_pConfig->addConfigValue("misc:render_ahead_safezone", Hyprlang::INT{1}); @@ -357,9 +371,15 @@ CConfigManager::CConfigManager() { m_pConfig->addConfigValue("misc:exit_window_retains_fullscreen", Hyprlang::INT{0}); m_pConfig->addConfigValue("misc:initial_workspace_tracking", Hyprlang::INT{1}); m_pConfig->addConfigValue("misc:middle_click_paste", Hyprlang::INT{1}); + m_pConfig->addConfigValue("misc:render_unfocused_fps", Hyprlang::INT{15}); + m_pConfig->addConfigValue("misc:disable_xdg_env_checks", Hyprlang::INT{0}); m_pConfig->addConfigValue("group:insert_after_current", Hyprlang::INT{1}); m_pConfig->addConfigValue("group:focus_removed_window", Hyprlang::INT{1}); + m_pConfig->addConfigValue("group:merge_groups_on_drag", Hyprlang::INT{1}); + m_pConfig->addConfigValue("group:merge_floated_into_tiled_on_groupbar", Hyprlang::INT{0}); + m_pConfig->addConfigValue("group:auto_group", Hyprlang::INT{1}); + m_pConfig->addConfigValue("group:drag_into_group", Hyprlang::INT{1}); m_pConfig->addConfigValue("group:groupbar:enabled", Hyprlang::INT{1}); m_pConfig->addConfigValue("group:groupbar:font_family", {STRVAL_EMPTY}); m_pConfig->addConfigValue("group:groupbar:font_size", Hyprlang::INT{8}); @@ -426,9 +446,9 @@ CConfigManager::CConfigManager() { m_pConfig->addConfigValue("dwindle:preserve_split", Hyprlang::INT{0}); m_pConfig->addConfigValue("dwindle:special_scale_factor", {1.f}); m_pConfig->addConfigValue("dwindle:split_width_multiplier", {1.0f}); - m_pConfig->addConfigValue("dwindle:no_gaps_when_only", Hyprlang::INT{0}); m_pConfig->addConfigValue("dwindle:use_active_for_splits", Hyprlang::INT{1}); m_pConfig->addConfigValue("dwindle:default_split_ratio", {1.f}); + m_pConfig->addConfigValue("dwindle:split_bias", Hyprlang::INT{0}); m_pConfig->addConfigValue("dwindle:smart_split", Hyprlang::INT{0}); m_pConfig->addConfigValue("dwindle:smart_resizing", Hyprlang::INT{1}); @@ -438,7 +458,6 @@ CConfigManager::CConfigManager() { m_pConfig->addConfigValue("master:always_center_master", Hyprlang::INT{0}); m_pConfig->addConfigValue("master:new_on_active", {"none"}); m_pConfig->addConfigValue("master:new_on_top", Hyprlang::INT{0}); - m_pConfig->addConfigValue("master:no_gaps_when_only", Hyprlang::INT{0}); m_pConfig->addConfigValue("master:orientation", {"left"}); m_pConfig->addConfigValue("master:inherit_fullscreen", Hyprlang::INT{1}); m_pConfig->addConfigValue("master:allow_small_split", Hyprlang::INT{0}); @@ -449,6 +468,7 @@ CConfigManager::CConfigManager() { m_pConfig->addConfigValue("animations:first_launch_animation", Hyprlang::INT{1}); m_pConfig->addConfigValue("input:follow_mouse", Hyprlang::INT{1}); + m_pConfig->addConfigValue("input:focus_on_close", Hyprlang::INT{0}); m_pConfig->addConfigValue("input:mouse_refocus", Hyprlang::INT{1}); m_pConfig->addConfigValue("input:special_fallthrough", Hyprlang::INT{0}); m_pConfig->addConfigValue("input:off_window_axis_events", Hyprlang::INT{1}); @@ -521,6 +541,7 @@ CConfigManager::CConfigManager() { m_pConfig->addConfigValue("gestures:workspace_swipe_touch", Hyprlang::INT{0}); m_pConfig->addConfigValue("gestures:workspace_swipe_touch_invert", Hyprlang::INT{0}); + m_pConfig->addConfigValue("xwayland:enabled", Hyprlang::INT{1}); m_pConfig->addConfigValue("xwayland:use_nearest_neighbor", Hyprlang::INT{1}); m_pConfig->addConfigValue("xwayland:force_zero_scaling", Hyprlang::INT{0}); @@ -531,7 +552,7 @@ CConfigManager::CConfigManager() { m_pConfig->addConfigValue("cursor:no_break_fs_vrr", Hyprlang::INT{0}); m_pConfig->addConfigValue("cursor:min_refresh_rate", Hyprlang::INT{24}); m_pConfig->addConfigValue("cursor:hotspot_padding", Hyprlang::INT{0}); - m_pConfig->addConfigValue("cursor:inactive_timeout", Hyprlang::INT{0}); + m_pConfig->addConfigValue("cursor:inactive_timeout", {0.f}); m_pConfig->addConfigValue("cursor:no_warps", Hyprlang::INT{0}); m_pConfig->addConfigValue("cursor:persistent_warps", Hyprlang::INT{0}); m_pConfig->addConfigValue("cursor:warp_on_change_workspace", Hyprlang::INT{0}); @@ -539,6 +560,7 @@ CConfigManager::CConfigManager() { m_pConfig->addConfigValue("cursor:zoom_factor", {1.f}); m_pConfig->addConfigValue("cursor:zoom_rigid", Hyprlang::INT{0}); m_pConfig->addConfigValue("cursor:enable_hyprcursor", Hyprlang::INT{1}); + m_pConfig->addConfigValue("cursor:sync_gsettings_theme", Hyprlang::INT{1}); m_pConfig->addConfigValue("cursor:hide_on_key_press", Hyprlang::INT{0}); m_pConfig->addConfigValue("cursor:hide_on_touch", Hyprlang::INT{1}); m_pConfig->addConfigValue("cursor:allow_dumb_copy", Hyprlang::INT{0}); @@ -560,7 +582,9 @@ CConfigManager::CConfigManager() { m_pConfig->addConfigValue("group:groupbar:col.locked_active", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0x66ff5500"}); m_pConfig->addConfigValue("group:groupbar:col.locked_inactive", Hyprlang::CConfigCustomValueType{&configHandleGradientSet, configHandleGradientDestroy, "0x66775500"}); - m_pConfig->addConfigValue("experimental:explicit_sync", Hyprlang::INT{0}); + m_pConfig->addConfigValue("render:explicit_sync", Hyprlang::INT{2}); + m_pConfig->addConfigValue("render:explicit_sync_kms", Hyprlang::INT{2}); + m_pConfig->addConfigValue("render:direct_scanout", Hyprlang::INT{0}); // devices m_pConfig->addSpecialCategory("device", {"name"}); @@ -601,6 +625,7 @@ CConfigManager::CConfigManager() { // keywords m_pConfig->registerHandler(&::handleRawExec, "exec", {false}); m_pConfig->registerHandler(&::handleExecOnce, "exec-once", {false}); + m_pConfig->registerHandler(&::handleExecShutdown, "exec-shutdown", {false}); m_pConfig->registerHandler(&::handleMonitor, "monitor", {false}); m_pConfig->registerHandler(&::handleBind, "bind", {true}); m_pConfig->registerHandler(&::handleUnbind, "unbind", {false}); @@ -661,6 +686,10 @@ std::string CConfigManager::getMainConfigPath() { if (!g_pCompositor->explicitConfigPath.empty()) return g_pCompositor->explicitConfigPath; + if (const auto CFG_ENV = getenv("HYPRLAND_CONFIG"); CFG_ENV) + return CFG_ENV; + Debug::log(TRACE, "Seems as if HYPRLAND_CONFIG isn't set, let's see what we can do with HOME."); + static const auto paths = Hyprutils::Path::findConfig(ISDEBUG ? "hyprlandd" : "hyprland"); if (paths.first.has_value()) { return paths.first.value(); @@ -720,7 +749,6 @@ void CConfigManager::setDefaultAnimationVars() { INITANIMCFG("fade"); INITANIMCFG("border"); INITANIMCFG("borderangle"); - INITANIMCFG("workspaces"); // windows INITANIMCFG("windowsIn"); @@ -741,7 +769,12 @@ void CConfigManager::setDefaultAnimationVars() { // border // workspaces + INITANIMCFG("workspaces"); + INITANIMCFG("workspacesIn"); + INITANIMCFG("workspacesOut"); INITANIMCFG("specialWorkspace"); + INITANIMCFG("specialWorkspaceIn"); + INITANIMCFG("specialWorkspaceOut"); } // init the values @@ -770,7 +803,11 @@ void CConfigManager::setDefaultAnimationVars() { CREATEANIMCFG("fadeLayersIn", "fadeLayers"); CREATEANIMCFG("fadeLayersOut", "fadeLayers"); + CREATEANIMCFG("workspacesIn", "workspaces"); + CREATEANIMCFG("workspacesOut", "workspaces"); CREATEANIMCFG("specialWorkspace", "workspaces"); + CREATEANIMCFG("specialWorkspaceIn", "specialWorkspace"); + CREATEANIMCFG("specialWorkspaceOut", "specialWorkspace"); } std::optional CConfigManager::resetHLConfig() { @@ -785,6 +822,7 @@ std::optional CConfigManager::resetHLConfig() { m_vDeclaredPlugins.clear(); m_dLayerRules.clear(); m_vFailedPluginConfigValues.clear(); + finalExecRequests.clear(); // paths configPaths.clear(); @@ -798,11 +836,14 @@ std::optional CConfigManager::resetHLConfig() { } void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) { - for (auto& w : g_pCompositor->m_vWindows) { + static const auto PENABLEEXPLICIT = CConfigValue("render:explicit_sync"); + static int prevEnabledExplicit = *PENABLEEXPLICIT; + + for (auto const& w : g_pCompositor->m_vWindows) { w->uncacheWindowDecos(); } - for (auto& m : g_pCompositor->m_vMonitors) + for (auto const& m : g_pCompositor->m_vMonitors) g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->ID); // Update the keyboard layout to the cfg'd one if this is not the first launch @@ -826,8 +867,12 @@ void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) { if (result.error && !std::any_cast(m_pConfig->getConfigValue("debug:suppress_errors"))) g_pHyprError->queueCreate(result.getError(), CColor(1.0, 50.0 / 255.0, 50.0 / 255.0, 1.0)); else if (std::any_cast(m_pConfig->getConfigValue("autogenerated")) == 1) - g_pHyprError->queueCreate("Warning: You're using an autogenerated config! (config file: " + getMainConfigPath() + " )\nSUPER+Q -> kitty\nSUPER+M -> exit Hyprland", - CColor(1.0, 1.0, 70.0 / 255.0, 1.0)); + g_pHyprError->queueCreate( + "Warning: You're using an autogenerated config! (config file: " + getMainConfigPath() + + " )\nSUPER+Q -> kitty (if it doesn't launch, make sure it's installed or choose a different terminal in the config)\nSUPER+M -> exit Hyprland", + CColor(1.0, 1.0, 70.0 / 255.0, 1.0)); + else if (*PENABLEEXPLICIT != prevEnabledExplicit) + g_pHyprError->queueCreate("Warning: You changed the render:explicit_sync option, this requires you to restart Hyprland.", CColor(0.9, 0.76, 0.221, 1.0)); else g_pHyprError->destroy(); @@ -842,11 +887,34 @@ void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) { ensureVRR(); } +#ifndef NO_XWAYLAND + const auto PENABLEXWAYLAND = std::any_cast(m_pConfig->getConfigValue("xwayland:enabled")); + // enable/disable xwayland usage + if (!isFirstLaunch) { + bool prevEnabledXwayland = g_pCompositor->m_bEnableXwayland; + if (PENABLEXWAYLAND != prevEnabledXwayland) { + g_pCompositor->m_bEnableXwayland = PENABLEXWAYLAND; + if (PENABLEXWAYLAND) { + Debug::log(LOG, "xwayland has been enabled"); + } else { + Debug::log(LOG, "xwayland has been disabled, cleaning up..."); + for (auto& w : g_pCompositor->m_vWindows) { + if (w->m_pXDGSurface || !w->m_bIsX11) + continue; + g_pCompositor->closeWindow(w); + } + } + g_pXWayland = std::make_unique(g_pCompositor->m_bEnableXwayland); + } + } else + g_pCompositor->m_bEnableXwayland = PENABLEXWAYLAND; +#endif + if (!isFirstLaunch && !g_pCompositor->m_bUnsafeState) refreshGroupBarGradients(); // Updates dynamic window and workspace rules - for (auto& w : g_pCompositor->m_vWorkspaces) { + for (auto const& w : g_pCompositor->m_vWorkspaces) { if (w->inert()) continue; g_pCompositor->updateWorkspaceWindows(w->m_iID); @@ -874,7 +942,7 @@ void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) { Debug::coloredLogs = reinterpret_cast(m_pConfig->getConfigValuePtr("debug:colored_stdout_logs")->getDataStaticPtr()); - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { // mark blur dirty g_pHyprOpenGL->markBlurDirtyForMonitor(m.get()); @@ -884,7 +952,7 @@ void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) { m->forceFullFrames = 2; // also force mirrors, as the aspect ratio could've changed - for (auto& mirror : m->mirrors) + for (auto const& mirror : m->mirrors) mirror->forceFullFrames = 3; } @@ -916,14 +984,24 @@ void CConfigManager::init() { } std::string CConfigManager::parseKeyword(const std::string& COMMAND, const std::string& VALUE) { - const auto RET = m_pConfig->parseDynamic(COMMAND.c_str(), VALUE.c_str()); + static const auto PENABLEEXPLICIT = CConfigValue("render:explicit_sync"); + static int prevEnabledExplicit = *PENABLEEXPLICIT; + + const auto RET = m_pConfig->parseDynamic(COMMAND.c_str(), VALUE.c_str()); // invalidate layouts if they changed if (COMMAND == "monitor" || COMMAND.contains("gaps_") || COMMAND.starts_with("dwindle:") || COMMAND.starts_with("master:")) { - for (auto& m : g_pCompositor->m_vMonitors) + for (auto const& m : g_pCompositor->m_vMonitors) g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->ID); } + if (COMMAND.contains("explicit")) { + if (*PENABLEEXPLICIT != prevEnabledExplicit) + g_pHyprError->queueCreate("Warning: You changed the render:explicit_sync option, this requires you to restart Hyprland.", CColor(0.9, 0.76, 0.221, 1.0)); + else + g_pHyprError->destroy(); + } + // Update window border colors g_pCompositor->updateAllWindowsAnimatedDecorationValues(); @@ -951,7 +1029,7 @@ void CConfigManager::tick() { bool parse = false; - for (auto& cf : configPaths) { + for (auto const& cf : configPaths) { struct stat fileStat; int err = stat(cf.c_str(), &fileStat); if (err != 0) { @@ -1006,33 +1084,74 @@ std::string CConfigManager::getDeviceString(const std::string& dev, const std::s return VAL; } -SMonitorRule CConfigManager::getMonitorRuleFor(const CMonitor& PMONITOR) { - for (auto& r : m_dMonitorRules | std::views::reverse) { - if (PMONITOR.matchesStaticSelector(r.name)) { - return r; +SMonitorRule CConfigManager::getMonitorRuleFor(const SP PMONITOR) { + auto applyWlrOutputConfig = [PMONITOR](SMonitorRule rule) -> SMonitorRule { + const auto CONFIG = PROTO::outputManagement->getOutputStateFor(PMONITOR); + + if (!CONFIG) + return rule; + + Debug::log(LOG, "CConfigManager::getMonitorRuleFor: found a wlr_output_manager override for {}", PMONITOR->szName); + + Debug::log(LOG, " > overriding enabled: {} -> {}", !rule.disabled, !CONFIG->enabled); + rule.disabled = !CONFIG->enabled; + + if ((CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_MODE) || (CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_CUSTOM_MODE)) { + Debug::log(LOG, " > overriding mode: {:.0f}x{:.0f}@{:.2f}Hz -> {:.0f}x{:.0f}@{:.2f}Hz", rule.resolution.x, rule.resolution.y, rule.refreshRate, CONFIG->resolution.x, + CONFIG->resolution.y, CONFIG->refresh / 1000.F); + rule.resolution = CONFIG->resolution; + rule.refreshRate = CONFIG->refresh / 1000.F; + } + + if (CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_POSITION) { + Debug::log(LOG, " > overriding offset: {:.0f}, {:.0f} -> {:.0f}, {:.0f}", rule.offset.x, rule.offset.y, CONFIG->position.x, CONFIG->position.y); + rule.offset = CONFIG->position; + } + + if (CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_TRANSFORM) { + Debug::log(LOG, " > overriding transform: {} -> {}", (uint8_t)rule.transform, (uint8_t)CONFIG->transform); + rule.transform = CONFIG->transform; + } + + if (CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_SCALE) { + Debug::log(LOG, " > overriding scale: {} -> {}", (uint8_t)rule.scale, (uint8_t)CONFIG->scale); + rule.scale = CONFIG->scale; + } + + if (CONFIG->committedProperties & OUTPUT_HEAD_COMMITTED_ADAPTIVE_SYNC) { + Debug::log(LOG, " > overriding vrr: {} -> {}", rule.vrr.value_or(0), CONFIG->adaptiveSync); + rule.vrr = (int)CONFIG->adaptiveSync; + } + + return rule; + }; + + for (auto const& r : m_dMonitorRules | std::views::reverse) { + if (PMONITOR->matchesStaticSelector(r.name)) { + return applyWlrOutputConfig(r); } } - Debug::log(WARN, "No rule found for {}, trying to use the first.", PMONITOR.szName); + Debug::log(WARN, "No rule found for {}, trying to use the first.", PMONITOR->szName); - for (auto& r : m_dMonitorRules) { + for (auto const& r : m_dMonitorRules) { if (r.name.empty()) { - return r; + return applyWlrOutputConfig(r); } } Debug::log(WARN, "No rules configured. Using the default hardcoded one."); - return SMonitorRule{.autoDir = eAutoDirs::DIR_AUTO_RIGHT, - .name = "", - .resolution = Vector2D(0, 0), - .offset = Vector2D(-INT32_MAX, -INT32_MAX), - .scale = -1}; // 0, 0 is preferred and -1, -1 is auto + return applyWlrOutputConfig(SMonitorRule{.autoDir = eAutoDirs::DIR_AUTO_RIGHT, + .name = "", + .resolution = Vector2D(0, 0), + .offset = Vector2D(-INT32_MAX, -INT32_MAX), + .scale = -1}); // 0, 0 is preferred and -1, -1 is auto } SWorkspaceRule CConfigManager::getWorkspaceRuleFor(PHLWORKSPACE pWorkspace) { SWorkspaceRule mergedRule{}; - for (auto& rule : m_dWorkspaceRules) { + for (auto const& rule : m_dWorkspaceRules) { if (!pWorkspace->matchesStaticSelector(rule.workspaceString)) continue; @@ -1105,7 +1224,7 @@ std::vector CConfigManager::getMatchingRules(PHLWINDOW pWindow, boo // local tags for dynamic tag rule match auto tags = pWindow->m_tags; - for (auto& rule : m_dWindowRules) { + for (auto const& rule : m_dWindowRules) { // check if we have a matching rule if (!rule.v2) { try { @@ -1185,6 +1304,32 @@ std::vector CConfigManager::getMatchingRules(PHLWINDOW pWindow, boo continue; } + if (!rule.szFullscreenState.empty()) { + const auto ARGS = CVarList(rule.szFullscreenState, 2, ' '); + // + std::optional internalMode, clientMode; + + if (ARGS[0] == "*") + internalMode = std::nullopt; + else if (isNumber(ARGS[0])) + internalMode = (eFullscreenMode)std::stoi(ARGS[0]); + else + throw std::runtime_error("szFullscreenState internal mode not valid"); + + if (ARGS[1] == "*") + clientMode = std::nullopt; + else if (isNumber(ARGS[1])) + clientMode = (eFullscreenMode)std::stoi(ARGS[1]); + else + throw std::runtime_error("szFullscreenState client mode not valid"); + + if (internalMode.has_value() && pWindow->m_sFullscreenState.internal != internalMode) + continue; + + if (clientMode.has_value() && pWindow->m_sFullscreenState.client != clientMode) + continue; + } + if (!rule.szOnWorkspace.empty()) { const auto PWORKSPACE = pWindow->m_pWorkspace; if (!PWORKSPACE || !PWORKSPACE->matchesStaticSelector(rule.szOnWorkspace)) @@ -1244,7 +1389,7 @@ std::vector CConfigManager::getMatchingRules(PHLWINDOW pWindow, boo bool anyExecFound = false; - for (auto& er : execRequestedRules) { + for (auto const& er : execRequestedRules) { if (std::ranges::any_of(PIDs, [&](const auto& pid) { return pid == er.iPid; })) { returns.push_back({er.szRule, "execRule"}); anyExecFound = true; @@ -1264,7 +1409,7 @@ std::vector CConfigManager::getMatchingRules(PHLLS pLS) { if (!pLS->layerSurface || pLS->fadingOut) return returns; - for (auto& lr : m_dLayerRules) { + for (auto const& lr : m_dLayerRules) { if (lr.targetNamespace.starts_with("address:0x")) { if (std::format("address:0x{:x}", (uintptr_t)pLS.get()) != lr.targetNamespace) continue; @@ -1301,7 +1446,7 @@ void CConfigManager::dispatchExecOnce() { firstExecDispatched = true; isLaunchingExecOnce = true; - for (auto& c : firstExecRequests) { + for (auto const& c : firstExecRequests) { handleRawExec("", c); } @@ -1318,6 +1463,24 @@ void CConfigManager::dispatchExecOnce() { g_pCompositor->performUserChecks(); } +void CConfigManager::dispatchExecShutdown() { + if (finalExecRequests.empty()) { + g_pCompositor->m_bFinalRequests = false; + return; + } + + g_pCompositor->m_bFinalRequests = true; + + for (auto const& c : finalExecRequests) { + handleExecShutdown("", c); + } + + finalExecRequests.clear(); + + // Actually exit now + handleExecShutdown("", "hyprctl dispatch exit"); +} + void CConfigManager::appendMonitorRule(const SMonitorRule& r) { m_dMonitorRules.emplace_back(r); } @@ -1338,11 +1501,11 @@ void CConfigManager::performMonitorReload() { bool overAgain = false; - for (auto& m : g_pCompositor->m_vRealMonitors) { + for (auto const& m : g_pCompositor->m_vRealMonitors) { if (!m->output || m->isUnsafeFallback) continue; - auto rule = getMonitorRuleFor(*m); + auto rule = getMonitorRuleFor(m); if (!g_pHyprRenderer->applyMonitorRule(m.get(), &rule)) { overAgain = true; @@ -1385,7 +1548,7 @@ bool CConfigManager::deviceConfigExists(const std::string& dev) { } bool CConfigManager::shouldBlurLS(const std::string& ns) { - for (auto& bls : m_dBlurLSNamespaces) { + for (auto const& bls : m_dBlurLSNamespaces) { if (bls == ns) { return true; } @@ -1395,11 +1558,11 @@ bool CConfigManager::shouldBlurLS(const std::string& ns) { } void CConfigManager::ensureMonitorStatus() { - for (auto& rm : g_pCompositor->m_vRealMonitors) { + for (auto const& rm : g_pCompositor->m_vRealMonitors) { if (!rm->output || rm->isUnsafeFallback) continue; - auto rule = getMonitorRuleFor(*rm); + auto rule = getMonitorRuleFor(rm); if (rule.disabled == rm->m_bEnabled) g_pHyprRenderer->applyMonitorRule(rm.get(), &rule); @@ -1417,7 +1580,7 @@ void CConfigManager::ensureVRR(CMonitor* pMonitor) { if (USEVRR == 0) { if (m->vrrActive) { - + m->output->state->resetExplicitFences(); m->output->state->setAdaptiveSync(false); if (!m->state.commit()) @@ -1427,6 +1590,7 @@ void CConfigManager::ensureVRR(CMonitor* pMonitor) { return; } else if (USEVRR == 1) { if (!m->vrrActive) { + m->output->state->resetExplicitFences(); m->output->state->setAdaptiveSync(true); if (!m->state.test()) { @@ -1451,6 +1615,7 @@ void CConfigManager::ensureVRR(CMonitor* pMonitor) { const auto WORKSPACEFULL = PWORKSPACE->m_bHasFullscreenWindow && (PWORKSPACE->m_efFullscreenMode & FSMODE_FULLSCREEN); if (WORKSPACEFULL) { + m->output->state->resetExplicitFences(); m->output->state->setAdaptiveSync(true); if (!m->state.test()) { @@ -1462,6 +1627,7 @@ void CConfigManager::ensureVRR(CMonitor* pMonitor) { Debug::log(ERR, "Couldn't commit output {} in ensureVRR -> true", m->output->name); } else if (!WORKSPACEFULL) { + m->output->state->resetExplicitFences(); m->output->state->setAdaptiveSync(false); if (!m->state.commit()) @@ -1475,7 +1641,7 @@ void CConfigManager::ensureVRR(CMonitor* pMonitor) { return; } - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { ensureVRRForDisplay(m.get()); } } @@ -1497,7 +1663,7 @@ CMonitor* CConfigManager::getBoundMonitorForWS(const std::string& wsname) { } std::string CConfigManager::getBoundMonitorStringForWS(const std::string& wsname) { - for (auto& wr : m_dWorkspaceRules) { + for (auto const& wr : m_dWorkspaceRules) { const auto WSNAME = wr.workspaceName.starts_with("name:") ? wr.workspaceName.substr(5) : wr.workspaceName; if (WSNAME == wsname) @@ -1568,7 +1734,7 @@ void CConfigManager::addPluginKeyword(HANDLE handle, const std::string& name, Hy } void CConfigManager::removePluginConfig(HANDLE handle) { - for (auto& k : pluginKeywords) { + for (auto const& k : pluginKeywords) { if (k.handle != handle) continue; @@ -1576,7 +1742,7 @@ void CConfigManager::removePluginConfig(HANDLE handle) { } std::erase_if(pluginKeywords, [&](const auto& other) { return other.handle == handle; }); - for (auto& [h, n] : pluginVariables) { + for (auto const& [h, n] : pluginVariables) { if (h != handle) continue; @@ -1591,7 +1757,7 @@ std::string CConfigManager::getDefaultWorkspaceFor(const std::string& name) { if (other->monitor == name) return other->workspaceString; if (other->monitor.substr(0, 5) == "desc:") { - auto monitor = g_pCompositor->getMonitorFromDesc(other->monitor.substr(5)); + auto const monitor = g_pCompositor->getMonitorFromDesc(other->monitor.substr(5)); if (monitor && monitor->szName == name) return other->workspaceString; } @@ -1617,6 +1783,16 @@ std::optional CConfigManager::handleExecOnce(const std::string& com return {}; } +std::optional CConfigManager::handleExecShutdown(const std::string& command, const std::string& args) { + if (g_pCompositor->m_bFinalRequests) { + g_pKeybindManager->spawn(args); + return {}; + } + + finalExecRequests.push_back(args); + return {}; +} + static bool parseModeLine(const std::string& modeline, drmModeModeInfo& mode) { auto args = CVarList(modeline, 0, 's'); @@ -1664,7 +1840,7 @@ static bool parseModeLine(const std::string& modeline, drmModeModeInfo& mode) { if (it != flagsmap.end()) mode.flags |= it->second; else - Debug::log(ERR, "invalid flag {} in modeline", it->first); + Debug::log(ERR, "invalid flag {} in modeline", key); } snprintf(mode.name, sizeof(mode.name), "%dx%d@%d", mode.hdisplay, mode.vdisplay, mode.vrefresh / 1000); @@ -1988,7 +2164,7 @@ std::optional CConfigManager::handleBind(const std::string& command bool dontInhibit = false; const auto BINDARGS = command.substr(4); - for (auto& arg : BINDARGS) { + for (auto const& arg : BINDARGS) { if (arg == 'l') { locked = true; } else if (arg == 'r') { @@ -2099,7 +2275,7 @@ std::optional CConfigManager::handleUnbind(const std::string& comma bool windowRuleValid(const std::string& RULE) { static const auto rules = std::unordered_set{ - "float", "fullscreen", "maximize", "noinitialfocus", "pin", "stayfocused", "tile", + "float", "fullscreen", "maximize", "noinitialfocus", "pin", "stayfocused", "tile", "renderunfocused", }; static const auto rulesPrefix = std::vector{ "animation", "bordercolor", "bordersize", "center", "fullscreenstate", "group", "idleinhibit", "maxsize", "minsize", "monitor", @@ -2114,7 +2290,7 @@ bool windowRuleValid(const std::string& RULE) { bool layerRuleValid(const std::string& RULE) { static const auto rules = std::unordered_set{"noanim", "blur", "blurpopups", "dimaround"}; - static const auto rulesPrefix = std::vector{"ignorealpha", "ignorezero", "xray", "animation"}; + static const auto rulesPrefix = std::vector{"ignorealpha", "ignorezero", "xray", "animation", "order"}; return rules.contains(RULE) || std::any_of(rulesPrefix.begin(), rulesPrefix.end(), [&RULE](auto prefix) { return RULE.starts_with(prefix); }); } @@ -2166,9 +2342,9 @@ std::optional CConfigManager::handleLayerRule(const std::string& co m_dLayerRules.push_back({VALUE, RULE}); - for (auto& m : g_pCompositor->m_vMonitors) - for (auto& lsl : m->m_aLayerSurfaceLayers) - for (auto& ls : lsl) + for (auto const& m : g_pCompositor->m_vMonitors) + for (auto const& lsl : m->m_aLayerSurfaceLayers) + for (auto const& ls : lsl) ls->applyRules(); return {}; @@ -2189,17 +2365,18 @@ std::optional CConfigManager::handleWindowRuleV2(const std::string& rule.szRule = RULE; rule.szValue = VALUE; - const auto TAGPOS = VALUE.find("tag:"); - const auto TITLEPOS = VALUE.find("title:"); - const auto CLASSPOS = VALUE.find("class:"); - const auto INITIALTITLEPOS = VALUE.find("initialTitle:"); - const auto INITIALCLASSPOS = VALUE.find("initialClass:"); - const auto X11POS = VALUE.find("xwayland:"); - const auto FLOATPOS = VALUE.find("floating:"); - const auto FULLSCREENPOS = VALUE.find("fullscreen:"); - const auto PINNEDPOS = VALUE.find("pinned:"); - const auto FOCUSPOS = VALUE.find("focus:"); - const auto ONWORKSPACEPOS = VALUE.find("onworkspace:"); + const auto TAGPOS = VALUE.find("tag:"); + const auto TITLEPOS = VALUE.find("title:"); + const auto CLASSPOS = VALUE.find("class:"); + const auto INITIALTITLEPOS = VALUE.find("initialTitle:"); + const auto INITIALCLASSPOS = VALUE.find("initialClass:"); + const auto X11POS = VALUE.find("xwayland:"); + const auto FLOATPOS = VALUE.find("floating:"); + const auto FULLSCREENPOS = VALUE.find("fullscreen:"); + const auto PINNEDPOS = VALUE.find("pinned:"); + const auto FOCUSPOS = VALUE.find("focus:"); + const auto FULLSCREENSTATEPOS = VALUE.find("fullscreenstate:"); + const auto ONWORKSPACEPOS = VALUE.find("onworkspace:"); // find workspacepos that isn't onworkspacepos size_t WORKSPACEPOS = std::string::npos; @@ -2212,9 +2389,8 @@ std::optional CConfigManager::handleWindowRuleV2(const std::string& currentPos = VALUE.find("workspace:", currentPos + 1); } - const auto checkPos = std::unordered_set{ - TAGPOS, TITLEPOS, CLASSPOS, INITIALTITLEPOS, INITIALCLASSPOS, X11POS, FLOATPOS, FULLSCREENPOS, PINNEDPOS, WORKSPACEPOS, FOCUSPOS, ONWORKSPACEPOS, - }; + const auto checkPos = std::unordered_set{TAGPOS, TITLEPOS, CLASSPOS, INITIALTITLEPOS, INITIALCLASSPOS, X11POS, FLOATPOS, + FULLSCREENPOS, PINNEDPOS, FULLSCREENSTATEPOS, WORKSPACEPOS, FOCUSPOS, ONWORKSPACEPOS}; if (checkPos.size() == 1 && checkPos.contains(std::string::npos)) { Debug::log(ERR, "Invalid rulev2 syntax: {}", VALUE); return "Invalid rulev2 syntax: " + VALUE; @@ -2243,6 +2419,8 @@ std::optional CConfigManager::handleWindowRuleV2(const std::string& min = FULLSCREENPOS; if (PINNEDPOS > pos && PINNEDPOS < min) min = PINNEDPOS; + if (FULLSCREENSTATEPOS > pos && FULLSCREENSTATEPOS < min) + min = FULLSCREENSTATEPOS; if (ONWORKSPACEPOS > pos && ONWORKSPACEPOS < min) min = ONWORKSPACEPOS; if (WORKSPACEPOS > pos && WORKSPACEPOS < min) @@ -2287,6 +2465,9 @@ std::optional CConfigManager::handleWindowRuleV2(const std::string& if (PINNEDPOS != std::string::npos) rule.bPinned = extract(PINNEDPOS + 7) == "1" ? 1 : 0; + if (FULLSCREENSTATEPOS != std::string::npos) + rule.szFullscreenState = extract(FULLSCREENSTATEPOS + 16); + if (WORKSPACEPOS != std::string::npos) rule.szWorkspace = extract(WORKSPACEPOS + 10); @@ -2328,6 +2509,9 @@ std::optional CConfigManager::handleWindowRuleV2(const std::string& if (rule.bPinned != -1 && rule.bPinned != other.bPinned) return false; + if (!rule.szFullscreenState.empty() && rule.szFullscreenState != other.szFullscreenState) + return false; + if (!rule.szWorkspace.empty() && rule.szWorkspace != other.szWorkspace) return false; @@ -2359,9 +2543,9 @@ void CConfigManager::updateBlurredLS(const std::string& name, const bool forceBl matchName = matchName.substr(8); } - for (auto& m : g_pCompositor->m_vMonitors) { - for (auto& lsl : m->m_aLayerSurfaceLayers) { - for (auto& ls : lsl) { + for (auto const& m : g_pCompositor->m_vMonitors) { + for (auto const& lsl : m->m_aLayerSurfaceLayers) { + for (auto const& ls : lsl) { if (BYADDRESS) { if (std::format("0x{:x}", (uintptr_t)ls.get()) == matchName) ls->forceBlur = forceBlur; @@ -2414,7 +2598,7 @@ std::optional CConfigManager::handleWorkspaceRules(const std::strin // } const static std::string ruleOnCreatedEmpty = "on-created-empty:"; - const static int ruleOnCreatedEmptyLen = ruleOnCreatedEmpty.length(); + const static auto ruleOnCreatedEmptyLen = ruleOnCreatedEmpty.length(); auto assignRule = [&](std::string rule) -> std::optional { size_t delim = std::string::npos; @@ -2470,7 +2654,7 @@ std::optional CConfigManager::handleWorkspaceRules(const std::strin }; CVarList rulesList{rules, 0, ',', true}; - for (auto& r : rulesList) { + for (auto const& r : rulesList) { const auto R = assignRule(r); if (R.has_value()) return R; @@ -2586,3 +2770,60 @@ std::optional CConfigManager::handlePlugin(const std::string& comma return {}; } + +const std::vector& CConfigManager::getAllDescriptions() { + return CONFIG_OPTIONS; +} + +std::string SConfigOptionDescription::jsonify() const { + auto parseData = [this]() -> std::string { + return std::visit( + [](auto&& val) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return std::format(R"#( "value": "{}")#", val.value); + } else if constexpr (std::is_same_v) { + return std::format(R"#( "value": {}, + "min": {}, + "max": {})#", + val.value, val.min, val.max); + } else if constexpr (std::is_same_v) { + return std::format(R"#( "value": {}, + "min": {}, + "max": {})#", + val.value, val.min, val.max); + } else if constexpr (std::is_same_v) { + return std::format(R"#( "value": {})#", val.color.getAsHex()); + } else if constexpr (std::is_same_v) { + return std::format(R"#( "value": {})#", val.value); + } else if constexpr (std::is_same_v) { + return std::format(R"#( "value": {})#", val.choices); + } else if constexpr (std::is_same_v) { + return std::format(R"#( "x": {}, + "y": {}, + "min_x": {}, + "min_y": {}, + "max_x": {}, + "max_y": {})#", + val.vec.x, val.vec.y, val.min.x, val.min.y, val.max.x, val.max.y); + } else if constexpr (std::is_same_v) { + return std::format(R"#( "value": "{}")#", val.gradient); + } + return std::string{""}; + }, + data); + }; + + std::string json = std::format(R"#({{ + "value": "{}", + "description": "{}", + "type": {}, + "flags": {}, + "data": {{ + {} + }} +}})#", + value, description, (uint16_t)type, (uint32_t)flags, parseData()); + + return json; +} diff --git a/src/config/ConfigManager.hpp b/src/config/ConfigManager.hpp index 75dea9ef..2134acf4 100644 --- a/src/config/ConfigManager.hpp +++ b/src/config/ConfigManager.hpp @@ -6,6 +6,7 @@ #include "../debug/Log.hpp" #include #include "../defines.hpp" +#include #include #include #include @@ -33,7 +34,7 @@ struct SWorkspaceRule { std::string monitor = ""; std::string workspaceString = ""; std::string workspaceName = ""; - int workspaceId = -1; + WORKSPACEID workspaceId = -1; bool isDefault = false; bool isPersistent = false; std::optional gapsIn; @@ -83,6 +84,70 @@ struct SExecRequestedRule { uint64_t iPid = 0; }; +enum eConfigOptionType : uint16_t { + CONFIG_OPTION_BOOL = 0, + CONFIG_OPTION_INT = 1, /* e.g. 0/1/2*/ + CONFIG_OPTION_FLOAT = 2, + CONFIG_OPTION_STRING_SHORT = 3, /* e.g. "auto" */ + CONFIG_OPTION_STRING_LONG = 4, /* e.g. a command */ + CONFIG_OPTION_COLOR = 5, + CONFIG_OPTION_CHOICE = 6, /* e.g. "one", "two", "three" */ + CONFIG_OPTION_GRADIENT = 7, + CONFIG_OPTION_VECTOR = 8, +}; + +enum eConfigOptionFlags : uint32_t { + CONFIG_OPTION_FLAG_PERCENTAGE = (1 << 0), +}; + +struct SConfigOptionDescription { + + struct SBoolData { + bool value = false; + }; + + struct SRangeData { + int value = 0, min = 0, max = 2; + }; + + struct SFloatData { + float value = 0, min = 0, max = 100; + }; + + struct SStringData { + std::string value; + }; + + struct SColorData { + CColor color; + }; + + struct SChoiceData { + int firstIndex = 0; + std::string choices; // comma-separated + }; + + struct SGradientData { + std::string gradient; + }; + + struct SVectorData { + Vector2D vec, min, max; + }; + + std::string value; // e.g. general:gaps_in + std::string description; + std::string specialCategory; // if value is special (e.g. device:abc) value will be abc and special device + bool specialKey = false; + eConfigOptionType type = CONFIG_OPTION_BOOL; + uint32_t flags = 0; // eConfigOptionFlags + + std::string jsonify() const; + + // + std::variant data; +}; + class CConfigManager { public: CConfigManager(); @@ -104,7 +169,7 @@ class CConfigManager { static std::string getMainConfigPath(); const std::string getConfigString(); - SMonitorRule getMonitorRuleFor(const CMonitor&); + SMonitorRule getMonitorRuleFor(const SP); SWorkspaceRule getWorkspaceRuleFor(PHLWORKSPACE workspace); std::string getDefaultWorkspaceFor(const std::string&); @@ -115,6 +180,8 @@ class CConfigManager { std::vector getMatchingRules(PHLWINDOW, bool dynamic = true, bool shadowExec = false); std::vector getMatchingRules(PHLLS); + const std::vector& getAllDescriptions(); + std::unordered_map m_mAdditionalReservedAreas; std::unordered_map getAnimationConfig(); @@ -125,6 +192,7 @@ class CConfigManager { // no-op when done. void dispatchExecOnce(); + void dispatchExecShutdown(); void performMonitorReload(); void appendMonitorRule(const SMonitorRule&); @@ -146,6 +214,7 @@ class CConfigManager { // keywords std::optional handleRawExec(const std::string&, const std::string&); std::optional handleExecOnce(const std::string&, const std::string&); + std::optional handleExecShutdown(const std::string&, const std::string&); std::optional handleMonitor(const std::string&, const std::string&); std::optional handleBind(const std::string&, const std::string&); std::optional handleUnbind(const std::string&, const std::string&); @@ -222,6 +291,7 @@ class CConfigManager { bool firstExecDispatched = false; bool m_bManualCrashInitiated = false; std::deque firstExecRequests; + std::deque finalExecRequests; std::vector> m_vFailedPluginConfigValues; // for plugin values of unloaded plugins std::string m_szConfigErrors = ""; diff --git a/src/config/defaultConfig.hpp b/src/config/defaultConfig.hpp index 98b617d0..5c8b0e68 100644 --- a/src/config/defaultConfig.hpp +++ b/src/config/defaultConfig.hpp @@ -72,7 +72,7 @@ env = HYPRCURSOR_SIZE,24 # Refer to https://wiki.hyprland.org/Configuring/Variables/ # https://wiki.hyprland.org/Configuring/Variables/#general -general { +general { gaps_in = 5 gaps_out = 20 @@ -83,7 +83,7 @@ general { col.inactive_border = rgba(595959aa) # Set to true enable resizing windows by clicking and dragging on borders and gaps - resize_on_border = false + resize_on_border = false # Please see https://wiki.hyprland.org/Configuring/Tearing/ before you turn this on allow_tearing = false @@ -109,7 +109,7 @@ decoration { enabled = true size = 3 passes = 1 - + vibrancy = 0.1696 } } @@ -130,6 +130,13 @@ animations { animation = workspaces, 1, 6, default } +# Ref https://wiki.hyprland.org/Configuring/Workspace-Rules/ +# "Smart gaps" / "No gaps when only" +# uncomment all three if you wish to use that. +# workspace = w[t1], gapsout:0, gapsin:0, border: 0, rounding:0 +# workspace = w[tg1], gapsout:0, gapsin:0, border: 0, rounding:0 +# workspace = f[1], gapsout:0, gapsin:0, border: 0, rounding:0 + # See https://wiki.hyprland.org/Configuring/Dwindle-Layout/ for more dwindle { pseudotile = true # Master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below @@ -142,7 +149,7 @@ master { } # https://wiki.hyprland.org/Configuring/Variables/#misc -misc { +misc { force_default_wallpaper = -1 # Set to 0 or 1 to disable the anime mascot wallpapers disable_hyprland_logo = false # If true disables the random hyprland logo / anime girl background. :( } @@ -182,9 +189,9 @@ device { } -#################### -### KEYBINDINGSS ### -#################### +################### +### KEYBINDINGS ### +################### # See https://wiki.hyprland.org/Configuring/Keywords/ $mainMod = SUPER # Sets "Windows" key as main modifier @@ -241,6 +248,19 @@ bind = $mainMod, mouse_up, workspace, e-1 bindm = $mainMod, mouse:272, movewindow bindm = $mainMod, mouse:273, resizewindow +# Laptop multimedia keys for volume and LCD brightness +bindel = ,XF86AudioRaiseVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+ +bindel = ,XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%- +bindel = ,XF86AudioMute, exec, wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle +bindel = ,XF86AudioMicMute, exec, wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle +bindel = ,XF86MonBrightnessUp, exec, brightnessctl s 10%+ +bindel = ,XF86MonBrightnessDown, exec, brightnessctl s 10%- + +# Requires playerctl +bindl = , XF86AudioNext, exec, playerctl next +bindl = , XF86AudioPause, exec, playerctl play-pause +bindl = , XF86AudioPlay, exec, playerctl play-pause +bindl = , XF86AudioPrev, exec, playerctl previous ############################## ### WINDOWS AND WORKSPACES ### @@ -255,5 +275,9 @@ bindm = $mainMod, mouse:273, resizewindow # Example windowrule v2 # windowrulev2 = float,class:^(kitty)$,title:^(kitty)$ -windowrulev2 = suppressevent maximize, class:.* # You'll probably like this. +# Ignore maximize requests from apps. You'll probably like this. +windowrulev2 = suppressevent maximize, class:.* + +# Fix some dragging issues with XWayland +windowrulev2 = nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0 )#"; diff --git a/src/debug/CrashReporter.cpp b/src/debug/CrashReporter.cpp index c31975b8..000d8c5d 100644 --- a/src/debug/CrashReporter.cpp +++ b/src/debug/CrashReporter.cpp @@ -86,7 +86,7 @@ void CrashReporter::createAndSaveCrash(int sig) { stderr.flush(); } - reportFd = open(reportPath.get_str(), O_WRONLY | O_CREAT, S_IRWXU); + reportFd = open(reportPath.get_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); if (reportFd < 0) { exit_with_error("Failed to open crash report path for writing"); } diff --git a/src/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp index d91a1cec..e0bb2b83 100644 --- a/src/debug/HyprCtl.cpp +++ b/src/debug/HyprCtl.cpp @@ -57,7 +57,7 @@ static std::string formatToString(uint32_t drmFormat) { static std::string availableModesForOutput(CMonitor* pMonitor, eHyprCtlOutputFormat format) { std::string result; - for (auto& m : pMonitor->output->modes) { + for (auto const& m : pMonitor->output->modes) { if (format == FORMAT_NORMAL) result += std::format("{}x{}@{:.2f}Hz ", m->pixelSize.x, m->pixelSize.y, m->refreshRate / 1000.0); else @@ -71,7 +71,7 @@ static std::string availableModesForOutput(CMonitor* pMonitor, eHyprCtlOutputFor std::string CHyprCtl::getMonitorData(Hyprutils::Memory::CSharedPointer m, eHyprCtlOutputFormat format) { std::string result; - if (!m->output || m->ID == -1ull) + if (!m->output || m->ID == -1) return ""; if (format == eHyprCtlOutputFormat::FORMAT_JSON) { @@ -103,6 +103,7 @@ std::string CHyprCtl::getMonitorData(Hyprutils::Memory::CSharedPointer "focused": {}, "dpmsStatus": {}, "vrr": {}, + "solitary": "{:x}", "activelyTearing": {}, "disabled": {}, "currentFormat": "{}", @@ -114,19 +115,20 @@ std::string CHyprCtl::getMonitorData(Hyprutils::Memory::CSharedPointer m->activeWorkspaceID(), (!m->activeWorkspace ? "" : escapeJSONStrings(m->activeWorkspace->m_szName)), m->activeSpecialWorkspaceID(), escapeJSONStrings(m->activeSpecialWorkspace ? m->activeSpecialWorkspace->m_szName : ""), (int)m->vecReservedTopLeft.x, (int)m->vecReservedTopLeft.y, (int)m->vecReservedBottomRight.x, (int)m->vecReservedBottomRight.y, m->scale, (int)m->transform, (m == g_pCompositor->m_pLastMonitor ? "true" : "false"), - (m->dpmsStatus ? "true" : "false"), (m->output->state->state().adaptiveSync ? "true" : "false"), (m->tearingState.activelyTearing ? "true" : "false"), - (m->m_bEnabled ? "false" : "true"), formatToString(m->output->state->state().drmFormat), availableModesForOutput(m.get(), format)); + (m->dpmsStatus ? "true" : "false"), (m->output->state->state().adaptiveSync ? "true" : "false"), (uint64_t)m->solitaryClient.get(), + (m->tearingState.activelyTearing ? "true" : "false"), (m->m_bEnabled ? "false" : "true"), formatToString(m->output->state->state().drmFormat), + availableModesForOutput(m.get(), format)); } else { result += std::format("Monitor {} (ID {}):\n\t{}x{}@{:.5f} at {}x{}\n\tdescription: {}\n\tmake: {}\n\tmodel: {}\n\tserial: {}\n\tactive workspace: {} ({})\n\t" "special workspace: {} ({})\n\treserved: {} {} {} {}\n\tscale: {:.2f}\n\ttransform: {}\n\tfocused: {}\n\t" - "dpmsStatus: {}\n\tvrr: {}\n\tactivelyTearing: {}\n\tdisabled: {}\n\tcurrentFormat: {}\n\tavailableModes: {}\n\n", + "dpmsStatus: {}\n\tvrr: {}\n\tsolitary: {:x}\n\tactivelyTearing: {}\n\tdisabled: {}\n\tcurrentFormat: {}\n\tavailableModes: {}\n\n", m->szName, m->ID, (int)m->vecPixelSize.x, (int)m->vecPixelSize.y, m->refreshRate, (int)m->vecPosition.x, (int)m->vecPosition.y, m->szShortDescription, m->output->make, m->output->model, m->output->serial, m->activeWorkspaceID(), (!m->activeWorkspace ? "" : m->activeWorkspace->m_szName), m->activeSpecialWorkspaceID(), (m->activeSpecialWorkspace ? m->activeSpecialWorkspace->m_szName : ""), (int)m->vecReservedTopLeft.x, (int)m->vecReservedTopLeft.y, (int)m->vecReservedBottomRight.x, (int)m->vecReservedBottomRight.y, m->scale, (int)m->transform, - (m == g_pCompositor->m_pLastMonitor ? "yes" : "no"), (int)m->dpmsStatus, m->output->state->state().adaptiveSync, m->tearingState.activelyTearing, - !m->m_bEnabled, formatToString(m->output->state->state().drmFormat), availableModesForOutput(m.get(), format)); + (m == g_pCompositor->m_pLastMonitor ? "yes" : "no"), (int)m->dpmsStatus, m->output->state->state().adaptiveSync, (uint64_t)m->solitaryClient.get(), + m->tearingState.activelyTearing, !m->m_bEnabled, formatToString(m->output->state->state().drmFormat), availableModesForOutput(m.get(), format)); } return result; @@ -146,7 +148,7 @@ std::string monitorsRequest(eHyprCtlOutputFormat format, std::string request) { if (format == eHyprCtlOutputFormat::FORMAT_JSON) { result += "["; - for (auto& m : allMonitors ? g_pCompositor->m_vRealMonitors : g_pCompositor->m_vMonitors) { + for (auto const& m : allMonitors ? g_pCompositor->m_vRealMonitors : g_pCompositor->m_vMonitors) { result += CHyprCtl::getMonitorData(m, format); } @@ -154,20 +156,11 @@ std::string monitorsRequest(eHyprCtlOutputFormat format, std::string request) { result += "]"; } else { - for (auto& m : allMonitors ? g_pCompositor->m_vRealMonitors : g_pCompositor->m_vMonitors) { - if (!m->output || m->ID == -1ull) + for (auto const& m : allMonitors ? g_pCompositor->m_vRealMonitors : g_pCompositor->m_vMonitors) { + if (!m->output || m->ID == -1) continue; - result += - std::format("Monitor {} (ID {}):\n\t{}x{}@{:.5f} at {}x{}\n\tdescription: {}\n\tmake: {}\n\tmodel: {}\n\tserial: {}\n\tactive workspace: {} ({})\n\t" - "special workspace: {} ({})\n\treserved: {} {} {} {}\n\tscale: {:.2f}\n\ttransform: {}\n\tfocused: {}\n\t" - "dpmsStatus: {}\n\tvrr: {}\n\tactivelyTearing: {}\n\tdisabled: {}\n\tcurrentFormat: {}\n\tavailableModes: {}\n\n", - m->szName, m->ID, (int)m->vecPixelSize.x, (int)m->vecPixelSize.y, m->refreshRate, (int)m->vecPosition.x, (int)m->vecPosition.y, m->szShortDescription, - m->output->make, m->output->model, m->output->serial, m->activeWorkspaceID(), (!m->activeWorkspace ? "" : m->activeWorkspace->m_szName), - m->activeSpecialWorkspaceID(), (m->activeSpecialWorkspace ? m->activeSpecialWorkspace->m_szName : ""), (int)m->vecReservedTopLeft.x, - (int)m->vecReservedTopLeft.y, (int)m->vecReservedBottomRight.x, (int)m->vecReservedBottomRight.y, m->scale, (int)m->transform, - (m == g_pCompositor->m_pLastMonitor ? "yes" : "no"), (int)m->dpmsStatus, (int)(m->output->state ? m->output->state->state().adaptiveSync : false), - m->tearingState.activelyTearing, !m->m_bEnabled, formatToString(m->output->state->state().drmFormat), availableModesForOutput(m.get(), format)); + result += CHyprCtl::getMonitorData(m, format); } } @@ -272,7 +265,7 @@ std::string clientsRequest(eHyprCtlOutputFormat format, std::string request) { if (format == eHyprCtlOutputFormat::FORMAT_JSON) { result += "["; - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (!w->m_bIsMapped && !g_pHyprCtl->m_sCurrentRequestParams.all) continue; @@ -283,7 +276,7 @@ std::string clientsRequest(eHyprCtlOutputFormat format, std::string request) { result += "]"; } else { - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (!w->m_bIsMapped && !g_pHyprCtl->m_sCurrentRequestParams.all) continue; @@ -320,45 +313,48 @@ std::string CHyprCtl::getWorkspaceData(PHLWORKSPACE w, eHyprCtlOutputFormat form static std::string getWorkspaceRuleData(const SWorkspaceRule& r, eHyprCtlOutputFormat format) { const auto boolToString = [](const bool b) -> std::string { return b ? "true" : "false"; }; if (format == eHyprCtlOutputFormat::FORMAT_JSON) { - const std::string monitor = r.monitor.empty() ? "" : std::format(",\n \"monitor\": \"{}\"", escapeJSONStrings(r.monitor)); - const std::string default_ = (bool)(r.isDefault) ? std::format(",\n \"default\": {}", boolToString(r.isDefault)) : ""; - const std::string persistent = (bool)(r.isPersistent) ? std::format(",\n \"persistent\": {}", boolToString(r.isPersistent)) : ""; - const std::string gapsIn = (bool)(r.gapsIn) ? - std::format(",\n \"gapsIn\": [{}, {}, {}, {}]", r.gapsIn.value().top, r.gapsIn.value().right, r.gapsIn.value().bottom, r.gapsIn.value().left) : + const std::string monitor = r.monitor.empty() ? "" : std::format(",\n \"monitor\": \"{}\"", escapeJSONStrings(r.monitor)); + const std::string default_ = (bool)(r.isDefault) ? std::format(",\n \"default\": {}", boolToString(r.isDefault)) : ""; + const std::string persistent = (bool)(r.isPersistent) ? std::format(",\n \"persistent\": {}", boolToString(r.isPersistent)) : ""; + const std::string gapsIn = (bool)(r.gapsIn) ? + std::format(",\n \"gapsIn\": [{}, {}, {}, {}]", r.gapsIn.value().top, r.gapsIn.value().right, r.gapsIn.value().bottom, r.gapsIn.value().left) : + ""; + const std::string gapsOut = (bool)(r.gapsOut) ? + std::format(",\n \"gapsOut\": [{}, {}, {}, {}]", r.gapsOut.value().top, r.gapsOut.value().right, r.gapsOut.value().bottom, r.gapsOut.value().left) : ""; - const std::string gapsOut = (bool)(r.gapsOut) ? - std::format(",\n \"gapsOut\": [{}, {}, {}, {}]", r.gapsOut.value().top, r.gapsOut.value().right, r.gapsOut.value().bottom, r.gapsOut.value().left) : - ""; - const std::string borderSize = (bool)(r.borderSize) ? std::format(",\n \"borderSize\": {}", r.borderSize.value()) : ""; - const std::string border = (bool)(r.noBorder) ? std::format(",\n \"border\": {}", boolToString(!r.noBorder.value())) : ""; - const std::string rounding = (bool)(r.noRounding) ? std::format(",\n \"rounding\": {}", boolToString(!r.noRounding.value())) : ""; - const std::string decorate = (bool)(r.decorate) ? std::format(",\n \"decorate\": {}", boolToString(r.decorate.value())) : ""; - const std::string shadow = (bool)(r.noShadow) ? std::format(",\n \"shadow\": {}", boolToString(!r.noShadow.value())) : ""; + const std::string borderSize = (bool)(r.borderSize) ? std::format(",\n \"borderSize\": {}", r.borderSize.value()) : ""; + const std::string border = (bool)(r.noBorder) ? std::format(",\n \"border\": {}", boolToString(!r.noBorder.value())) : ""; + const std::string rounding = (bool)(r.noRounding) ? std::format(",\n \"rounding\": {}", boolToString(!r.noRounding.value())) : ""; + const std::string decorate = (bool)(r.decorate) ? std::format(",\n \"decorate\": {}", boolToString(r.decorate.value())) : ""; + const std::string shadow = (bool)(r.noShadow) ? std::format(",\n \"shadow\": {}", boolToString(!r.noShadow.value())) : ""; + const std::string defaultName = r.defaultName.has_value() ? std::format(",\n \"defaultName\": \"{}\"", escapeJSONStrings(r.defaultName.value())) : ""; - std::string result = std::format(R"#({{ - "workspaceString": "{}"{}{}{}{}{}{}{}{} + std::string result = + std::format(R"#({{ + "workspaceString": "{}"{}{}{}{}{}{}{}{}{}{}{} }})#", - escapeJSONStrings(r.workspaceString), monitor, default_, persistent, gapsIn, gapsOut, borderSize, border, rounding, decorate, shadow); + escapeJSONStrings(r.workspaceString), monitor, default_, persistent, gapsIn, gapsOut, borderSize, border, rounding, decorate, shadow, defaultName); return result; } else { - const std::string monitor = std::format("\tmonitor: {}\n", r.monitor.empty() ? "" : escapeJSONStrings(r.monitor)); - const std::string default_ = std::format("\tdefault: {}\n", (bool)(r.isDefault) ? boolToString(r.isDefault) : ""); - const std::string persistent = std::format("\tpersistent: {}\n", (bool)(r.isPersistent) ? boolToString(r.isPersistent) : ""); - const std::string gapsIn = (bool)(r.gapsIn) ? std::format("\tgapsIn: {} {} {} {}\n", std::to_string(r.gapsIn.value().top), std::to_string(r.gapsIn.value().right), - std::to_string(r.gapsIn.value().bottom), std::to_string(r.gapsIn.value().left)) : - std::format("\tgapsIn: \n"); - const std::string gapsOut = (bool)(r.gapsOut) ? std::format("\tgapsOut: {} {} {} {}\n", std::to_string(r.gapsOut.value().top), std::to_string(r.gapsOut.value().right), - std::to_string(r.gapsOut.value().bottom), std::to_string(r.gapsOut.value().left)) : - std::format("\tgapsOut: \n"); - const std::string borderSize = std::format("\tborderSize: {}\n", (bool)(r.borderSize) ? std::to_string(r.borderSize.value()) : ""); - const std::string border = std::format("\tborder: {}\n", (bool)(r.noBorder) ? boolToString(!r.noBorder.value()) : ""); - const std::string rounding = std::format("\trounding: {}\n", (bool)(r.noRounding) ? boolToString(!r.noRounding.value()) : ""); - const std::string decorate = std::format("\tdecorate: {}\n", (bool)(r.decorate) ? boolToString(r.decorate.value()) : ""); - const std::string shadow = std::format("\tshadow: {}\n", (bool)(r.noShadow) ? boolToString(!r.noShadow.value()) : ""); + const std::string monitor = std::format("\tmonitor: {}\n", r.monitor.empty() ? "" : escapeJSONStrings(r.monitor)); + const std::string default_ = std::format("\tdefault: {}\n", (bool)(r.isDefault) ? boolToString(r.isDefault) : ""); + const std::string persistent = std::format("\tpersistent: {}\n", (bool)(r.isPersistent) ? boolToString(r.isPersistent) : ""); + const std::string gapsIn = (bool)(r.gapsIn) ? std::format("\tgapsIn: {} {} {} {}\n", std::to_string(r.gapsIn.value().top), std::to_string(r.gapsIn.value().right), + std::to_string(r.gapsIn.value().bottom), std::to_string(r.gapsIn.value().left)) : + std::format("\tgapsIn: \n"); + const std::string gapsOut = (bool)(r.gapsOut) ? std::format("\tgapsOut: {} {} {} {}\n", std::to_string(r.gapsOut.value().top), std::to_string(r.gapsOut.value().right), + std::to_string(r.gapsOut.value().bottom), std::to_string(r.gapsOut.value().left)) : + std::format("\tgapsOut: \n"); + const std::string borderSize = std::format("\tborderSize: {}\n", (bool)(r.borderSize) ? std::to_string(r.borderSize.value()) : ""); + const std::string border = std::format("\tborder: {}\n", (bool)(r.noBorder) ? boolToString(!r.noBorder.value()) : ""); + const std::string rounding = std::format("\trounding: {}\n", (bool)(r.noRounding) ? boolToString(!r.noRounding.value()) : ""); + const std::string decorate = std::format("\tdecorate: {}\n", (bool)(r.decorate) ? boolToString(r.decorate.value()) : ""); + const std::string shadow = std::format("\tshadow: {}\n", (bool)(r.noShadow) ? boolToString(!r.noShadow.value()) : ""); + const std::string defaultName = std::format("\tdefaultName: {}\n", r.defaultName.value_or("")); - std::string result = std::format("Workspace rule {}:\n{}{}{}{}{}{}{}{}{}{}\n", escapeJSONStrings(r.workspaceString), monitor, default_, persistent, gapsIn, gapsOut, - borderSize, border, rounding, decorate, shadow); + std::string result = std::format("Workspace rule {}:\n{}{}{}{}{}{}{}{}{}{}{}\n", escapeJSONStrings(r.workspaceString), monitor, default_, persistent, gapsIn, gapsOut, + borderSize, border, rounding, decorate, shadow, defaultName); return result; } @@ -381,7 +377,7 @@ std::string workspacesRequest(eHyprCtlOutputFormat format, std::string request) if (format == eHyprCtlOutputFormat::FORMAT_JSON) { result += "["; - for (auto& w : g_pCompositor->m_vWorkspaces) { + for (auto const& w : g_pCompositor->m_vWorkspaces) { result += CHyprCtl::getWorkspaceData(w, format); result += ","; } @@ -389,7 +385,7 @@ std::string workspacesRequest(eHyprCtlOutputFormat format, std::string request) trimTrailingComma(result); result += "]"; } else { - for (auto& w : g_pCompositor->m_vWorkspaces) { + for (auto const& w : g_pCompositor->m_vWorkspaces) { result += CHyprCtl::getWorkspaceData(w, format); } } @@ -401,7 +397,7 @@ std::string workspaceRulesRequest(eHyprCtlOutputFormat format, std::string reque std::string result = ""; if (format == eHyprCtlOutputFormat::FORMAT_JSON) { result += "["; - for (auto& r : g_pConfigManager->getAllWorkspaceRules()) { + for (auto const& r : g_pConfigManager->getAllWorkspaceRules()) { result += getWorkspaceRuleData(r, format); result += ","; } @@ -409,7 +405,7 @@ std::string workspaceRulesRequest(eHyprCtlOutputFormat format, std::string reque trimTrailingComma(result); result += "]"; } else { - for (auto& r : g_pConfigManager->getAllWorkspaceRules()) { + for (auto const& r : g_pConfigManager->getAllWorkspaceRules()) { result += getWorkspaceRuleData(r, format); } } @@ -437,7 +433,7 @@ std::string layersRequest(eHyprCtlOutputFormat format, std::string request) { if (format == eHyprCtlOutputFormat::FORMAT_JSON) { result += "{\n"; - for (auto& mon : g_pCompositor->m_vMonitors) { + for (auto const& mon : g_pCompositor->m_vMonitors) { result += std::format( R"#("{}": {{ "levels": {{ @@ -445,13 +441,13 @@ std::string layersRequest(eHyprCtlOutputFormat format, std::string request) { escapeJSONStrings(mon->szName)); int layerLevel = 0; - for (auto& level : mon->m_aLayerSurfaceLayers) { + for (auto const& level : mon->m_aLayerSurfaceLayers) { result += std::format( R"#( "{}": [ )#", layerLevel); - for (auto& layer : level) { + for (auto const& layer : level) { result += std::format( R"#( {{ "address": "0x{:x}", @@ -484,14 +480,14 @@ std::string layersRequest(eHyprCtlOutputFormat format, std::string request) { result += "\n}\n"; } else { - for (auto& mon : g_pCompositor->m_vMonitors) { + for (auto const& mon : g_pCompositor->m_vMonitors) { result += std::format("Monitor {}:\n", mon->szName); int layerLevel = 0; static const std::array levelNames = {"background", "bottom", "top", "overlay"}; - for (auto& level : mon->m_aLayerSurfaceLayers) { + for (auto const& level : mon->m_aLayerSurfaceLayers) { result += std::format("\tLayer level {} ({}):\n", layerLevel, levelNames[layerLevel]); - for (auto& layer : level) { + for (auto const& layer : level) { result += std::format("\t\tLayer {:x}: xywh: {} {} {} {}, namespace: {}\n", (uintptr_t)layer.get(), layer->geometry.x, layer->geometry.y, layer->geometry.width, layer->geometry.height, layer->szNamespace); } @@ -510,7 +506,7 @@ std::string layoutsRequest(eHyprCtlOutputFormat format, std::string request) { if (format == eHyprCtlOutputFormat::FORMAT_JSON) { result += "["; - for (auto& m : g_pLayoutManager->getAllLayoutNames()) { + for (auto const& m : g_pLayoutManager->getAllLayoutNames()) { result += std::format( R"#( "{}",)#", @@ -520,7 +516,7 @@ std::string layoutsRequest(eHyprCtlOutputFormat format, std::string request) { result += "\n]\n"; } else { - for (auto& m : g_pLayoutManager->getAllLayoutNames()) { + for (auto const& m : g_pLayoutManager->getAllLayoutNames()) { result += std::format("{}\n", m); } } @@ -557,7 +553,7 @@ std::string devicesRequest(eHyprCtlOutputFormat format, std::string request) { result += "{\n"; result += "\"mice\": [\n"; - for (auto& m : g_pInputManager->m_vPointers) { + for (auto const& m : g_pInputManager->m_vPointers) { result += std::format( R"#( {{ "address": "0x{:x}", @@ -572,7 +568,7 @@ std::string devicesRequest(eHyprCtlOutputFormat format, std::string request) { result += "\n],\n"; result += "\"keyboards\": [\n"; - for (auto& k : g_pInputManager->m_vKeyboards) { + for (auto const& k : g_pInputManager->m_vKeyboards) { const auto KM = k->getActiveLayout(); result += std::format( R"#( {{ @@ -596,7 +592,7 @@ std::string devicesRequest(eHyprCtlOutputFormat format, std::string request) { result += "\"tablets\": [\n"; - for (auto& d : g_pInputManager->m_vTabletPads) { + for (auto const& d : g_pInputManager->m_vTabletPads) { result += std::format( R"#( {{ "address": "0x{:x}", @@ -609,7 +605,7 @@ std::string devicesRequest(eHyprCtlOutputFormat format, std::string request) { (uintptr_t)d.get(), (uintptr_t)d->parent.get(), escapeJSONStrings(d->parent ? d->parent->hlName : "")); } - for (auto& d : g_pInputManager->m_vTablets) { + for (auto const& d : g_pInputManager->m_vTablets) { result += std::format( R"#( {{ "address": "0x{:x}", @@ -618,7 +614,7 @@ std::string devicesRequest(eHyprCtlOutputFormat format, std::string request) { (uintptr_t)d.get(), escapeJSONStrings(d->hlName)); } - for (auto& d : g_pInputManager->m_vTabletTools) { + for (auto const& d : g_pInputManager->m_vTabletTools) { result += std::format( R"#( {{ "address": "0x{:x}", @@ -632,7 +628,7 @@ std::string devicesRequest(eHyprCtlOutputFormat format, std::string request) { result += "\"touch\": [\n"; - for (auto& d : g_pInputManager->m_vTouches) { + for (auto const& d : g_pInputManager->m_vTouches) { result += std::format( R"#( {{ "address": "0x{:x}", @@ -646,7 +642,7 @@ std::string devicesRequest(eHyprCtlOutputFormat format, std::string request) { result += "\"switches\": [\n"; - for (auto& d : g_pInputManager->m_lSwitches) { + for (auto const& d : g_pInputManager->m_lSwitches) { result += std::format( R"#( {{ "address": "0x{:x}", @@ -663,14 +659,14 @@ std::string devicesRequest(eHyprCtlOutputFormat format, std::string request) { } else { result += "mice:\n"; - for (auto& m : g_pInputManager->m_vPointers) { + for (auto const& m : g_pInputManager->m_vPointers) { result += std::format("\tMouse at {:x}:\n\t\t{}\n\t\t\tdefault speed: {:.5f}\n", (uintptr_t)m.get(), m->hlName, (m->aq() && m->aq()->getLibinputHandle() ? libinput_device_config_accel_get_default_speed(m->aq()->getLibinputHandle()) : 0.f)); } result += "\n\nKeyboards:\n"; - for (auto& k : g_pInputManager->m_vKeyboards) { + for (auto const& k : g_pInputManager->m_vKeyboards) { const auto KM = k->getActiveLayout(); result += std::format("\tKeyboard at {:x}:\n\t\t{}\n\t\t\trules: r \"{}\", m \"{}\", l \"{}\", v \"{}\", o \"{}\"\n\t\t\tactive keymap: {}\n\t\t\tmain: {}\n", (uintptr_t)k.get(), k->hlName, k->currentRules.rules, k->currentRules.model, k->currentRules.layout, k->currentRules.variant, @@ -679,27 +675,27 @@ std::string devicesRequest(eHyprCtlOutputFormat format, std::string request) { result += "\n\nTablets:\n"; - for (auto& d : g_pInputManager->m_vTabletPads) { + for (auto const& d : g_pInputManager->m_vTabletPads) { result += std::format("\tTablet Pad at {:x} (belongs to {:x} -> {})\n", (uintptr_t)d.get(), (uintptr_t)d->parent.get(), d->parent ? d->parent->hlName : ""); } - for (auto& d : g_pInputManager->m_vTablets) { + for (auto const& d : g_pInputManager->m_vTablets) { result += std::format("\tTablet at {:x}:\n\t\t{}\n\t\t\tsize: {}x{}mm\n", (uintptr_t)d.get(), d->hlName, d->aq()->physicalSize.x, d->aq()->physicalSize.y); } - for (auto& d : g_pInputManager->m_vTabletTools) { + for (auto const& d : g_pInputManager->m_vTabletTools) { result += std::format("\tTablet Tool at {:x}\n", (uintptr_t)d.get()); } result += "\n\nTouch:\n"; - for (auto& d : g_pInputManager->m_vTouches) { + for (auto const& d : g_pInputManager->m_vTouches) { result += std::format("\tTouch Device at {:x}:\n\t\t{}\n", (uintptr_t)d.get(), d->hlName); } result += "\n\nSwitches:\n"; - for (auto& d : g_pInputManager->m_lSwitches) { + for (auto const& d : g_pInputManager->m_lSwitches) { result += std::format("\tSwitch Device at {:x}:\n\t\t{}\n", (uintptr_t)&d, d.pDevice ? d.pDevice->getName() : ""); } } @@ -712,21 +708,21 @@ std::string animationsRequest(eHyprCtlOutputFormat format, std::string request) if (format == eHyprCtlOutputFormat::FORMAT_NORMAL) { ret += "animations:\n"; - for (auto& ac : g_pConfigManager->getAnimationConfig()) { + for (auto const& ac : g_pConfigManager->getAnimationConfig()) { ret += std::format("\n\tname: {}\n\t\toverriden: {}\n\t\tbezier: {}\n\t\tenabled: {}\n\t\tspeed: {:.2f}\n\t\tstyle: {}\n", ac.first, (int)ac.second.overridden, ac.second.internalBezier, ac.second.internalEnabled, ac.second.internalSpeed, ac.second.internalStyle); } ret += "beziers:\n"; - for (auto& bz : g_pAnimationManager->getAllBeziers()) { + for (auto const& bz : g_pAnimationManager->getAllBeziers()) { ret += std::format("\n\tname: {}\n", bz.first); } } else { // json ret += "[["; - for (auto& ac : g_pConfigManager->getAnimationConfig()) { + for (auto const& ac : g_pConfigManager->getAnimationConfig()) { ret += std::format(R"#( {{ "name": "{}", @@ -744,7 +740,7 @@ std::string animationsRequest(eHyprCtlOutputFormat format, std::string request) ret += ",\n["; - for (auto& bz : g_pAnimationManager->getAllBeziers()) { + for (auto const& bz : g_pAnimationManager->getAllBeziers()) { ret += std::format(R"#( {{ "name": "{}" @@ -778,11 +774,11 @@ std::string globalShortcutsRequest(eHyprCtlOutputFormat format, std::string requ std::string ret = ""; const auto SHORTCUTS = PROTO::globalShortcuts->getAllShortcuts(); if (format == eHyprCtlOutputFormat::FORMAT_NORMAL) { - for (auto& sh : SHORTCUTS) + for (auto const& sh : SHORTCUTS) ret += std::format("{}:{} -> {}\n", sh.appid, sh.id, sh.description); } else { ret += "["; - for (auto& sh : SHORTCUTS) { + for (auto const& sh : SHORTCUTS) { ret += std::format(R"#( {{ "name": "{}", @@ -800,7 +796,7 @@ std::string globalShortcutsRequest(eHyprCtlOutputFormat format, std::string requ std::string bindsRequest(eHyprCtlOutputFormat format, std::string request) { std::string ret = ""; if (format == eHyprCtlOutputFormat::FORMAT_NORMAL) { - for (auto& kb : g_pKeybindManager->m_lKeybinds) { + for (auto const& kb : g_pKeybindManager->m_lKeybinds) { ret += "bind"; if (kb.locked) ret += "l"; @@ -821,7 +817,7 @@ std::string bindsRequest(eHyprCtlOutputFormat format, std::string request) { } else { // json ret += "["; - for (auto& kb : g_pKeybindManager->m_lKeybinds) { + for (auto const& kb : g_pKeybindManager->m_lKeybinds) { ret += std::format( R"#( {{ @@ -857,37 +853,47 @@ std::string versionRequest(eHyprCtlOutputFormat format, std::string request) { std::replace(commitMsg.begin(), commitMsg.end(), '#', ' '); if (format == eHyprCtlOutputFormat::FORMAT_NORMAL) { - std::string result = "Hyprland, built from branch " + std::string(GIT_BRANCH) + " at commit " + GIT_COMMIT_HASH + " " + GIT_DIRTY + " (" + commitMsg + - ").\nDate: " + GIT_COMMIT_DATE + "\nTag: " + GIT_TAG + ", commits: " + GIT_COMMITS + "\n\nflags: (if any)\n"; + std::string result = std::format("Hyprland {} built from branch {} at commit {} {} ({}).\n" + "Date: {}\n" + "Tag: {}, commits: {}\n" + "built against aquamarine {}\n\n\n", + HYPRLAND_VERSION, GIT_BRANCH, GIT_COMMIT_HASH, GIT_DIRTY, commitMsg, GIT_COMMIT_DATE, GIT_TAG, GIT_COMMITS, AQUAMARINE_VERSION); +#if (!defined(LEGACY_RENDERER) && !defined(ISDEBUG) && !defined(NO_XWAYLAND)) + result += "no flags were set\n"; +#else + result += "flags set:\n"; #ifdef LEGACY_RENDERER result += "legacyrenderer\n"; #endif -#ifndef ISDEBUG +#ifdef ISDEBUG result += "debug\n"; #endif #ifdef NO_XWAYLAND result += "no xwayland\n"; #endif - +#endif return result; } else { std::string result = std::format( R"#({{ "branch": "{}", "commit": "{}", + "version": "{}", "dirty": {}, "commit_message": "{}", "commit_date": "{}", "tag": "{}", "commits": "{}", + "buildAquamarine": "{}", "flags": [)#", - GIT_BRANCH, GIT_COMMIT_HASH, (strcmp(GIT_DIRTY, "dirty") == 0 ? "true" : "false"), escapeJSONStrings(commitMsg), GIT_COMMIT_DATE, GIT_TAG, GIT_COMMITS); + GIT_BRANCH, GIT_COMMIT_HASH, HYPRLAND_VERSION, (strcmp(GIT_DIRTY, "dirty") == 0 ? "true" : "false"), escapeJSONStrings(commitMsg), GIT_COMMIT_DATE, GIT_TAG, + GIT_COMMITS, AQUAMARINE_VERSION); #ifdef LEGACY_RENDERER result += "\"legacyrenderer\","; #endif -#ifndef ISDEBUG +#ifdef ISDEBUG result += "\"debug\","; #endif #ifdef NO_XWAYLAND @@ -935,11 +941,14 @@ std::string systemInfoRequest(eHyprCtlOutputFormat format, std::string request) result += "os-release: " + execAndGet("cat /etc/os-release") + "\n\n"; result += "plugins:\n"; - for (auto& pl : g_pPluginSystem->getAllPlugins()) { - result += std::format(" {} by {} ver {}\n", pl->name, pl->author, pl->version); - } + if (g_pPluginSystem) { + for (auto const& pl : g_pPluginSystem->getAllPlugins()) { + result += std::format(" {} by {} ver {}\n", pl->name, pl->author, pl->version); + } + } else + result += "\tunknown: not runtime\n"; - if (g_pHyprCtl->m_sCurrentRequestParams.sysInfoConfig) { + if (g_pHyprCtl && g_pHyprCtl->m_sCurrentRequestParams.sysInfoConfig) { result += "\n======Config-Start======\n"; result += g_pConfigManager->getConfigString(); result += "\n======Config-End========\n"; @@ -962,20 +971,34 @@ std::string dispatchRequest(eHyprCtlOutputFormat format, std::string in) { if (DISPATCHER == g_pKeybindManager->m_mDispatchers.end()) return "Invalid dispatcher"; - DISPATCHER->second(DISPATCHARG); + SDispatchResult res = DISPATCHER->second(DISPATCHARG); - Debug::log(LOG, "Hyprctl: dispatcher {} : {}", DISPATCHSTR, DISPATCHARG); + Debug::log(LOG, "Hyprctl: dispatcher {} : {}{}", DISPATCHSTR, DISPATCHARG, res.success ? "" : " -> " + res.error); - return "ok"; + return res.success ? "ok" : res.error; } std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in) { - // get rid of the keyword keyword - in = in.substr(in.find_first_of(' ') + 1); + // Find the first space to strip the keyword keyword + auto const firstSpacePos = in.find_first_of(' '); + if (firstSpacePos == std::string::npos) // Handle the case where there's no space found (invalid input) + return "Invalid input: no space found"; - const auto COMMAND = in.substr(0, in.find_first_of(' ')); + // Strip the keyword + in = in.substr(firstSpacePos + 1); - const auto VALUE = in.substr(in.find_first_of(' ') + 1); + // Find the next space for the COMMAND and VALUE + auto const secondSpacePos = in.find_first_of(' '); + if (secondSpacePos == std::string::npos) // Handle the case where there's no second space (invalid input) + return "Invalid input: command and value not properly formatted"; + + // Extract COMMAND and VALUE + const auto COMMAND = in.substr(0, secondSpacePos); + const auto VALUE = in.substr(secondSpacePos + 1); + + // If COMMAND is empty, handle accordingly + if (COMMAND.empty()) + return "Invalid input: command is empty"; std::string retval = g_pConfigManager->parseKeyword(COMMAND, VALUE); @@ -1006,7 +1029,7 @@ std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in) { // decorations will probably need a repaint if (COMMAND.contains("decoration:") || COMMAND.contains("border") || COMMAND == "workspace" || COMMAND.contains("zoom_factor") || COMMAND == "source") { - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { g_pHyprRenderer->damageMonitor(m.get()); g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->ID); } @@ -1122,46 +1145,77 @@ std::string dispatchSetCursor(eHyprCtlOutputFormat format, std::string request) } std::string switchXKBLayoutRequest(eHyprCtlOutputFormat format, std::string request) { - CVarList vars(request, 0, ' '); + CVarList vars(request, 0, ' '); - const auto KB = vars[1]; - const auto CMD = vars[2]; + const auto KB = vars[1]; + const auto CMD = vars[2]; - // get kb - const auto PKEYBOARD = std::find_if(g_pInputManager->m_vKeyboards.begin(), g_pInputManager->m_vKeyboards.end(), - [&](const auto& other) { return other->hlName == g_pInputManager->deviceNameToInternalString(KB); }); + SP pKeyboard; - if (PKEYBOARD == g_pInputManager->m_vKeyboards.end()) - return "device not found"; + auto updateKeyboard = [](const SP KEEB, const std::string& CMD) -> std::optional { + const auto LAYOUTS = xkb_keymap_num_layouts(KEEB->xkbKeymap); + xkb_layout_index_t activeLayout = 0; + while (activeLayout < LAYOUTS) { + if (xkb_state_layout_index_is_active(KEEB->xkbState, activeLayout, XKB_STATE_LAYOUT_EFFECTIVE) == 1) + break; - const auto KEEB = *PKEYBOARD; - - const auto LAYOUTS = xkb_keymap_num_layouts(KEEB->xkbKeymap); - xkb_layout_index_t activeLayout = 0; - while (activeLayout < LAYOUTS) { - if (xkb_state_layout_index_is_active(KEEB->xkbState, activeLayout, XKB_STATE_LAYOUT_EFFECTIVE) == 1) - break; - - activeLayout++; - } - - if (CMD == "next") - KEEB->updateModifiers(KEEB->modifiersState.depressed, KEEB->modifiersState.latched, KEEB->modifiersState.locked, activeLayout > LAYOUTS ? 0 : activeLayout + 1); - else if (CMD == "prev") - KEEB->updateModifiers(KEEB->modifiersState.depressed, KEEB->modifiersState.latched, KEEB->modifiersState.locked, activeLayout == 0 ? LAYOUTS - 1 : activeLayout - 1); - else { - int requestedLayout = 0; - try { - requestedLayout = std::stoi(CMD); - } catch (std::exception& e) { return "invalid arg 2"; } - - if (requestedLayout < 0 || (uint64_t)requestedLayout > LAYOUTS - 1) { - return "layout idx out of range of " + std::to_string(LAYOUTS); + activeLayout++; } - KEEB->updateModifiers(KEEB->modifiersState.depressed, KEEB->modifiersState.latched, KEEB->modifiersState.locked, requestedLayout); + if (CMD == "next") + KEEB->updateModifiers(KEEB->modifiersState.depressed, KEEB->modifiersState.latched, KEEB->modifiersState.locked, activeLayout > LAYOUTS ? 0 : activeLayout + 1); + else if (CMD == "prev") + KEEB->updateModifiers(KEEB->modifiersState.depressed, KEEB->modifiersState.latched, KEEB->modifiersState.locked, activeLayout == 0 ? LAYOUTS - 1 : activeLayout - 1); + else { + int requestedLayout = 0; + try { + requestedLayout = std::stoi(CMD); + } catch (std::exception& e) { return "invalid arg 2"; } + + if (requestedLayout < 0 || (uint64_t)requestedLayout > LAYOUTS - 1) { + return "layout idx out of range of " + std::to_string(LAYOUTS); + } + + KEEB->updateModifiers(KEEB->modifiersState.depressed, KEEB->modifiersState.latched, KEEB->modifiersState.locked, requestedLayout); + } + + return std::nullopt; + }; + + if (KB == "main" || KB == "active" || KB == "current") { + for (auto const& k : g_pInputManager->m_vKeyboards) { + if (!k->active) + continue; + + pKeyboard = k; + break; + } + } else if (KB == "all") { + std::string result = ""; + for (auto const& k : g_pInputManager->m_vKeyboards) { + auto res = updateKeyboard(k, CMD); + if (res.has_value()) + result += *res + "\n"; + } + return result.empty() ? "ok" : result; + } else { + auto k = std::find_if(g_pInputManager->m_vKeyboards.begin(), g_pInputManager->m_vKeyboards.end(), + [&](const auto& other) { return other->hlName == g_pInputManager->deviceNameToInternalString(KB); }); + + if (k == g_pInputManager->m_vKeyboards.end()) + return "device not found"; + + pKeyboard = *k; } + if (!pKeyboard) + return "no device"; + + auto result = updateKeyboard(pKeyboard, CMD); + + if (result.has_value()) + return *result; + return "ok"; } @@ -1286,7 +1340,7 @@ std::string dispatchSetProp(eHyprCtlOutputFormat format, std::string request) { g_pCompositor->focusWindow(PLASTWINDOW); } - for (auto& m : g_pCompositor->m_vMonitors) + for (auto const& m : g_pCompositor->m_vMonitors) g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->ID); return "ok"; @@ -1359,7 +1413,7 @@ std::string decorationRequest(eHyprCtlOutputFormat format, std::string request) std::string result = ""; if (format == eHyprCtlOutputFormat::FORMAT_JSON) { result += "["; - for (auto& wd : PWINDOW->m_dWindowDecorations) { + for (auto const& wd : PWINDOW->m_dWindowDecorations) { result += "{\n\"decorationName\": \"" + wd->getDisplayName() + "\",\n\"priority\": " + std::to_string(wd->getPositioningInfo().priority) + "\n},"; } @@ -1367,7 +1421,7 @@ std::string decorationRequest(eHyprCtlOutputFormat format, std::string request) result += "]"; } else { result = +"Decoration\tPriority\n"; - for (auto& wd : PWINDOW->m_dWindowDecorations) { + for (auto const& wd : PWINDOW->m_dWindowDecorations) { result += wd->getDisplayName() + "\t" + std::to_string(wd->getPositioningInfo().priority) + "\n"; } } @@ -1386,7 +1440,7 @@ std::string dispatchOutput(eHyprCtlOutputFormat format, std::string request) { bool added = false; if (!vars[3].empty()) { - for (auto& m : g_pCompositor->m_vRealMonitors) { + for (auto const& m : g_pCompositor->m_vRealMonitors) { if (m->szName == vars[3]) return "Name already taken"; } @@ -1396,7 +1450,7 @@ std::string dispatchOutput(eHyprCtlOutputFormat format, std::string request) { if (g_pCompositor->getMonitorFromName(vars[3])) return "A real monitor already uses that name."; - for (auto& impl : g_pCompositor->m_pAqBackend->getImplementations() | std::views::reverse) { + for (auto const& impl : g_pCompositor->m_pAqBackend->getImplementations() | std::views::reverse) { auto type = impl->type(); if (type == Aquamarine::AQ_BACKEND_HEADLESS && (vars[2] == "headless" || vars[2] == "auto")) { @@ -1464,7 +1518,7 @@ std::string dispatchPlugin(eHyprCtlOutputFormat format, std::string request) { return "no plugins loaded"; std::string list = ""; - for (auto& p : PLUGINS) { + for (auto const& p : PLUGINS) { list += std::format("\nPlugin {} by {}:\n\tHandle: {:x}\n\tVersion: {}\n\tDescription: {}\n", p->name, p->author, (uintptr_t)p->m_pHandle, p->version, p->description); } @@ -1561,6 +1615,29 @@ std::string getIsLocked(eHyprCtlOutputFormat format, std::string request) { return lockedStr; } +std::string getDescriptions(eHyprCtlOutputFormat format, std::string request) { + std::string json = "{"; + const auto& DESCS = g_pConfigManager->getAllDescriptions(); + + for (const auto& d : DESCS) { + json += d.jsonify() + ",\n"; + } + + json.pop_back(); + json.pop_back(); + + json += "}\n"; + return json; +} + +std::string submapRequest(eHyprCtlOutputFormat format, std::string request) { + std::string submap = g_pKeybindManager->getCurrentSubmap(); + if (submap.empty()) + submap = "default"; + + return format == FORMAT_JSON ? std::format("{{\"{}\"}}\n", escapeJSONStrings(submap)) : (submap + "\n"); +} + CHyprCtl::CHyprCtl() { registerCommand(SHyprCtlCommand{"workspaces", true, workspacesRequest}); registerCommand(SHyprCtlCommand{"workspacerules", true, workspaceRulesRequest}); @@ -1581,6 +1658,8 @@ CHyprCtl::CHyprCtl() { registerCommand(SHyprCtlCommand{"layouts", true, layoutsRequest}); registerCommand(SHyprCtlCommand{"configerrors", true, configErrorsRequest}); registerCommand(SHyprCtlCommand{"locked", true, getIsLocked}); + registerCommand(SHyprCtlCommand{"descriptions", true, getDescriptions}); + registerCommand(SHyprCtlCommand{"submap", true, submapRequest}); registerCommand(SHyprCtlCommand{"monitors", false, monitorsRequest}); registerCommand(SHyprCtlCommand{"reload", false, reloadRequest}); @@ -1657,7 +1736,7 @@ std::string CHyprCtl::getReply(std::string request) { std::string result = ""; // parse exact cmds first, then non-exact. - for (auto& cmd : m_vCommands) { + for (auto const& cmd : m_vCommands) { if (!cmd->exact) continue; @@ -1668,7 +1747,7 @@ std::string CHyprCtl::getReply(std::string request) { } if (result.empty()) - for (auto& cmd : m_vCommands) { + for (auto const& cmd : m_vCommands) { if (cmd->exact) continue; @@ -1699,7 +1778,7 @@ std::string CHyprCtl::getReply(std::string request) { rd.blurFBDirty = true; } - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { g_pHyprRenderer->damageMonitor(m.get()); g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->ID); } diff --git a/src/debug/HyprCtl.hpp b/src/debug/HyprCtl.hpp index cbacd7cb..c1bf51ca 100644 --- a/src/debug/HyprCtl.hpp +++ b/src/debug/HyprCtl.hpp @@ -5,6 +5,10 @@ #include "../helpers/MiscFunctions.hpp" #include +// exposed for main.cpp +std::string systemInfoRequest(eHyprCtlOutputFormat format, std::string request); +std::string versionRequest(eHyprCtlOutputFormat format, std::string request); + class CHyprCtl { public: CHyprCtl(); diff --git a/src/debug/HyprDebugOverlay.cpp b/src/debug/HyprDebugOverlay.cpp index 889be8ea..2da64e63 100644 --- a/src/debug/HyprDebugOverlay.cpp +++ b/src/debug/HyprDebugOverlay.cpp @@ -7,8 +7,8 @@ CHyprDebugOverlay::CHyprDebugOverlay() { m_pTexture = makeShared(); } -void CHyprMonitorDebugOverlay::renderData(CMonitor* pMonitor, float µs) { - m_dLastRenderTimes.push_back(µs / 1000.f); +void CHyprMonitorDebugOverlay::renderData(CMonitor* pMonitor, float durationUs) { + m_dLastRenderTimes.push_back(durationUs / 1000.f); if (m_dLastRenderTimes.size() > (long unsigned int)pMonitor->refreshRate) m_dLastRenderTimes.pop_front(); @@ -17,8 +17,8 @@ void CHyprMonitorDebugOverlay::renderData(CMonitor* pMonitor, float µs) { m_pMonitor = pMonitor; } -void CHyprMonitorDebugOverlay::renderDataNoOverlay(CMonitor* pMonitor, float µs) { - m_dLastRenderTimesNoOverlay.push_back(µs / 1000.f); +void CHyprMonitorDebugOverlay::renderDataNoOverlay(CMonitor* pMonitor, float durationUs) { + m_dLastRenderTimesNoOverlay.push_back(durationUs / 1000.f); if (m_dLastRenderTimesNoOverlay.size() > (long unsigned int)pMonitor->refreshRate) m_dLastRenderTimesNoOverlay.pop_front(); @@ -57,7 +57,7 @@ int CHyprMonitorDebugOverlay::draw(int offset) { float avgFrametime = 0; float maxFrametime = 0; float minFrametime = 9999; - for (auto& ft : m_dLastFrametimes) { + for (auto const& ft : m_dLastFrametimes) { if (ft > maxFrametime) maxFrametime = ft; if (ft < minFrametime) @@ -70,7 +70,7 @@ int CHyprMonitorDebugOverlay::draw(int offset) { float avgRenderTime = 0; float maxRenderTime = 0; float minRenderTime = 9999; - for (auto& rt : m_dLastRenderTimes) { + for (auto const& rt : m_dLastRenderTimes) { if (rt > maxRenderTime) maxRenderTime = rt; if (rt < minRenderTime) @@ -83,7 +83,7 @@ int CHyprMonitorDebugOverlay::draw(int offset) { float avgRenderTimeNoOverlay = 0; float maxRenderTimeNoOverlay = 0; float minRenderTimeNoOverlay = 9999; - for (auto& rt : m_dLastRenderTimesNoOverlay) { + for (auto const& rt : m_dLastRenderTimesNoOverlay) { if (rt > maxRenderTimeNoOverlay) maxRenderTimeNoOverlay = rt; if (rt < minRenderTimeNoOverlay) @@ -96,7 +96,7 @@ int CHyprMonitorDebugOverlay::draw(int offset) { float avgAnimMgrTick = 0; float maxAnimMgrTick = 0; float minAnimMgrTick = 9999; - for (auto& at : m_dLastAnimationTicks) { + for (auto const& at : m_dLastAnimationTicks) { if (at > maxAnimMgrTick) maxAnimMgrTick = at; if (at < minAnimMgrTick) @@ -188,12 +188,12 @@ int CHyprMonitorDebugOverlay::draw(int offset) { return posY - offset; } -void CHyprDebugOverlay::renderData(CMonitor* pMonitor, float µs) { - m_mMonitorOverlays[pMonitor].renderData(pMonitor, µs); +void CHyprDebugOverlay::renderData(CMonitor* pMonitor, float durationUs) { + m_mMonitorOverlays[pMonitor].renderData(pMonitor, durationUs); } -void CHyprDebugOverlay::renderDataNoOverlay(CMonitor* pMonitor, float µs) { - m_mMonitorOverlays[pMonitor].renderDataNoOverlay(pMonitor, µs); +void CHyprDebugOverlay::renderDataNoOverlay(CMonitor* pMonitor, float durationUs) { + m_mMonitorOverlays[pMonitor].renderDataNoOverlay(pMonitor, durationUs); } void CHyprDebugOverlay::frameData(CMonitor* pMonitor) { @@ -217,7 +217,7 @@ void CHyprDebugOverlay::draw() { // draw the things int offsetY = 0; - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { offsetY += m_mMonitorOverlays[m.get()].draw(offsetY); offsetY += 5; // for padding between mons } diff --git a/src/debug/HyprDebugOverlay.hpp b/src/debug/HyprDebugOverlay.hpp index a6063ee9..e7742b35 100644 --- a/src/debug/HyprDebugOverlay.hpp +++ b/src/debug/HyprDebugOverlay.hpp @@ -13,8 +13,8 @@ class CHyprMonitorDebugOverlay { public: int draw(int offset); - void renderData(CMonitor* pMonitor, float µs); - void renderDataNoOverlay(CMonitor* pMonitor, float µs); + void renderData(CMonitor* pMonitor, float durationUs); + void renderDataNoOverlay(CMonitor* pMonitor, float durationUs); void frameData(CMonitor* pMonitor); private: @@ -33,8 +33,8 @@ class CHyprDebugOverlay { public: CHyprDebugOverlay(); void draw(); - void renderData(CMonitor*, float µs); - void renderDataNoOverlay(CMonitor*, float µs); + void renderData(CMonitor*, float durationUs); + void renderDataNoOverlay(CMonitor*, float durationUs); void frameData(CMonitor*); private: diff --git a/src/debug/HyprNotificationOverlay.cpp b/src/debug/HyprNotificationOverlay.cpp index 3f8bb579..16f80ab6 100644 --- a/src/debug/HyprNotificationOverlay.cpp +++ b/src/debug/HyprNotificationOverlay.cpp @@ -37,14 +37,14 @@ CHyprNotificationOverlay::~CHyprNotificationOverlay() { void CHyprNotificationOverlay::addNotification(const std::string& text, const CColor& color, const float timeMs, const eIcons icon, const float fontSize) { const auto PNOTIF = m_dNotifications.emplace_back(std::make_unique()).get(); - PNOTIF->text = text; + PNOTIF->text = icon != eIcons::ICON_NONE ? " " + text /* tiny bit of padding otherwise icon touches text */ : text; PNOTIF->color = color == CColor(0) ? ICONS_COLORS[icon] : color; PNOTIF->started.reset(); PNOTIF->timeMs = timeMs; PNOTIF->icon = icon; PNOTIF->fontSize = fontSize; - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { g_pCompositor->scheduleFrameForMonitor(m.get()); } } @@ -87,7 +87,7 @@ CBox CHyprNotificationOverlay::drawNotifications(CMonitor* pMonitor) { const auto iconBackendID = iconBackendFromLayout(layout); const auto PBEZIER = g_pAnimationManager->getBezier("default"); - for (auto& notif : m_dNotifications) { + for (auto const& notif : m_dNotifications) { const auto ICONPADFORNOTIF = notif->icon == ICON_NONE ? 0 : ICON_PAD; const auto FONTSIZE = std::clamp((int)(notif->fontSize * ((pMonitor->vecPixelSize.x * SCALE) / 1920.f)), 8, 40); diff --git a/src/debug/Log.cpp b/src/debug/Log.cpp index 0def77c0..5a314833 100644 --- a/src/debug/Log.cpp +++ b/src/debug/Log.cpp @@ -4,11 +4,14 @@ #include "RollingLogFollow.hpp" #include -#include +#include +#include void Debug::init(const std::string& IS) { logFile = IS + (ISDEBUG ? "/hyprlandd.log" : "/hyprland.log"); logOfs.open(logFile, std::ios::out | std::ios::app); + auto handle = logOfs.native_handle(); + fcntl(handle, F_SETFD, FD_CLOEXEC); } void Debug::close() { @@ -66,5 +69,5 @@ void Debug::log(LogLevel level, std::string str) { // log it to the stdout too. if (!disableStdout) - std::cout << ((coloredLogs && !**coloredLogs) ? str : coloredStr) << "\n"; + std::println("{}", ((coloredLogs && !**coloredLogs) ? str : coloredStr)); } diff --git a/src/debug/Log.hpp b/src/debug/Log.hpp index 617f451a..d3190d4f 100644 --- a/src/debug/Log.hpp +++ b/src/debug/Log.hpp @@ -55,8 +55,9 @@ namespace Debug { // print date and time to the ofs if (disableTime && !**disableTime) { #ifndef _LIBCPP_VERSION - const auto zt = std::chrono::zoned_time{std::chrono::current_zone(), std::chrono::system_clock::now()}; - const auto hms = std::chrono::hh_mm_ss{zt.get_local_time() - std::chrono::floor(zt.get_local_time())}; + static auto current_zone = std::chrono::current_zone(); + const auto zt = std::chrono::zoned_time{current_zone, std::chrono::system_clock::now()}; + const auto hms = std::chrono::hh_mm_ss{zt.get_local_time() - std::chrono::floor(zt.get_local_time())}; #else // TODO: current clang 17 does not support `zoned_time`, remove this once clang 19 is ready const auto hms = std::chrono::hh_mm_ss{std::chrono::system_clock::now() - std::chrono::floor(std::chrono::system_clock::now())}; @@ -72,6 +73,5 @@ namespace Debug { logMsg += std::vformat(fmt.get(), std::make_format_args(args...)); log(level, logMsg); - logMutex.unlock(); } }; diff --git a/src/defines.hpp b/src/defines.hpp index 0b2c0e0c..41ee4502 100644 --- a/src/defines.hpp +++ b/src/defines.hpp @@ -1,6 +1,5 @@ #include "includes.hpp" #include "debug/Log.hpp" -#include "helpers/WLListener.hpp" #include "helpers/Color.hpp" #include "macros.hpp" #include "desktop/DesktopTypes.hpp" diff --git a/src/desktop/LayerSurface.cpp b/src/desktop/LayerSurface.cpp index 8fd448ef..0e7e71b6 100644 --- a/src/desktop/LayerSurface.cpp +++ b/src/desktop/LayerSurface.cpp @@ -71,6 +71,12 @@ CLayerSurface::~CLayerSurface() { surface->unassign(); g_pHyprRenderer->makeEGLCurrent(); std::erase_if(g_pHyprOpenGL->m_mLayerFramebuffers, [&](const auto& other) { return other.first.expired() || other.first.lock() == self.lock(); }); + + for (auto const& mon : g_pCompositor->m_vRealMonitors) { + for (auto& lsl : mon->m_aLayerSurfaceLayers) { + std::erase_if(lsl, [this](auto& ls) { return ls.expired() || ls.get() == this; }); + } + } } void CLayerSurface::onDestroy() { @@ -360,7 +366,7 @@ void CLayerSurface::applyRules() { xray = -1; animationStyle.reset(); - for (auto& rule : g_pConfigManager->getMatchingRules(self.lock())) { + for (auto const& rule : g_pConfigManager->getMatchingRules(self.lock())) { if (rule.rule == "noanim") noAnimations = true; else if (rule.rule == "blur") @@ -388,6 +394,11 @@ void CLayerSurface::applyRules() { } else if (rule.rule.starts_with("animation")) { CVarList vars{rule.rule, 2, 's'}; animationStyle = vars[1]; + } else if (rule.rule.starts_with("order")) { + CVarList vars{rule.rule, 2, 's'}; + try { + order = std::stoi(vars[1]); + } catch (...) { Debug::log(ERR, "Invalid value passed to order"); } } } } diff --git a/src/desktop/LayerSurface.hpp b/src/desktop/LayerSurface.hpp index 056f66a8..e0f17039 100644 --- a/src/desktop/LayerSurface.hpp +++ b/src/desktop/LayerSurface.hpp @@ -42,7 +42,7 @@ class CLayerSurface { bool mapped = false; uint32_t layer = 0; - int monitorID = -1; + MONITORID monitorID = -1; bool fadingOut = false; bool readyToDelete = false; @@ -51,10 +51,11 @@ class CLayerSurface { bool forceBlur = false; bool forceBlurPopups = false; - int xray = -1; + int64_t xray = -1; bool ignoreAlpha = false; float ignoreAlphaValue = 0.f; bool dimAround = false; + int64_t order = 0; std::optional animationStyle; diff --git a/src/desktop/Popup.cpp b/src/desktop/Popup.cpp index e48b7400..9e254fa6 100644 --- a/src/desktop/Popup.cpp +++ b/src/desktop/Popup.cpp @@ -202,6 +202,13 @@ void CPopup::reposition() { m_pResource->applyPositioning(box, COORDS); } +SP CPopup::getT1Owner() { + if (m_pWindowOwner) + return m_pWindowOwner->m_pWLSurface; + else + return m_pLayerOwner->surface; +} + Vector2D CPopup::coordsRelativeToParent() { Vector2D offset; @@ -251,7 +258,7 @@ void CPopup::recheckTree() { void CPopup::recheckChildrenRecursive() { auto cpy = m_vChildren; - for (auto& c : cpy) { + for (auto const& c : cpy) { c->onCommit(true); c->recheckChildrenRecursive(); } @@ -282,14 +289,14 @@ bool CPopup::visible() { } void CPopup::bfHelper(std::vector nodes, std::function fn, void* data) { - for (auto& n : nodes) { + for (auto const& n : nodes) { fn(n, data); } std::vector nodes2; - for (auto& n : nodes) { - for (auto& c : n->m_vChildren) { + for (auto const& n : nodes) { + for (auto const& c : n->m_vChildren) { nodes2.push_back(c.get()); } } @@ -308,7 +315,7 @@ CPopup* CPopup::at(const Vector2D& globalCoords, bool allowsInput) { std::vector popups; breadthfirst([](CPopup* popup, void* data) { ((std::vector*)data)->push_back(popup); }, &popups); - for (auto& p : popups | std::views::reverse) { + for (auto const& p : popups | std::views::reverse) { if (!p->m_pResource) continue; diff --git a/src/desktop/Popup.hpp b/src/desktop/Popup.hpp index eea3fb84..04996612 100644 --- a/src/desktop/Popup.hpp +++ b/src/desktop/Popup.hpp @@ -18,21 +18,22 @@ class CPopup { ~CPopup(); - Vector2D coordsRelativeToParent(); - Vector2D coordsGlobal(); + SP getT1Owner(); + Vector2D coordsRelativeToParent(); + Vector2D coordsGlobal(); - Vector2D size(); + Vector2D size(); - void onNewPopup(SP popup); - void onDestroy(); - void onMap(); - void onUnmap(); - void onCommit(bool ignoreSiblings = false); - void onReposition(); + void onNewPopup(SP popup); + void onDestroy(); + void onMap(); + void onUnmap(); + void onCommit(bool ignoreSiblings = false); + void onReposition(); - void recheckTree(); + void recheckTree(); - bool visible(); + bool visible(); // will also loop over this node void breadthfirst(std::function fn, void* data); diff --git a/src/desktop/Subsurface.cpp b/src/desktop/Subsurface.cpp index 71ee16f0..893411bd 100644 --- a/src/desktop/Subsurface.cpp +++ b/src/desktop/Subsurface.cpp @@ -30,13 +30,7 @@ CSubsurface::CSubsurface(SP pSubsurface, CPopup* pOwner) } CSubsurface::~CSubsurface() { - hyprListener_newSubsurface.removeCallback(); - - if (!m_pSubsurface) - return; - - hyprListener_commitSubsurface.removeCallback(); - hyprListener_destroySubsurface.removeCallback(); + ; } void CSubsurface::initSignals() { @@ -65,7 +59,7 @@ void CSubsurface::checkSiblingDamage() { const double SCALE = m_pWindowParent.lock() && m_pWindowParent->m_bIsX11 ? 1.0 / m_pWindowParent->m_fX11SurfaceScaledBy : 1.0; - for (auto& n : m_pParent->m_vChildren) { + for (auto const& n : m_pParent->m_vChildren) { if (n.get() == this) continue; @@ -75,7 +69,7 @@ void CSubsurface::checkSiblingDamage() { } void CSubsurface::recheckDamageForSubsurfaces() { - for (auto& n : m_vChildren) { + for (auto const& n : m_vChildren) { const auto COORDS = n->coordsGlobal(); g_pHyprRenderer->damageSurface(n->m_pWLSurface->resource(), COORDS.x, COORDS.y); } @@ -183,7 +177,7 @@ Vector2D CSubsurface::coordsGlobal() { } void CSubsurface::initExistingSubsurfaces(SP pSurface) { - for (auto& s : pSurface->subsurfaces) { + for (auto const& s : pSurface->subsurfaces) { if (!s || s->surface->hlSurface /* already assigned */) continue; onNewSubsurface(s.lock()); diff --git a/src/desktop/Subsurface.hpp b/src/desktop/Subsurface.hpp index 101f4f19..7829c489 100644 --- a/src/desktop/Subsurface.hpp +++ b/src/desktop/Subsurface.hpp @@ -35,10 +35,6 @@ class CSubsurface { void recheckDamageForSubsurfaces(); private: - DYNLISTENER(destroySubsurface); - DYNLISTENER(commitSubsurface); - DYNLISTENER(newSubsurface); - struct { CHyprSignalListener destroySubsurface; CHyprSignalListener commitSubsurface; diff --git a/src/desktop/WLSurface.cpp b/src/desktop/WLSurface.cpp index 45050e35..5e88f507 100644 --- a/src/desktop/WLSurface.cpp +++ b/src/desktop/WLSurface.cpp @@ -62,7 +62,7 @@ bool CWLSurface::small() const { const auto O = m_pWindowOwner.lock(); - return O->m_vReportedSize.x > m_pResource->current.bufferSize.x + 1 || O->m_vReportedSize.y > m_pResource->current.bufferSize.y + 1; + return O->m_vReportedSize.x > m_pResource->current.size.x + 1 || O->m_vReportedSize.y > m_pResource->current.size.y + 1; } Vector2D CWLSurface::correctSmallVec() const { diff --git a/src/desktop/Window.cpp b/src/desktop/Window.cpp index 93c208cf..a393f361 100644 --- a/src/desktop/Window.cpp +++ b/src/desktop/Window.cpp @@ -19,9 +19,8 @@ using namespace Hyprutils::String; PHLWINDOW CWindow::create(SP surface) { PHLWINDOW pWindow = SP(new CWindow(surface)); - pWindow->m_pSelf = pWindow; - pWindow->m_bIsX11 = true; - pWindow->m_iX11Type = surface->overrideRedirect ? 2 : 1; + pWindow->m_pSelf = pWindow; + pWindow->m_bIsX11 = true; pWindow->m_vRealPosition.create(g_pConfigManager->getAnimationPropertyConfig("windowsIn"), pWindow, AVARDAMAGE_ENTIRE); pWindow->m_vRealSize.create(g_pConfigManager->getAnimationPropertyConfig("windowsIn"), pWindow, AVARDAMAGE_ENTIRE); @@ -31,6 +30,7 @@ PHLWINDOW CWindow::create(SP surface) { pWindow->m_fActiveInactiveAlpha.create(g_pConfigManager->getAnimationPropertyConfig("fadeSwitch"), pWindow, AVARDAMAGE_ENTIRE); pWindow->m_cRealShadowColor.create(g_pConfigManager->getAnimationPropertyConfig("fadeShadow"), pWindow, AVARDAMAGE_SHADOW); pWindow->m_fDimPercent.create(g_pConfigManager->getAnimationPropertyConfig("fadeDim"), pWindow, AVARDAMAGE_ENTIRE); + pWindow->m_fMovingToWorkspaceAlpha.create(g_pConfigManager->getAnimationPropertyConfig("fadeOut"), pWindow, AVARDAMAGE_ENTIRE); pWindow->addWindowDeco(std::make_unique(pWindow)); pWindow->addWindowDeco(std::make_unique(pWindow)); @@ -52,6 +52,7 @@ PHLWINDOW CWindow::create(SP resource) { pWindow->m_fActiveInactiveAlpha.create(g_pConfigManager->getAnimationPropertyConfig("fadeSwitch"), pWindow, AVARDAMAGE_ENTIRE); pWindow->m_cRealShadowColor.create(g_pConfigManager->getAnimationPropertyConfig("fadeShadow"), pWindow, AVARDAMAGE_SHADOW); pWindow->m_fDimPercent.create(g_pConfigManager->getAnimationPropertyConfig("fadeDim"), pWindow, AVARDAMAGE_ENTIRE); + pWindow->m_fMovingToWorkspaceAlpha.create(g_pConfigManager->getAnimationPropertyConfig("fadeOut"), pWindow, AVARDAMAGE_ENTIRE); pWindow->addWindowDeco(std::make_unique(pWindow)); pWindow->addWindowDeco(std::make_unique(pWindow)); @@ -252,7 +253,7 @@ void CWindow::updateWindowDecos() { if (!m_bIsMapped || isHidden()) return; - for (auto& wd : m_vDecosToRemove) { + for (auto const& wd : m_vDecosToRemove) { for (auto it = m_dWindowDecorations.begin(); it != m_dWindowDecorations.end(); it++) { if (it->get() == wd) { g_pDecorationPositioner->uncacheDecoration(it->get()); @@ -270,11 +271,11 @@ void CWindow::updateWindowDecos() { // make a copy because updateWindow can remove decos. std::vector decos; - for (auto& wd : m_dWindowDecorations) { + for (auto const& wd : m_dWindowDecorations) { decos.push_back(wd.get()); } - for (auto& wd : decos) { + for (auto const& wd : decos) { if (std::find_if(m_dWindowDecorations.begin(), m_dWindowDecorations.end(), [wd](const auto& other) { return other.get() == wd; }) == m_dWindowDecorations.end()) continue; wd->updateWindow(m_pSelf.lock()); @@ -296,7 +297,7 @@ void CWindow::removeWindowDeco(IHyprWindowDecoration* deco) { } void CWindow::uncacheWindowDecos() { - for (auto& wd : m_dWindowDecorations) { + for (auto const& wd : m_dWindowDecorations) { g_pDecorationPositioner->uncacheDecoration(wd.get()); } } @@ -305,7 +306,7 @@ bool CWindow::checkInputOnDecos(const eInputType type, const Vector2D& mouseCoor if (type != INPUT_TYPE_DRAG_END && hasPopupAt(mouseCoords)) return false; - for (auto& wd : m_dWindowDecorations) { + for (auto const& wd : m_dWindowDecorations) { if (!(wd->getDecorationFlags() & DECORATION_ALLOWS_MOUSE_INPUT)) continue; @@ -337,7 +338,7 @@ pid_t CWindow::getPID() { } IHyprWindowDecoration* CWindow::getDecorationByType(eDecorationType type) { - for (auto& wd : m_dWindowDecorations) { + for (auto const& wd : m_dWindowDecorations) { if (wd->getDecorationType() == type) return wd.get(); } @@ -407,6 +408,11 @@ void CWindow::moveToWorkspace(PHLWORKSPACE pWorkspace) { const auto OLDWORKSPACE = m_pWorkspace; + m_iMonitorMovedFrom = OLDWORKSPACE ? OLDWORKSPACE->m_iMonitorID : -1; + m_fMovingToWorkspaceAlpha.setValueAndWarp(1.F); + m_fMovingToWorkspaceAlpha = 0.F; + m_fMovingToWorkspaceAlpha.setCallbackOnEnd([this](void* thisptr) { m_iMonitorMovedFrom = -1; }); + m_pWorkspace = pWorkspace; setAnimationsToMove(); @@ -445,16 +451,22 @@ PHLWINDOW CWindow::X11TransientFor() { if (!m_pXWaylandSurface || !m_pXWaylandSurface->parent) return nullptr; - auto s = m_pXWaylandSurface->parent; - auto oldParent = s; + auto s = m_pXWaylandSurface->parent; + std::vector> visited; while (s) { - // break cyclic loop of m_pXWaylandSurface being parent of itself, #TODO reject this from even being created? - if (!s->parent || s->parent == oldParent) + // break loops. Some X apps make them, and it seems like it's valid behavior?!?!?! + // TODO: we should reject loops being created in the first place. + if (std::find(visited.begin(), visited.end(), s) != visited.end()) break; + + visited.emplace_back(s.lock()); s = s->parent; } - for (auto& w : g_pCompositor->m_vWindows) { + if (s == m_pXWaylandSurface) + return nullptr; // dead-ass circle + + for (auto const& w : g_pCompositor->m_vWindows) { if (w->m_pXWaylandSurface != s) continue; return w; @@ -464,7 +476,7 @@ PHLWINDOW CWindow::X11TransientFor() { } void CWindow::removeDecorationByType(eDecorationType type) { - for (auto& wd : m_dWindowDecorations) { + for (auto const& wd : m_dWindowDecorations) { if (wd->getDecorationType() == type) m_vDecosToRemove.push_back(wd.get()); } @@ -502,6 +514,7 @@ void CWindow::onUnmap() { m_fAlpha.setCallbackOnEnd(unregisterVar); m_cRealShadowColor.setCallbackOnEnd(unregisterVar); m_fDimPercent.setCallbackOnEnd(unregisterVar); + m_fMovingToWorkspaceAlpha.setCallbackOnEnd(unregisterVar); m_vRealSize.setCallbackOnBegin(nullptr); @@ -542,6 +555,7 @@ void CWindow::onMap() { m_fAlpha.resetAllCallbacks(); m_cRealShadowColor.resetAllCallbacks(); m_fDimPercent.resetAllCallbacks(); + m_fMovingToWorkspaceAlpha.resetAllCallbacks(); m_vRealPosition.registerVar(); m_vRealSize.registerVar(); @@ -551,6 +565,7 @@ void CWindow::onMap() { m_fAlpha.registerVar(); m_cRealShadowColor.registerVar(); m_fDimPercent.registerVar(); + m_fMovingToWorkspaceAlpha.registerVar(); m_fBorderAngleAnimationProgress.setCallbackOnEnd([&](void* ptr) { onBorderAngleAnimEnd(ptr); }, false); @@ -617,7 +632,7 @@ void CWindow::applyDynamicRule(const SWindowRule& r) { int opacityIDX = 0; - for (auto& r : vars) { + for (auto const& r : vars) { if (r == "opacity") continue; @@ -666,7 +681,7 @@ void CWindow::applyDynamicRule(const SWindowRule& r) { return; } - for (auto& token : colorsAndAngles) { + for (auto const& token : colorsAndAngles) { // The first angle, or an explicit "0deg", splits the two gradients if (active && token.contains("deg")) { activeBorderGradient.m_fAngle = std::stoi(token.substr(0, token.size() - 3)) * (PI / 180.0); @@ -727,9 +742,8 @@ void CWindow::applyDynamicRule(const SWindowRule& r) { } m_sWindowData.maxSize = CWindowOverridableVar(VEC, priority); - m_vRealSize = - Vector2D(std::min((double)m_sWindowData.maxSize.value().x, m_vRealSize.goal().x), std::min((double)m_sWindowData.maxSize.value().y, m_vRealSize.goal().y)); - g_pXWaylandManager->setWindowSize(m_pSelf.lock(), m_vRealSize.goal()); + clampWindowSize(std::nullopt, m_sWindowData.maxSize.value()); + } catch (std::exception& e) { Debug::log(ERR, "maxsize rule \"{}\" failed with: {}", r.szRule, e.what()); } } else if (r.szRule.starts_with("minsize")) { try { @@ -742,12 +756,14 @@ void CWindow::applyDynamicRule(const SWindowRule& r) { } m_sWindowData.minSize = CWindowOverridableVar(VEC, priority); - m_vRealSize = - Vector2D(std::max((double)m_sWindowData.minSize.value().x, m_vRealSize.goal().x), std::max((double)m_sWindowData.minSize.value().y, m_vRealSize.goal().y)); - g_pXWaylandManager->setWindowSize(m_pSelf.lock(), m_vRealSize.goal()); + clampWindowSize(m_sWindowData.minSize.value(), std::nullopt); + if (m_sGroupData.pNextWindow.expired()) setHidden(false); } catch (std::exception& e) { Debug::log(ERR, "minsize rule \"{}\" failed with: {}", r.szRule, e.what()); } + } else if (r.szRule == "renderunfocused") { + m_sWindowData.renderUnfocused = CWindowOverridableVar(true, priority); + g_pHyprRenderer->addWindowToRenderUnfocused(m_pSelf.lock()); } } @@ -765,12 +781,14 @@ void CWindow::updateDynamicRules() { m_sWindowData.activeBorderColor.unset(PRIORITY_WINDOW_RULE); m_sWindowData.inactiveBorderColor.unset(PRIORITY_WINDOW_RULE); + m_sWindowData.renderUnfocused.unset(PRIORITY_WINDOW_RULE); + m_eIdleInhibitMode = IDLEINHIBIT_NONE; m_tags.removeDynamicTags(); m_vMatchedRules = g_pConfigManager->getMatchingRules(m_pSelf.lock()); - for (auto& r : m_vMatchedRules) { + for (auto const& r : m_vMatchedRules) { applyDynamicRule(r); } @@ -881,7 +899,7 @@ void CWindow::destroyGroup() { addresses += std::format("{:x},", (uintptr_t)curr.get()); } while (curr.get() != this); - for (auto& w : members) { + for (auto const& w : members) { if (w->m_sGroupData.head) g_pLayoutManager->getCurrentLayout()->onWindowRemoved(curr); w->m_sGroupData.head = false; @@ -889,7 +907,7 @@ void CWindow::destroyGroup() { const bool GROUPSLOCKEDPREV = g_pKeybindManager->m_bGroupsLocked; g_pKeybindManager->m_bGroupsLocked = true; - for (auto& w : members) { + for (auto const& w : members) { g_pLayoutManager->getCurrentLayout()->onWindowCreated(w); w->updateWindowDecos(); } @@ -937,12 +955,16 @@ int CWindow::getGroupSize() { } bool CWindow::canBeGroupedInto(PHLWINDOW pWindow) { + static auto ALLOWGROUPMERGE = CConfigValue("group:merge_groups_on_drag"); + bool isGroup = m_sGroupData.pNextWindow; + bool disallowDragIntoGroup = g_pInputManager->m_bWasDraggingWindow && isGroup && !bool(*ALLOWGROUPMERGE); return !g_pKeybindManager->m_bGroupsLocked // global group lock disengaged && ((m_eGroupRules & GROUP_INVADE && m_bFirstMap) // window ignore local group locks, or || (!pWindow->getGroupHead()->m_sGroupData.locked // target unlocked && !(m_sGroupData.pNextWindow.lock() && getGroupHead()->m_sGroupData.locked))) // source unlocked or isn't group && !m_sGroupData.deny // source is not denied entry - && !(m_eGroupRules & GROUP_BARRED && m_bFirstMap); // group rule doesn't prevent adding window + && !(m_eGroupRules & GROUP_BARRED && m_bFirstMap) // group rule doesn't prevent adding window + && !disallowDragIntoGroup; // config allows groups to be merged } PHLWINDOW CWindow::getGroupWindowByIndex(int index) { @@ -973,7 +995,7 @@ void CWindow::setGroupCurrent(PHLWINDOW pWindow) { const auto PCURRENT = getGroupCurrent(); const bool FULLSCREEN = PCURRENT->isFullscreen(); const auto WORKSPACE = PCURRENT->m_pWorkspace; - const auto MODE = PCURRENT->m_sFullscreenState.client; + const auto MODE = PCURRENT->m_sFullscreenState.internal; const auto PWINDOWSIZE = PCURRENT->m_vRealSize.goal(); const auto PWINDOWPOS = PCURRENT->m_vRealPosition.goal(); @@ -1235,6 +1257,16 @@ int CWindow::surfacesCount() { return no; } +void CWindow::clampWindowSize(const std::optional minSize, const std::optional maxSize) { + const Vector2D REALSIZE = m_vRealSize.goal(); + const Vector2D NEWSIZE = REALSIZE.clamp(minSize.value_or(Vector2D{0.f, 0.f}), maxSize.value_or(Vector2D{INFINITY, INFINITY})); + const Vector2D DELTA = REALSIZE - NEWSIZE; + + m_vRealPosition = m_vRealPosition.goal() + DELTA / 2.0; + m_vRealSize = NEWSIZE; + g_pXWaylandManager->setWindowSize(m_pSelf.lock(), NEWSIZE); +} + bool CWindow::isFullscreen() { return m_sFullscreenState.internal != FSMODE_NONE; } @@ -1243,7 +1275,7 @@ bool CWindow::isEffectiveInternalFSMode(const eFullscreenMode MODE) { return (eFullscreenMode)std::bit_floor((uint8_t)m_sFullscreenState.internal) == MODE; } -int CWindow::workspaceID() { +WORKSPACEID CWindow::workspaceID() { return m_pWorkspace ? m_pWorkspace->m_iID : m_iLastWorkspace; } @@ -1282,7 +1314,7 @@ std::unordered_map CWindow::getEnv() { CVarList envs(std::string{buffer.data(), buffer.size() - 1}, 0, '\n', true); - for (auto& e : envs) { + for (auto const& e : envs) { if (!e.contains('=')) continue; @@ -1307,6 +1339,11 @@ void CWindow::activate(bool force) { if (!force && (!m_sWindowData.focusOnActivate.valueOr(*PFOCUSONACTIVATE) || (m_eSuppressedEvents & SUPPRESS_ACTIVATE_FOCUSONLY) || (m_eSuppressedEvents & SUPPRESS_ACTIVATE))) return; + if (!m_bIsMapped) { + Debug::log(LOG, "Ignoring CWindow::activate focus/warp, window is not mapped yet."); + return; + } + if (m_bIsFloating) g_pCompositor->changeWindowZOrder(m_pSelf.lock(), true); @@ -1511,7 +1548,7 @@ PHLWINDOW CWindow::getSwallower() { if (!currentPid) break; - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (!w->m_bIsMapped || w->isHidden()) continue; @@ -1536,7 +1573,7 @@ PHLWINDOW CWindow::getSwallower() { return candidates.at(0); // walk up the focus history and find the last focused - for (auto& w : g_pCompositor->m_vWindowFocusHistory) { + for (auto const& w : g_pCompositor->m_vWindowFocusHistory) { if (!w) continue; @@ -1556,3 +1593,11 @@ void CWindow::unsetWindowData(eOverridePriority priority) { element.second(m_pSelf.lock())->unset(priority); } } + +bool CWindow::isX11OverrideRedirect() { + return m_pXWaylandSurface && m_pXWaylandSurface->overrideRedirect; +} + +bool CWindow::isModal() { + return (m_pXWaylandSurface && m_pXWaylandSurface->modal); +} diff --git a/src/desktop/Window.hpp b/src/desktop/Window.hpp index 11bf662a..9baf57c4 100644 --- a/src/desktop/Window.hpp +++ b/src/desktop/Window.hpp @@ -174,6 +174,7 @@ struct SWindowData { CWindowOverridableVar syncFullscreen = true; CWindowOverridableVar tearing = false; CWindowOverridableVar xray = false; + CWindowOverridableVar renderUnfocused = false; CWindowOverridableVar rounding; CWindowOverridableVar borderSize; @@ -196,13 +197,14 @@ struct SWindowRule { std::string szInitialTitle; std::string szInitialClass; std::string szTag; - int bX11 = -1; // -1 means "ANY" - int bFloating = -1; - int bFullscreen = -1; - int bPinned = -1; - int bFocus = -1; - std::string szOnWorkspace = ""; // empty means any - std::string szWorkspace = ""; // empty means any + int bX11 = -1; // -1 means "ANY" + int bFloating = -1; + int bFullscreen = -1; + int bPinned = -1; + int bFocus = -1; + std::string szFullscreenState = ""; // empty means any + std::string szOnWorkspace = ""; // empty means any + std::string szWorkspace = ""; // empty means any }; struct SInitialWorkspaceToken { @@ -270,7 +272,7 @@ class CWindow { bool m_bDraggingTiled = false; // for dragging around tiled windows bool m_bWasMaximized = false; sFullscreenState m_sFullscreenState = {.internal = FSMODE_NONE, .client = FSMODE_NONE}; - uint64_t m_iMonitorID = -1; + MONITORID m_iMonitorID = -1; std::string m_szTitle = ""; std::string m_szClass = ""; std::string m_szInitialTitle = ""; @@ -287,8 +289,6 @@ class CWindow { // XWayland stuff bool m_bIsX11 = false; PHLWINDOWREF m_pX11Parent; - uint64_t m_iX11Type = 0; - bool m_bIsModal = false; bool m_bX11DoesntWantBorders = false; bool m_bX11ShouldntFocus = false; float m_fX11SurfaceScaledBy = 1.f; @@ -351,6 +351,10 @@ class CWindow { // animated tint CAnimatedVariable m_fDimPercent; + // animate moving to an invisible workspace + int m_iMonitorMovedFrom = -1; // -1 means not moving + CAnimatedVariable m_fMovingToWorkspaceAlpha; + // swallowing PHLWINDOWREF m_pSwallowed; @@ -358,8 +362,8 @@ class CWindow { bool m_bStayFocused = false; // for toplevel monitor events - uint64_t m_iLastToplevelMonitorID = -1; - uint64_t m_iLastSurfaceMonitorID = -1; + MONITORID m_iLastToplevelMonitorID = -1; + MONITORID m_iLastSurfaceMonitorID = -1; // for idle inhibiting windows eIdleInhibitMode m_eIdleInhibitMode = IDLEINHIBIT_NONE; @@ -421,10 +425,11 @@ class CWindow { bool canBeTorn(); void setSuspended(bool suspend); bool visibleOnMonitor(CMonitor* pMonitor); - int workspaceID(); + WORKSPACEID workspaceID(); bool onSpecialWorkspace(); void activate(bool force = false); int surfacesCount(); + void clampWindowSize(const std::optional minSize, const std::optional maxSize); bool isFullscreen(); bool isEffectiveInternalFSMode(const eFullscreenMode); @@ -463,6 +468,8 @@ class CWindow { void warpCursor(); PHLWINDOW getSwallower(); void unsetWindowData(eOverridePriority priority); + bool isX11OverrideRedirect(); + bool isModal(); // listeners void onAck(uint32_t serial); @@ -490,9 +497,9 @@ class CWindow { private: // For hidden windows and stuff - bool m_bHidden = false; - bool m_bSuspended = false; - int m_iLastWorkspace = WORKSPACE_INVALID; + bool m_bHidden = false; + bool m_bSuspended = false; + WORKSPACEID m_iLastWorkspace = WORKSPACE_INVALID; }; inline bool valid(PHLWINDOW w) { diff --git a/src/desktop/Workspace.cpp b/src/desktop/Workspace.cpp index a08f1804..2d92659c 100644 --- a/src/desktop/Workspace.cpp +++ b/src/desktop/Workspace.cpp @@ -5,27 +5,28 @@ #include using namespace Hyprutils::String; -PHLWORKSPACE CWorkspace::create(int id, int monitorID, std::string name, bool special, bool isEmtpy) { - PHLWORKSPACE workspace = makeShared(id, monitorID, name, special, isEmtpy); +PHLWORKSPACE CWorkspace::create(WORKSPACEID id, MONITORID monitorID, std::string name, bool special, bool isEmpty) { + PHLWORKSPACE workspace = makeShared(id, monitorID, name, special, isEmpty); workspace->init(workspace); return workspace; } -CWorkspace::CWorkspace(int id, int monitorID, std::string name, bool special, bool isEmtpy) { +CWorkspace::CWorkspace(WORKSPACEID id, MONITORID monitorID, std::string name, bool special, bool isEmpty) { m_iMonitorID = monitorID; m_iID = id; m_szName = name; m_bIsSpecialWorkspace = special; - m_bWasCreatedEmtpy = isEmtpy; + m_bWasCreatedEmpty = isEmpty; } void CWorkspace::init(PHLWORKSPACE self) { m_pSelf = self; - m_vRenderOffset.create(m_bIsSpecialWorkspace ? g_pConfigManager->getAnimationPropertyConfig("specialWorkspace") : g_pConfigManager->getAnimationPropertyConfig("workspaces"), + m_vRenderOffset.create(m_bIsSpecialWorkspace ? g_pConfigManager->getAnimationPropertyConfig("specialWorkspaceIn") : + g_pConfigManager->getAnimationPropertyConfig("workspacesIn"), self, AVARDAMAGE_ENTIRE); m_fAlpha.create(AVARTYPE_FLOAT, - m_bIsSpecialWorkspace ? g_pConfigManager->getAnimationPropertyConfig("specialWorkspace") : g_pConfigManager->getAnimationPropertyConfig("workspaces"), self, + m_bIsSpecialWorkspace ? g_pConfigManager->getAnimationPropertyConfig("specialWorkspaceIn") : g_pConfigManager->getAnimationPropertyConfig("workspacesIn"), self, AVARDAMAGE_ENTIRE); m_fAlpha.setValueAndWarp(1.f); @@ -48,9 +49,9 @@ void CWorkspace::init(PHLWORKSPACE self) { const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(self); m_bPersistent = WORKSPACERULE.isPersistent; - if (self->m_bWasCreatedEmtpy) + if (self->m_bWasCreatedEmpty) if (auto cmd = WORKSPACERULE.onCreatedEmptyRunCmd) - g_pKeybindManager->spawn(*cmd); + g_pKeybindManager->spawnWithRules(*cmd, self); g_pEventManager->postEvent({"createworkspace", m_szName}); g_pEventManager->postEvent({"createworkspacev2", std::format("{},{}", m_iID, m_szName)}); @@ -81,12 +82,19 @@ CWorkspace::~CWorkspace() { } void CWorkspace::startAnim(bool in, bool left, bool instant) { + if (!instant) { + const std::string ANIMNAME = std::format("{}{}", m_bIsSpecialWorkspace ? "specialWorkspace" : "workspaces", in ? "In" : "Out"); + + m_fAlpha.m_pConfig = g_pConfigManager->getAnimationPropertyConfig(ANIMNAME); + m_vRenderOffset.m_pConfig = g_pConfigManager->getAnimationPropertyConfig(ANIMNAME); + } + const auto ANIMSTYLE = m_fAlpha.m_pConfig->pValues->internalStyle; static auto PWORKSPACEGAP = CConfigValue("general:gaps_workspaces"); // set floating windows offset callbacks m_vRenderOffset.setUpdateCallback([&](void*) { - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (!validMapped(w) || w->workspaceID() != m_iID) continue; @@ -190,7 +198,7 @@ void CWorkspace::setActive(bool on) { ; // empty until https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/40 } -void CWorkspace::moveToMonitor(const int& id) { +void CWorkspace::moveToMonitor(const MONITORID& id) { ; // empty until https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/40 } @@ -275,7 +283,7 @@ bool CWorkspace::matchesStaticSelector(const std::string& selector_) { i = std::min(NEXTSPACE, std::string::npos - 1); if (cur == 'r') { - int from = 0, to = 0; + WORKSPACEID from = 0, to = 0; if (!prop.starts_with("r[") || !prop.ends_with("]")) { Debug::log(LOG, "Invalid selector {}", selector); return false; @@ -365,7 +373,7 @@ bool CWorkspace::matchesStaticSelector(const std::string& selector_) { } if (cur == 'w') { - int from = 0, to = 0; + WORKSPACEID from = 0, to = 0; if (!prop.starts_with("w[") || !prop.ends_with("]")) { Debug::log(LOG, "Invalid selector {}", selector); return false; @@ -378,7 +386,7 @@ bool CWorkspace::matchesStaticSelector(const std::string& selector_) { bool wantsCountVisible = false; int flagCount = 0; - for (auto& flag : prop) { + for (auto const& flag : prop) { if (flag == 't' && wantsOnlyTiled == -1) { wantsOnlyTiled = 1; flagCount++; @@ -446,7 +454,7 @@ bool CWorkspace::matchesStaticSelector(const std::string& selector_) { return false; } - int count; + WORKSPACEID count; if (wantsCountGroup) count = g_pCompositor->getGroupsOnWorkspace(m_iID, wantsOnlyTiled == -1 ? std::nullopt : std::optional((bool)wantsOnlyTiled), wantsCountVisible ? std::optional(wantsCountVisible) : std::nullopt); @@ -506,7 +514,7 @@ bool CWorkspace::matchesStaticSelector(const std::string& selector_) { void CWorkspace::markInert() { m_bInert = true; m_iID = WORKSPACE_INVALID; - m_iMonitorID = -1; + m_iMonitorID = MONITOR_INVALID; m_bVisible = false; } diff --git a/src/desktop/Workspace.hpp b/src/desktop/Workspace.hpp index 3e9ac8a8..7a32459c 100644 --- a/src/desktop/Workspace.hpp +++ b/src/desktop/Workspace.hpp @@ -17,16 +17,16 @@ class CWindow; class CWorkspace { public: - static PHLWORKSPACE create(int id, int monitorID, std::string name, bool special = false, bool isEmtpy = true); + static PHLWORKSPACE create(WORKSPACEID id, MONITORID monitorID, std::string name, bool special = false, bool isEmpty = true); // use create() don't use this - CWorkspace(int id, int monitorID, std::string name, bool special = false, bool isEmpty = true); + CWorkspace(WORKSPACEID id, MONITORID monitorID, std::string name, bool special = false, bool isEmpty = true); ~CWorkspace(); // Workspaces ID-based have IDs > 0 // and workspaces name-based have IDs starting with -1337 - int m_iID = -1; + WORKSPACEID m_iID = WORKSPACE_INVALID; std::string m_szName = ""; - uint64_t m_iMonitorID = -1; + MONITORID m_iMonitorID = MONITOR_INVALID; // Previous workspace ID and name is stored during a workspace change, allowing travel // to the previous workspace. SWorkspaceIDName m_sPrevWorkspace, m_sPrevWorkspacePerMonitor; @@ -57,7 +57,7 @@ class CWorkspace { // last monitor (used on reconnect) std::string m_szLastMonitor = ""; - bool m_bWasCreatedEmtpy = true; + bool m_bWasCreatedEmpty = true; bool m_bPersistent = false; @@ -67,7 +67,7 @@ class CWorkspace { void startAnim(bool in, bool left, bool instant = false); void setActive(bool on); - void moveToMonitor(const int&); + void moveToMonitor(const MONITORID&); PHLWINDOW getLastFocusedWindow(); void rememberPrevWorkspace(const PHLWORKSPACE& prevWorkspace); diff --git a/src/devices/IKeyboard.cpp b/src/devices/IKeyboard.cpp index e05cbd04..d1119772 100644 --- a/src/devices/IKeyboard.cpp +++ b/src/devices/IKeyboard.cpp @@ -119,7 +119,8 @@ void IKeyboard::setKeymap(const SStringRuleNames& rules) { if (IDX != XKB_MOD_INVALID) modifiersState.locked |= (uint32_t)1 << IDX; - updateModifiers(modifiersState.depressed, modifiersState.latched, modifiersState.locked, modifiersState.group); + // 0 to avoid mods getting stuck if depressed during reload + updateModifiers(0, 0, modifiersState.locked, modifiersState.group); } for (size_t i = 0; i < LEDNAMES.size(); ++i) { @@ -177,13 +178,18 @@ void IKeyboard::updateXKBTranslationState(xkb_keymap* const keymap) { if (xkbState) xkb_state_unref(xkbState); + if (xkbSymState) + xkb_state_unref(xkbSymState); + xkbState = nullptr; xkbStaticState = nullptr; + xkbSymState = nullptr; if (keymap) { Debug::log(LOG, "Updating keyboard {:x}'s translation state from a provided keymap", (uintptr_t)this); xkbStaticState = xkb_state_new(keymap); xkbState = xkb_state_new(keymap); + xkbSymState = xkb_state_new(keymap); return; } @@ -229,6 +235,7 @@ void IKeyboard::updateXKBTranslationState(xkb_keymap* const keymap) { xkbState = xkb_state_new(KEYMAP); xkbStaticState = xkb_state_new(KEYMAP); + xkbSymState = xkb_state_new(KEYMAP); xkb_keymap_unref(KEYMAP); xkb_context_unref(PCONTEXT); @@ -251,6 +258,7 @@ void IKeyboard::updateXKBTranslationState(xkb_keymap* const keymap) { xkbState = xkb_state_new(NEWKEYMAP); xkbStaticState = xkb_state_new(NEWKEYMAP); + xkbSymState = xkb_state_new(NEWKEYMAP); xkb_keymap_unref(NEWKEYMAP); xkb_context_unref(PCONTEXT); @@ -331,6 +339,9 @@ void IKeyboard::updateModifiers(uint32_t depressed, uint32_t latched, uint32_t l xkb_state_update_mask(xkbState, depressed, latched, locked, 0, 0, group); + if (xkbSymState) + xkb_state_update_mask(xkbSymState, 0, 0, 0, 0, 0, group); + if (!updateModifiersState()) return; @@ -381,6 +392,9 @@ void IKeyboard::updateXkbStateWithKey(uint32_t xkbKey, bool pressed) { xkb_state_update_key(xkbState, xkbKey, pressed ? XKB_KEY_DOWN : XKB_KEY_UP); if (updateModifiersState()) { + if (xkbSymState) + xkb_state_update_mask(xkbSymState, 0, 0, 0, 0, 0, modifiersState.group); + keyboardEvents.modifiers.emit(SModifiersEvent{ .depressed = modifiersState.depressed, .latched = modifiersState.latched, diff --git a/src/devices/IKeyboard.hpp b/src/devices/IKeyboard.hpp index ad8eaf7e..76d2c31b 100644 --- a/src/devices/IKeyboard.hpp +++ b/src/devices/IKeyboard.hpp @@ -1,7 +1,6 @@ #pragma once #include "IHID.hpp" -#include "../helpers/WLListener.hpp" #include "../macros.hpp" #include "../helpers/math/Math.hpp" @@ -83,8 +82,9 @@ class IKeyboard : public IHID { bool keymapOverridden = false; xkb_layout_index_t activeLayout = 0; - xkb_state * xkbState = nullptr, *xkbStaticState /* Static state: never gets modifiers or layout changes sent, used for keybinds. */ = nullptr; - xkb_keymap* xkbKeymap = nullptr; + xkb_state * xkbState = nullptr, *xkbStaticState /* Static state: never gets modifiers or layout changes sent, used for keybinds. */ = nullptr, + *xkbSymState = nullptr /* Same as static but gets layouts */; + xkb_keymap* xkbKeymap = nullptr; struct { uint32_t depressed = 0, latched = 0, locked = 0, group = 0; diff --git a/src/devices/IPointer.hpp b/src/devices/IPointer.hpp index 760ec8dc..f38ef55a 100644 --- a/src/devices/IPointer.hpp +++ b/src/devices/IPointer.hpp @@ -1,7 +1,6 @@ #pragma once #include "IHID.hpp" -#include "../helpers/WLListener.hpp" #include "../macros.hpp" #include "../helpers/math/Math.hpp" diff --git a/src/devices/ITouch.hpp b/src/devices/ITouch.hpp index cb8a6e90..bf969b2f 100644 --- a/src/devices/ITouch.hpp +++ b/src/devices/ITouch.hpp @@ -1,7 +1,6 @@ #pragma once #include "IHID.hpp" -#include "../helpers/WLListener.hpp" #include "../macros.hpp" #include "../helpers/math/Math.hpp" diff --git a/src/devices/Tablet.hpp b/src/devices/Tablet.hpp index 0efbe796..01901721 100644 --- a/src/devices/Tablet.hpp +++ b/src/devices/Tablet.hpp @@ -1,7 +1,6 @@ #pragma once #include "IHID.hpp" -#include "../helpers/WLListener.hpp" #include "../macros.hpp" #include "../helpers/math/Math.hpp" #include "../helpers/math/Math.hpp" diff --git a/src/events/Events.hpp b/src/events/Events.hpp index 8e73f54a..0af16f64 100644 --- a/src/events/Events.hpp +++ b/src/events/Events.hpp @@ -27,7 +27,6 @@ namespace Events { // Monitor part 2 the sequel DYNLISTENFUNC(monitorFrame); - DYNLISTENFUNC(monitorDestroy); DYNLISTENFUNC(monitorStateRequest); DYNLISTENFUNC(monitorDamage); DYNLISTENFUNC(monitorNeedsFrame); diff --git a/src/events/Monitors.cpp b/src/events/Monitors.cpp index b2778062..9d2210f6 100644 --- a/src/events/Monitors.cpp +++ b/src/events/Monitors.cpp @@ -85,31 +85,6 @@ void Events::listener_monitorFrame(void* owner, void* data) { } } -void Events::listener_monitorDestroy(void* owner, void* data) { - CMonitor* pMonitor = (CMonitor*)owner; - - for (auto& m : g_pCompositor->m_vRealMonitors) { - if (m->output == pMonitor->output) { - pMonitor = m.get(); - break; - } - } - - if (!pMonitor) - return; - - Debug::log(LOG, "Destroy called for monitor {}", pMonitor->output->name); - - pMonitor->onDisconnect(true); - - pMonitor->output = nullptr; - pMonitor->m_bRenderingInitPassed = false; - - Debug::log(LOG, "Removing monitor {} from realMonitors", pMonitor->szName); - - std::erase_if(g_pCompositor->m_vRealMonitors, [&](SP& el) { return el.get() == pMonitor; }); -} - void Events::listener_monitorNeedsFrame(void* owner, void* data) { const auto PMONITOR = (CMonitor*)owner; diff --git a/src/events/Windows.cpp b/src/events/Windows.cpp index 5d29a3b7..e549907e 100644 --- a/src/events/Windows.cpp +++ b/src/events/Windows.cpp @@ -12,6 +12,7 @@ #include "../protocols/core/Compositor.hpp" #include "../protocols/ToplevelExport.hpp" #include "../xwayland/XSurface.hpp" +#include "managers/PointerManager.hpp" #include using namespace Hyprutils::String; @@ -120,7 +121,7 @@ void Events::listener_mapWindow(void* owner, void* data) { PWINDOW->m_bRequestsFloat = true; } - PWINDOW->m_bX11ShouldntFocus = PWINDOW->m_bX11ShouldntFocus || (PWINDOW->m_bIsX11 && PWINDOW->m_iX11Type == 2 && !PWINDOW->m_pXWaylandSurface->wantsFocus()); + PWINDOW->m_bX11ShouldntFocus = PWINDOW->m_bX11ShouldntFocus || (PWINDOW->m_bIsX11 && PWINDOW->isX11OverrideRedirect() && !PWINDOW->m_pXWaylandSurface->wantsFocus()); if (PWORKSPACE->m_bDefaultFloating) PWINDOW->m_bIsFloating = true; @@ -139,7 +140,7 @@ void Events::listener_mapWindow(void* owner, void* data) { if (PWINDOW->m_bWantsInitialFullscreen || (PWINDOW->m_bIsX11 && PWINDOW->m_pXWaylandSurface->fullscreen)) requestedClientFSMode = FSMODE_FULLSCREEN; - for (auto& r : PWINDOW->m_vMatchedRules) { + for (auto const& r : PWINDOW->m_vMatchedRules) { if (r.szRule.starts_with("monitor")) { try { const auto MONITORSTR = trim(r.szRule.substr(r.szRule.find(' '))); @@ -148,7 +149,7 @@ void Events::listener_mapWindow(void* owner, void* data) { PWINDOW->m_iMonitorID = PMONITOR->ID; } else { if (isNumber(MONITORSTR)) { - const long int MONITOR = std::stoi(MONITORSTR); + const MONITORID MONITOR = std::stoi(MONITORSTR); if (!g_pCompositor->getMonitorFromID(MONITOR)) PWINDOW->m_iMonitorID = 0; else @@ -322,11 +323,11 @@ void Events::listener_mapWindow(void* owner, void* data) { PWINDOW->updateWindowData(); if (PWINDOW->m_bIsFloating) { - g_pLayoutManager->getCurrentLayout()->onWindowCreatedFloating(PWINDOW); + g_pLayoutManager->getCurrentLayout()->onWindowCreated(PWINDOW); PWINDOW->m_bCreatedOverFullscreen = true; // size and move rules - for (auto& r : PWINDOW->m_vMatchedRules) { + for (auto const& r : PWINDOW->m_vMatchedRules) { if (r.szRule.starts_with("size")) { try { const auto VALUE = r.szRule.substr(r.szRule.find(' ') + 1); @@ -344,7 +345,8 @@ void Events::listener_mapWindow(void* owner, void* data) { Debug::log(LOG, "Rule size, applying to {}", PWINDOW); - PWINDOW->m_vRealSize = Vector2D(SIZEX, SIZEY); + PWINDOW->m_vRealSize = Vector2D(SIZEX, SIZEY); + PWINDOW->m_vPseudoSize = PWINDOW->m_vRealSize.goal(); g_pXWaylandManager->setWindowSize(PWINDOW, PWINDOW->m_vRealSize.goal()); PWINDOW->setHidden(false); @@ -449,8 +451,36 @@ void Events::listener_mapWindow(void* owner, void* data) { } else { g_pLayoutManager->getCurrentLayout()->onWindowCreated(PWINDOW); - // Set the pseudo size here too so that it doesnt end up being 0x0 - PWINDOW->m_vPseudoSize = PWINDOW->m_vRealSize.goal() - Vector2D(10, 10); + bool setPseudo = false; + + for (auto const& r : PWINDOW->m_vMatchedRules) { + if (r.szRule.starts_with("size")) { + try { + const auto VALUE = r.szRule.substr(r.szRule.find(' ') + 1); + const auto SIZEXSTR = VALUE.substr(0, VALUE.find(' ')); + const auto SIZEYSTR = VALUE.substr(VALUE.find(' ') + 1); + + const auto MAXSIZE = g_pXWaylandManager->getMaxSizeForWindow(PWINDOW); + + const auto SIZEX = SIZEXSTR == "max" ? + std::clamp(MAXSIZE.x, 20.0, PMONITOR->vecSize.x) : + (!SIZEXSTR.contains('%') ? std::stoi(SIZEXSTR) : std::stof(SIZEXSTR.substr(0, SIZEXSTR.length() - 1)) * 0.01 * PMONITOR->vecSize.x); + const auto SIZEY = SIZEYSTR == "max" ? + std::clamp(MAXSIZE.y, 20.0, PMONITOR->vecSize.y) : + (!SIZEYSTR.contains('%') ? std::stoi(SIZEYSTR) : std::stof(SIZEYSTR.substr(0, SIZEYSTR.length() - 1)) * 0.01 * PMONITOR->vecSize.y); + + Debug::log(LOG, "Rule size (tiled), applying to {}", PWINDOW); + + setPseudo = true; + PWINDOW->m_vPseudoSize = Vector2D(SIZEX, SIZEY); + + PWINDOW->setHidden(false); + } catch (...) { Debug::log(LOG, "Rule size failed, rule: {} -> {}", r.szRule, r.szValue); } + } + } + + if (!setPseudo) + PWINDOW->m_vPseudoSize = PWINDOW->m_vRealSize.goal() - Vector2D(10, 10); } const auto PFOCUSEDWINDOWPREV = g_pCompositor->m_pLastWindow.lock(); @@ -477,7 +507,7 @@ void Events::listener_mapWindow(void* owner, void* data) { } if (!PWINDOW->m_sWindowData.noFocus.valueOrDefault() && !PWINDOW->m_bNoInitialFocus && - (PWINDOW->m_iX11Type != 2 || (PWINDOW->m_bIsX11 && PWINDOW->m_pXWaylandSurface->wantsFocus())) && !workspaceSilent && (!PFORCEFOCUS || PFORCEFOCUS == PWINDOW) && + (!PWINDOW->isX11OverrideRedirect() || (PWINDOW->m_bIsX11 && PWINDOW->m_pXWaylandSurface->wantsFocus())) && !workspaceSilent && (!PFORCEFOCUS || PFORCEFOCUS == PWINDOW) && !g_pInputManager->isConstrained()) { g_pCompositor->focusWindow(PWINDOW); PWINDOW->m_fActiveInactiveAlpha.setValueAndWarp(*PACTIVEALPHA); @@ -577,7 +607,7 @@ void Events::listener_mapWindow(void* owner, void* data) { g_pCompositor->updateWorkspaceWindows(PWINDOW->workspaceID()); - if (PMONITOR && PWINDOW->m_iX11Type == 2) + if (PMONITOR && PWINDOW->isX11OverrideRedirect()) PWINDOW->m_fX11SurfaceScaledBy = PMONITOR->scale; } @@ -648,7 +678,12 @@ void Events::listener_unmapWindow(void* owner, void* data) { // refocus on a new window if needed if (wasLastWindow) { - const auto PWINDOWCANDIDATE = g_pLayoutManager->getCurrentLayout()->getNextWindowCandidate(PWINDOW); + static auto FOCUSONCLOSE = CConfigValue("input:focus_on_close"); + PHLWINDOW PWINDOWCANDIDATE = nullptr; + if (*FOCUSONCLOSE) + PWINDOWCANDIDATE = (g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(), RESERVED_EXTENTS | INPUT_EXTENTS | ALLOW_FLOATING)); + else + PWINDOWCANDIDATE = g_pLayoutManager->getCurrentLayout()->getNextWindowCandidate(PWINDOW); Debug::log(LOG, "On closed window, new focused candidate is {}", PWINDOWCANDIDATE); @@ -717,33 +752,28 @@ void Events::listener_commitWindow(void* owner, void* data) { const auto MINSIZE = PWINDOW->m_pXDGSurface->toplevel->current.minSize; const auto MAXSIZE = PWINDOW->m_pXDGSurface->toplevel->current.maxSize; - if (MAXSIZE > Vector2D{1, 1}) { - const auto REALSIZE = PWINDOW->m_vRealSize.goal(); - Vector2D newSize = REALSIZE; - - if (MAXSIZE.x < newSize.x) - newSize.x = MAXSIZE.x; - if (MAXSIZE.y < newSize.y) - newSize.y = MAXSIZE.y; - if (MINSIZE.x > newSize.x) - newSize.x = MINSIZE.x; - if (MINSIZE.y > newSize.y) - newSize.y = MINSIZE.y; - - const Vector2D DELTA = REALSIZE - newSize; - - PWINDOW->m_vRealPosition = PWINDOW->m_vRealPosition.goal() + DELTA / 2.0; - PWINDOW->m_vRealSize = newSize; - g_pXWaylandManager->setWindowSize(PWINDOW, newSize); - g_pHyprRenderer->damageWindow(PWINDOW); - } + PWINDOW->clampWindowSize(MINSIZE, MAXSIZE > Vector2D{1, 1} ? std::optional{MAXSIZE} : std::nullopt); + g_pHyprRenderer->damageWindow(PWINDOW); } if (!PWINDOW->m_pWorkspace->m_bVisible) return; - g_pHyprRenderer->damageSurface(PWINDOW->m_pWLSurface->resource(), PWINDOW->m_vRealPosition.goal().x, PWINDOW->m_vRealPosition.goal().y, - PWINDOW->m_bIsX11 ? 1.0 / PWINDOW->m_fX11SurfaceScaledBy : 1.0); + const auto PMONITOR = g_pCompositor->getMonitorFromID(PWINDOW->m_iMonitorID); + + PMONITOR->debugLastPresentation(g_pSeatManager->isPointerFrameCommit ? "listener_commitWindow skip" : "listener_commitWindow"); + if (g_pSeatManager->isPointerFrameCommit) { + g_pSeatManager->isPointerFrameSkipped = false; + g_pSeatManager->isPointerFrameCommit = false; + } else + g_pHyprRenderer->damageSurface(PWINDOW->m_pWLSurface->resource(), PWINDOW->m_vRealPosition.goal().x, PWINDOW->m_vRealPosition.goal().y, + PWINDOW->m_bIsX11 ? 1.0 / PWINDOW->m_fX11SurfaceScaledBy : 1.0); + + if (g_pSeatManager->isPointerFrameSkipped) { + g_pPointerManager->sendStoredMovement(); + g_pSeatManager->sendPointerFrame(); + g_pSeatManager->isPointerFrameCommit = true; + } if (!PWINDOW->m_bIsX11) { PWINDOW->m_pSubsurfaceHead->recheckDamageForSubsurfaces(); @@ -751,7 +781,6 @@ void Events::listener_commitWindow(void* owner, void* data) { } // tearing: if solitary, redraw it. This still might be a single surface window - const auto PMONITOR = g_pCompositor->getMonitorFromID(PWINDOW->m_iMonitorID); if (PMONITOR && PMONITOR->solitaryClient.lock() == PWINDOW && PWINDOW->canBeTorn() && PMONITOR->tearingState.canTear && PWINDOW->m_pWLSurface->resource()->current.texture) { CRegion damageBox{PWINDOW->m_pWLSurface->resource()->accumulateCurrentBufferDamage()}; @@ -811,7 +840,7 @@ void Events::listener_activateX11(void* owner, void* data) { Debug::log(LOG, "X11 Activate request for window {}", PWINDOW); - if (PWINDOW->m_iX11Type == 2) { + if (PWINDOW->isX11OverrideRedirect()) { Debug::log(LOG, "Unmanaged X11 {} requests activate", PWINDOW); diff --git a/src/helpers/BezierCurve.cpp b/src/helpers/BezierCurve.cpp index dd0ff2b0..ea567ad6 100644 --- a/src/helpers/BezierCurve.cpp +++ b/src/helpers/BezierCurve.cpp @@ -12,7 +12,7 @@ void CBezierCurve::setup(std::vector* pVec) { m_dPoints.emplace_back(Vector2D(0, 0)); - for (auto& p : *pVec) { + for (auto const& p : *pVec) { m_dPoints.push_back(p); } diff --git a/src/helpers/ByteOperations.hpp b/src/helpers/ByteOperations.hpp new file mode 100644 index 00000000..13e98500 --- /dev/null +++ b/src/helpers/ByteOperations.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include + +#define ULL unsigned long long +#define LD long double + +constexpr ULL operator""_kB(const ULL BYTES) { + return BYTES * 1024; +} +constexpr ULL operator""_MB(const ULL BYTES) { + return BYTES * 1024 * 1024; +} +constexpr ULL operator""_GB(const ULL BYTES) { + return BYTES * 1024 * 1024 * 1024; +} +constexpr ULL operator""_TB(const ULL BYTES) { + return BYTES * 1024 * 1024 * 1024 * 1024; +} +constexpr LD operator""_kB(const LD BYTES) { + return BYTES * 1024; +} +constexpr LD operator""_MB(const LD BYTES) { + return BYTES * 1024 * 1024; +} +constexpr LD operator""_GB(const LD BYTES) { + return BYTES * 1024 * 1024 * 1024; +} +constexpr LD operator""_TB(const LD BYTES) { + return BYTES * 1024 * 1024 * 1024 * 1024; +} + +template +using __acceptable_byte_operation_type = typename std::enable_if::value || std::is_trivially_constructible::value>::type; + +template > +constexpr X kBtoBytes(const X kB) { + return kB * 1024; +} +template > +constexpr X MBtoBytes(const X MB) { + return MB * 1024 * 1024; +} +template > +constexpr X GBtoBytes(const X GB) { + return GB * 1024 * 1024 * 1024; +} +template > +constexpr X TBtoBytes(const X TB) { + return TB * 1024 * 1024 * 1024 * 1024; +} + +#undef ULL +#undef LD \ No newline at end of file diff --git a/src/helpers/Color.cpp b/src/helpers/Color.cpp index 67526dc7..f9a207bb 100644 --- a/src/helpers/Color.cpp +++ b/src/helpers/Color.cpp @@ -21,6 +21,6 @@ CColor::CColor(uint64_t hex) { this->a = ALPHA(hex); } -uint32_t CColor::getAsHex() { +uint32_t CColor::getAsHex() const { return (uint32_t)(a * 255.f) * 0x1000000 + (uint32_t)(r * 255.f) * 0x10000 + (uint32_t)(g * 255.f) * 0x100 + (uint32_t)(b * 255.f) * 0x1; } \ No newline at end of file diff --git a/src/helpers/Color.hpp b/src/helpers/Color.hpp index 7ec55b0d..32ed39ee 100644 --- a/src/helpers/Color.hpp +++ b/src/helpers/Color.hpp @@ -8,9 +8,10 @@ class CColor { CColor(float r, float g, float b, float a); CColor(uint64_t); - float r = 0, g = 0, b = 0, a = 1.f; + float r = 0, g = 0, b = 0, a = 1.f; - uint32_t getAsHex(); + // AR32 + uint32_t getAsHex() const; CColor operator-(const CColor& c2) const { return CColor(r - c2.r, g - c2.g, b - c2.b, a - c2.a); diff --git a/src/helpers/Format.cpp b/src/helpers/Format.cpp index afc8b1c5..bc54b76e 100644 --- a/src/helpers/Format.cpp +++ b/src/helpers/Format.cpp @@ -252,7 +252,7 @@ DRMFormat FormatUtils::shmToDRM(SHMFormat shm) { } const SPixelFormat* FormatUtils::getPixelFormatFromDRM(DRMFormat drm) { - for (auto& fmt : GLES3_FORMATS) { + for (auto const& fmt : GLES3_FORMATS) { if (fmt.drmFormat == drm) return &fmt; } @@ -261,7 +261,7 @@ const SPixelFormat* FormatUtils::getPixelFormatFromDRM(DRMFormat drm) { } const SPixelFormat* FormatUtils::getPixelFormatFromGL(uint32_t glFormat, uint32_t glType, bool alpha) { - for (auto& fmt : GLES3_FORMATS) { + for (auto const& fmt : GLES3_FORMATS) { if (fmt.glFormat == (int)glFormat && fmt.glType == (int)glType && fmt.withAlpha == alpha) return &fmt; } diff --git a/src/helpers/MiscFunctions.cpp b/src/helpers/MiscFunctions.cpp index 53c0dc13..20c79a2b 100644 --- a/src/helpers/MiscFunctions.cpp +++ b/src/helpers/MiscFunctions.cpp @@ -180,7 +180,7 @@ void handleNoop(struct wl_listener* listener, void* data) { std::string escapeJSONStrings(const std::string& str) { std::ostringstream oss; - for (auto& c : str) { + for (auto const& c : str) { switch (c) { case '"': oss << "\\\""; break; case '\\': oss << "\\\\"; break; @@ -249,17 +249,17 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { return {WORKSPACE_INVALID}; } - std::set invalidWSes; + std::set invalidWSes; if (same_mon) { - for (auto& rule : g_pConfigManager->getAllWorkspaceRules()) { + for (auto const& rule : g_pConfigManager->getAllWorkspaceRules()) { const auto PMONITOR = g_pCompositor->getMonitorFromName(rule.monitor); if (PMONITOR && (PMONITOR->ID != g_pCompositor->m_pLastMonitor->ID)) invalidWSes.insert(rule.workspaceId); } } - int id = next ? g_pCompositor->m_pLastMonitor->activeWorkspaceID() : 0; - while (++id < INT_MAX) { + WORKSPACEID id = next ? g_pCompositor->m_pLastMonitor->activeWorkspaceID() : 0; + while (++id < LONG_MAX) { const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(id); if (!invalidWSes.contains(id) && (!PWORKSPACE || g_pCompositor->getWindowsOnWorkspace(id) == 0)) { result.id = id; @@ -296,18 +296,18 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { result.id = (int)PLUSMINUSRESULT.value(); - int remains = (int)result.id; + WORKSPACEID remains = result.id; - std::set invalidWSes; + std::set invalidWSes; // Collect all the workspaces we can't jump to. - for (auto& ws : g_pCompositor->m_vWorkspaces) { + for (auto const& ws : g_pCompositor->m_vWorkspaces) { if (ws->m_bIsSpecialWorkspace || (ws->m_iMonitorID != g_pCompositor->m_pLastMonitor->ID)) { // Can't jump to this workspace invalidWSes.insert(ws->m_iID); } } - for (auto& rule : g_pConfigManager->getAllWorkspaceRules()) { + for (auto const& rule : g_pConfigManager->getAllWorkspaceRules()) { const auto PMONITOR = g_pCompositor->getMonitorFromName(rule.monitor); if (!PMONITOR || PMONITOR->ID == g_pCompositor->m_pLastMonitor->ID) { // Can't be invalid @@ -318,8 +318,8 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { } // Prepare all named workspaces in case when we need them - std::vector namedWSes; - for (auto& ws : g_pCompositor->m_vWorkspaces) { + std::vector namedWSes; + for (auto const& ws : g_pCompositor->m_vWorkspaces) { if (ws->m_bIsSpecialWorkspace || (ws->m_iMonitorID != g_pCompositor->m_pLastMonitor->ID) || ws->m_iID >= 0) continue; @@ -347,19 +347,19 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { } else { // Just take a blind guess at where we'll probably end up - int activeWSID = g_pCompositor->m_pLastMonitor->activeWorkspace ? g_pCompositor->m_pLastMonitor->activeWorkspace->m_iID : 1; - int predictedWSID = activeWSID + remains; - int remainingWSes = 0; - char walkDir = in[1]; + WORKSPACEID activeWSID = g_pCompositor->m_pLastMonitor->activeWorkspace ? g_pCompositor->m_pLastMonitor->activeWorkspace->m_iID : 1; + WORKSPACEID predictedWSID = activeWSID + remains; + int remainingWSes = 0; + char walkDir = in[1]; // sanitize. 0 means invalid oob in - - predictedWSID = std::max(predictedWSID, 0); + predictedWSID = std::max(predictedWSID, static_cast(0)); // Count how many invalidWSes are in between (how bad the prediction was) - int beginID = in[1] == '+' ? activeWSID + 1 : predictedWSID; - int endID = in[1] == '+' ? predictedWSID : activeWSID; - auto begin = invalidWSes.upper_bound(beginID - 1); // upper_bound is >, we want >= - for (auto it = begin; *it <= endID && it != invalidWSes.end(); it++) { + WORKSPACEID beginID = in[1] == '+' ? activeWSID + 1 : predictedWSID; + WORKSPACEID endID = in[1] == '+' ? predictedWSID : activeWSID; + auto begin = invalidWSes.upper_bound(beginID - 1); // upper_bound is >, we want >= + for (auto it = begin; it != invalidWSes.end() && *it <= endID; it++) { remainingWSes++; } @@ -367,7 +367,7 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { if (activeWSID < 0) { // Behaviour similar to 'm' // Find current - int currentItem = -1; + size_t currentItem = -1; for (size_t i = 0; i < namedWSes.size(); i++) { if (namedWSes[i] == activeWSID) { currentItem = i; @@ -376,15 +376,15 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { } currentItem += remains; - currentItem = std::max(currentItem, 0); - if (currentItem >= (int)namedWSes.size()) { + currentItem = std::max(currentItem, static_cast(0)); + if (currentItem >= namedWSes.size()) { // At the seam between namedWSes and normal WSes. Behave like r+[diff] at imaginary ws 0 - int diff = currentItem - (namedWSes.size() - 1); - predictedWSID = diff; - int beginID = 1; - int endID = predictedWSID; - auto begin = invalidWSes.upper_bound(beginID - 1); // upper_bound is >, we want >= - for (auto it = begin; *it <= endID && it != invalidWSes.end(); it++) { + size_t diff = currentItem - (namedWSes.size() - 1); + predictedWSID = diff; + WORKSPACEID beginID = 1; + WORKSPACEID endID = predictedWSID; + auto begin = invalidWSes.upper_bound(beginID - 1); // upper_bound is >, we want >= + for (auto it = begin; it != invalidWSes.end() && *it <= endID; it++) { remainingWSes++; } walkDir = '+'; @@ -397,10 +397,10 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { // Go in the search direction for remainingWSes // The performance impact is directly proportional to the number of open and bound workspaces - int finalWSID = predictedWSID; + WORKSPACEID finalWSID = predictedWSID; if (walkDir == '-') { - int beginID = finalWSID; - int curID = finalWSID; + WORKSPACEID beginID = finalWSID; + WORKSPACEID curID = finalWSID; while (--curID > 0 && remainingWSes > 0) { if (!invalidWSes.contains(curID)) { remainingWSes--; @@ -411,9 +411,9 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { if (namedWSes.size()) { // Go to the named workspaces // Need remainingWSes more - int namedWSIdx = namedWSes.size() - remainingWSes; + auto namedWSIdx = namedWSes.size() - remainingWSes; // Sanitze - namedWSIdx = std::clamp(namedWSIdx, 0, (int)namedWSes.size() - 1); + namedWSIdx = std::clamp(namedWSIdx, static_cast(0), namedWSes.size() - static_cast(1)); finalWSID = namedWSes[namedWSIdx]; } else { // Couldn't find valid workspace in negative direction, search last first one back up positive direction @@ -425,7 +425,7 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { } } if (walkDir == '+') { - int curID = finalWSID; + WORKSPACEID curID = finalWSID; while (++curID < INT32_MAX && remainingWSes > 0) { if (!invalidWSes.contains(curID)) { remainingWSes--; @@ -460,10 +460,10 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { result.id = (int)PLUSMINUSRESULT.value(); // result now has +/- what we should move on mon - int remains = (int)result.id; + int remains = (int)result.id; - std::vector validWSes; - for (auto& ws : g_pCompositor->m_vWorkspaces) { + std::vector validWSes; + for (auto const& ws : g_pCompositor->m_vWorkspaces) { if (ws->m_bIsSpecialWorkspace || (ws->m_iMonitorID != g_pCompositor->m_pLastMonitor->ID && !onAllMonitors)) continue; @@ -472,7 +472,7 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { std::sort(validWSes.begin(), validWSes.end()); - int currentItem = -1; + ssize_t currentItem = -1; if (absolute) { // 1-index @@ -481,7 +481,7 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { // clamp if (currentItem < 0) { currentItem = 0; - } else if (currentItem >= (int)validWSes.size()) { + } else if (currentItem >= (ssize_t)validWSes.size()) { currentItem = validWSes.size() - 1; } } else { @@ -489,8 +489,8 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { remains = remains < 0 ? -((-remains) % validWSes.size()) : remains % validWSes.size(); // get the current item - int activeWSID = g_pCompositor->m_pLastMonitor->activeWorkspace ? g_pCompositor->m_pLastMonitor->activeWorkspace->m_iID : 1; - for (size_t i = 0; i < validWSes.size(); i++) { + WORKSPACEID activeWSID = g_pCompositor->m_pLastMonitor->activeWorkspace ? g_pCompositor->m_pLastMonitor->activeWorkspace->m_iID : 1; + for (ssize_t i = 0; i < (ssize_t)validWSes.size(); i++) { if (validWSes[i] == activeWSID) { currentItem = i; break; @@ -501,7 +501,7 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { currentItem += remains; // sanitize - if (currentItem >= (int)validWSes.size()) { + if (currentItem >= (ssize_t)validWSes.size()) { currentItem = currentItem % validWSes.size(); } else if (currentItem < 0) { currentItem = validWSes.size() + currentItem; @@ -547,9 +547,9 @@ std::optional cleanCmdForWorkspace(const std::string& inWorkspaceNa const std::string workspaceRule = "workspace " + inWorkspaceName; if (cmd[0] == '[') { - const int closingBracketIdx = cmd.find_last_of(']'); - auto tmpRules = cmd.substr(1, closingBracketIdx - 1); - cmd = cmd.substr(closingBracketIdx + 1); + const auto closingBracketIdx = cmd.find_last_of(']'); + auto tmpRules = cmd.substr(1, closingBracketIdx - 1); + cmd = cmd.substr(closingBracketIdx + 1); auto rulesList = CVarList(tmpRules, 0, ';'); @@ -628,27 +628,6 @@ void logSystemInfo() { Debug::log(NONE, "{}", execAndGet("cat /etc/os-release")); } -void matrixProjection(float mat[9], int w, int h, wl_output_transform tr) { - memset(mat, 0, sizeof(*mat) * 9); - - const float* t = transforms[tr]; - float x = 2.0f / w; - float y = 2.0f / h; - - // Rotation + reflection - mat[0] = x * t[0]; - mat[1] = x * t[1]; - mat[3] = y * t[3]; - mat[4] = y * t[4]; - - // Translation - mat[2] = -copysign(1.0f, mat[0] + mat[1]); - mat[5] = -copysign(1.0f, mat[3] + mat[4]); - - // Identity - mat[8] = 1.0f; -} - int64_t getPPIDof(int64_t pid) { #if defined(KERN_PROC_PID) int mib[] = { @@ -785,13 +764,13 @@ std::vector getBacktrace() { #ifdef HAS_EXECINFO void* bt[1024]; - size_t btSize; + int btSize; char** btSymbols; btSize = backtrace(bt, 1024); btSymbols = backtrace_symbols(bt, btSize); - for (size_t i = 0; i < btSize; ++i) { + for (auto i = 0; i < btSize; ++i) { callstack.emplace_back(SCallstackFrameInfo{bt[i], std::string{btSymbols[i]}}); } #else diff --git a/src/helpers/MiscFunctions.hpp b/src/helpers/MiscFunctions.hpp index 49e3bced..f696fc5d 100644 --- a/src/helpers/MiscFunctions.hpp +++ b/src/helpers/MiscFunctions.hpp @@ -6,6 +6,8 @@ #include "math/Math.hpp" #include #include +#include "../SharedDefs.hpp" +#include "../macros.hpp" struct SCallstackFrameInfo { void* adr = nullptr; @@ -13,7 +15,7 @@ struct SCallstackFrameInfo { }; struct SWorkspaceIDName { - int id = -1; + WORKSPACEID id = WORKSPACE_INVALID; std::string name; }; @@ -32,7 +34,6 @@ int64_t getPPIDof(int64_t pid); int64_t configStringToInt(const std::string&); Vector2D configStringToVector2D(const std::string&); std::optional getPlusMinusKeywordResult(std::string in, float relative); -void matrixProjection(float mat[9], int w, int h, wl_output_transform tr); double normalizeAngleRad(double ang); std::vector getBacktrace(); void throwError(const std::string& err); diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index 1cf1b069..091745df 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -1,6 +1,7 @@ #include "Monitor.hpp" #include "MiscFunctions.hpp" #include "math/Math.hpp" +#include "sync/SyncReleaser.hpp" #include "../Compositor.hpp" #include "../config/ConfigValue.hpp" #include "../protocols/GammaControl.hpp" @@ -8,13 +9,18 @@ #include "../protocols/LayerShell.hpp" #include "../protocols/PresentationTime.hpp" #include "../protocols/DRMLease.hpp" +#include "../protocols/DRMSyncobj.hpp" #include "../protocols/core/Output.hpp" #include "../managers/PointerManager.hpp" +#include "../managers/eventLoop/EventLoopManager.hpp" #include "../protocols/core/Compositor.hpp" #include "sync/SyncTimeline.hpp" #include +#include "debug/Log.hpp" #include +#include using namespace Hyprutils::String; +using namespace Hyprutils::Utils; int ratHandler(void* data) { g_pHyprRenderer->renderMonitor((CMonitor*)data); @@ -22,7 +28,7 @@ int ratHandler(void* data) { return 1; } -CMonitor::CMonitor() : state(this) { +CMonitor::CMonitor(SP output_) : state(this), output(output_) { ; } @@ -31,20 +37,34 @@ CMonitor::~CMonitor() { } void CMonitor::onConnect(bool noRule) { + CScopeGuard x = {[]() { g_pCompositor->arrangeMonitors(); }}; if (output->supportsExplicit) { inTimeline = CSyncTimeline::create(output->getBackend()->drmFD()); outTimeline = CSyncTimeline::create(output->getBackend()->drmFD()); } - listeners.frame = output->events.frame.registerListener([this](std::any d) { Events::listener_monitorFrame(this, nullptr); }); - listeners.destroy = output->events.destroy.registerListener([this](std::any d) { Events::listener_monitorDestroy(this, nullptr); }); - listeners.commit = output->events.commit.registerListener([this](std::any d) { Events::listener_monitorCommit(this, nullptr); }); + listeners.frame = output->events.frame.registerListener([this](std::any d) { Events::listener_monitorFrame(this, nullptr); }); + listeners.commit = output->events.commit.registerListener([this](std::any d) { Events::listener_monitorCommit(this, nullptr); }); listeners.needsFrame = output->events.needsFrame.registerListener([this](std::any d) { g_pCompositor->scheduleFrameForMonitor(this, Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME); }); + listeners.presented = output->events.present.registerListener([this](std::any d) { auto E = std::any_cast(d); - PROTO::presentation->onPresented(this, E.when, E.refresh, E.seq, E.flags); + PROTO::presentation->onPresented(self.lock(), E.when, E.refresh, E.seq, E.flags); + }); + + listeners.destroy = output->events.destroy.registerListener([this](std::any d) { + Debug::log(LOG, "Destroy called for monitor {}", szName); + + onDisconnect(true); + + output = nullptr; + m_bRenderingInitPassed = false; + + Debug::log(LOG, "Removing monitor {} from realMonitors", szName); + + std::erase_if(g_pCompositor->m_vRealMonitors, [&](SP& el) { return el.get() == this; }); }); listeners.state = output->events.state.registerListener([this](std::any d) { @@ -77,6 +97,7 @@ void CMonitor::onConnect(bool noRule) { tearingState.canTear = output->getBackend()->type() == Aquamarine::AQ_BACKEND_DRM; if (m_bEnabled) { + output->state->resetExplicitFences(); output->state->setEnabled(true); state.commit(); return; @@ -96,11 +117,12 @@ void CMonitor::onConnect(bool noRule) { createdByUser = true; // should be true. WL and Headless backends should be addable / removable // get monitor rule that matches - SMonitorRule monitorRule = g_pConfigManager->getMonitorRuleFor(*this); + SMonitorRule monitorRule = g_pConfigManager->getMonitorRuleFor(self.lock()); // if it's disabled, disable and ignore if (monitorRule.disabled) { + output->state->resetExplicitFences(); output->state->setEnabled(false); if (!state.commit()) @@ -137,6 +159,7 @@ void CMonitor::onConnect(bool noRule) { m_bEnabled = true; + output->state->resetExplicitFences(); output->state->setEnabled(true); // set mode, also applies @@ -152,7 +175,7 @@ void CMonitor::onConnect(bool noRule) { setupDefaultWS(monitorRule); - for (auto& ws : g_pCompositor->m_vWorkspaces) { + for (auto const& ws : g_pCompositor->m_vWorkspaces) { if (!valid(ws)) continue; @@ -173,10 +196,6 @@ void CMonitor::onConnect(bool noRule) { if (!activeMonitorRule.mirrorOf.empty()) setMirror(activeMonitorRule.mirrorOf); - g_pEventManager->postEvent(SHyprIPCEvent{"monitoradded", szName}); - g_pEventManager->postEvent(SHyprIPCEvent{"monitoraddedv2", std::format("{},{},{}", ID, szName, szShortDescription)}); - EMIT_HOOK_EVENT("monitorAdded", this); - if (!g_pCompositor->m_pLastMonitor) // set the last monitor if it isnt set yet g_pCompositor->setActiveMonitor(this); @@ -188,7 +207,7 @@ void CMonitor::onConnect(bool noRule) { // verify last mon valid bool found = false; - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { if (m == g_pCompositor->m_pLastMonitor) { found = true; break; @@ -205,9 +224,20 @@ void CMonitor::onConnect(bool noRule) { PROTO::gamma->applyGammaToState(this); events.connect.emit(); + + g_pEventManager->postEvent(SHyprIPCEvent{"monitoradded", szName}); + g_pEventManager->postEvent(SHyprIPCEvent{"monitoraddedv2", std::format("{},{},{}", ID, szName, szShortDescription)}); + EMIT_HOOK_EVENT("monitorAdded", this); } void CMonitor::onDisconnect(bool destroy) { + CScopeGuard x = {[this]() { + if (g_pCompositor->m_bIsShuttingDown) + return; + g_pEventManager->postEvent(SHyprIPCEvent{"monitorremoved", szName}); + EMIT_HOOK_EVENT("monitorRemoved", this); + g_pCompositor->arrangeMonitors(); + }}; if (renderTimer) { wl_event_source_remove(renderTimer); @@ -223,7 +253,7 @@ void CMonitor::onDisconnect(bool destroy) { // Cleanup everything. Move windows back, snap cursor, shit. CMonitor* BACKUPMON = nullptr; - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { if (m.get() != this) { BACKUPMON = m.get(); break; @@ -240,7 +270,7 @@ void CMonitor::onDisconnect(bool destroy) { } if (!mirrors.empty()) { - for (auto& m : mirrors) { + for (auto const& m : mirrors) { m->setMirror(""); } @@ -253,7 +283,7 @@ void CMonitor::onDisconnect(bool destroy) { listeners.commit.reset(); for (size_t i = 0; i < 4; ++i) { - for (auto& ls : m_aLayerSurfaceLayers[i]) { + for (auto const& ls : m_aLayerSurfaceLayers[i]) { if (ls->layerSurface && !ls->fadingOut) ls->layerSurface->sendClosed(); } @@ -262,9 +292,6 @@ void CMonitor::onDisconnect(bool destroy) { Debug::log(LOG, "Removed monitor {}!", szName); - g_pEventManager->postEvent(SHyprIPCEvent{"monitorremoved", szName}); - EMIT_HOOK_EVENT("monitorRemoved", this); - if (!BACKUPMON) { Debug::log(WARN, "Unplugged last monitor, entering an unsafe state. Good luck my friend."); g_pCompositor->enterUnsafeState(); @@ -279,13 +306,13 @@ void CMonitor::onDisconnect(bool destroy) { // move workspaces std::deque wspToMove; - for (auto& w : g_pCompositor->m_vWorkspaces) { + for (auto const& w : g_pCompositor->m_vWorkspaces) { if (w->m_iMonitorID == ID || !g_pCompositor->getMonitorFromID(w->m_iMonitorID)) { wspToMove.push_back(w); } } - for (auto& w : wspToMove) { + for (auto const& w : wspToMove) { w->m_szLastMonitor = szName; g_pCompositor->moveWorkspaceToMonitor(w, BACKUPMON); w->startAnim(true, true, true); @@ -300,6 +327,7 @@ void CMonitor::onDisconnect(bool destroy) { activeWorkspace->m_bVisible = false; activeWorkspace.reset(); + output->state->resetExplicitFences(); output->state->setEnabled(false); if (!state.commit()) @@ -312,7 +340,7 @@ void CMonitor::onDisconnect(bool destroy) { int mostHz = 0; CMonitor* pMonitorMostHz = nullptr; - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { if (m->refreshRate > mostHz && m.get() != this) { pMonitorMostHz = m.get(); mostHz = m->refreshRate; @@ -357,7 +385,7 @@ bool CMonitor::shouldSkipScheduleFrameOnMouseEvent() { *PNOBREAK && output->state->state().adaptiveSync && activeWorkspace && activeWorkspace->m_bHasFullscreenWindow && activeWorkspace->m_efFullscreenMode == FSMODE_FULLSCREEN; // keep requested minimum refresh rate - if (shouldSkip && *PMINRR && lastPresentationTimer.getMillis() > 1000 / *PMINRR) { + if (shouldSkip && *PMINRR && lastPresentationTimer.getMillis() > 1000.0f / *PMINRR) { // damage whole screen because some previous cursor box damages were skipped damage.damageEntire(); return false; @@ -382,8 +410,8 @@ bool CMonitor::matchesStaticSelector(const std::string& selector) const { } } -int CMonitor::findAvailableDefaultWS() { - for (size_t i = 1; i < INT32_MAX; ++i) { +WORKSPACEID CMonitor::findAvailableDefaultWS() { + for (WORKSPACEID i = 1; i < LONG_MAX; ++i) { if (g_pCompositor->getWorkspaceByID(i)) continue; @@ -393,7 +421,7 @@ int CMonitor::findAvailableDefaultWS() { return i; } - return INT32_MAX; // shouldn't be reachable + return LONG_MAX; // shouldn't be reachable } void CMonitor::setupDefaultWS(const SMonitorRule& monitorRule) { @@ -468,7 +496,7 @@ void CMonitor::setMirror(const std::string& mirrorOf) { pMirrorOf = nullptr; // set rule - const auto RULE = g_pConfigManager->getMonitorRuleFor(*this); + const auto RULE = g_pConfigManager->getMonitorRuleFor(self.lock()); vecPosition = RULE.offset; @@ -496,7 +524,7 @@ void CMonitor::setMirror(const std::string& mirrorOf) { g_pHyprRenderer->applyMonitorRule(this, (SMonitorRule*)&RULE, true); // will apply the offset and stuff } else { CMonitor* BACKUPMON = nullptr; - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { if (m.get() != this) { BACKUPMON = m.get(); break; @@ -505,13 +533,13 @@ void CMonitor::setMirror(const std::string& mirrorOf) { // move all the WS std::deque wspToMove; - for (auto& w : g_pCompositor->m_vWorkspaces) { + for (auto const& w : g_pCompositor->m_vWorkspaces) { if (w->m_iMonitorID == ID) { wspToMove.push_back(w); } } - for (auto& w : wspToMove) { + for (auto const& w : wspToMove) { g_pCompositor->moveWorkspaceToMonitor(w, BACKUPMON); w->startAnim(true, true, true); } @@ -585,7 +613,7 @@ void CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bo pWorkspace->startAnim(true, ANIMTOLEFT); // move pinned windows - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (w->m_pWorkspace == POLDWORKSPACE && w->m_bPinned) w->moveToWorkspace(pWorkspace); } @@ -631,7 +659,7 @@ void CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bo g_pCompositor->updateFullscreenFadeOnWorkspace(activeSpecialWorkspace); } -void CMonitor::changeWorkspace(const int& id, bool internal, bool noMouseMove, bool noFocus) { +void CMonitor::changeWorkspace(const WORKSPACEID& id, bool internal, bool noMouseMove, bool noFocus) { changeWorkspace(g_pCompositor->getWorkspaceByID(id), internal, noMouseMove, noFocus); } @@ -694,14 +722,14 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) { if (animate) pWorkspace->startAnim(true, true); - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (w->m_pWorkspace == pWorkspace) { w->m_iMonitorID = ID; w->updateSurfaceScaleTransformDetails(); w->setAnimationsToMove(); const auto MIDDLE = w->middle(); - if (w->m_bIsFloating && !VECINRECT(MIDDLE, vecPosition.x, vecPosition.y, vecPosition.x + vecSize.x, vecPosition.y + vecSize.y) && w->m_iX11Type != 2) { + if (w->m_bIsFloating && !VECINRECT(MIDDLE, vecPosition.x, vecPosition.y, vecPosition.x + vecSize.x, vecPosition.y + vecSize.y) && !w->isX11OverrideRedirect()) { // if it's floating and the middle isnt on the current mon, move it to the center const auto PMONFROMMIDDLE = g_pCompositor->getMonitorFromVector(MIDDLE); Vector2D pos = w->m_vRealPosition.goal(); @@ -738,7 +766,7 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) { g_pCompositor->updateSuspendedStates(); } -void CMonitor::setSpecialWorkspace(const int& id) { +void CMonitor::setSpecialWorkspace(const WORKSPACEID& id) { setSpecialWorkspace(g_pCompositor->getWorkspaceByID(id)); } @@ -751,19 +779,16 @@ Vector2D CMonitor::middle() { } void CMonitor::updateMatrix() { - matrixIdentity(projMatrix.data()); - if (transform != WL_OUTPUT_TRANSFORM_NORMAL) { - matrixTranslate(projMatrix.data(), vecPixelSize.x / 2.0, vecPixelSize.y / 2.0); - matrixTransform(projMatrix.data(), wlTransformToHyprutils(transform)); - matrixTranslate(projMatrix.data(), -vecTransformedSize.x / 2.0, -vecTransformedSize.y / 2.0); - } + projMatrix = Mat3x3::identity(); + if (transform != WL_OUTPUT_TRANSFORM_NORMAL) + projMatrix.translate(vecPixelSize / 2.0).transform(wlTransformToHyprutils(transform)).translate(-vecTransformedSize / 2.0); } -int64_t CMonitor::activeWorkspaceID() { +WORKSPACEID CMonitor::activeWorkspaceID() { return activeWorkspace ? activeWorkspace->m_iID : 0; } -int64_t CMonitor::activeSpecialWorkspaceID() { +WORKSPACEID CMonitor::activeSpecialWorkspaceID() { return activeSpecialWorkspace ? activeSpecialWorkspace->m_iID : 0; } @@ -771,20 +796,28 @@ CBox CMonitor::logicalBox() { return {vecPosition, vecSize}; } -static void onDoneSource(void* data) { - auto pMonitor = (CMonitor*)data; - - if (!PROTO::outputs.contains(pMonitor->szName)) +void CMonitor::scheduleDone() { + if (doneScheduled) return; - PROTO::outputs.at(pMonitor->szName)->sendDone(); + doneScheduled = true; + + g_pEventLoopManager->doLater([M = self] { + if (!M) // if M is gone, we got destroyed, doesn't matter. + return; + + if (!PROTO::outputs.contains(M->szName)) + return; + + PROTO::outputs.at(M->szName)->sendDone(); + M->doneScheduled = false; + }); } -void CMonitor::scheduleDone() { - if (doneSource) - return; - - doneSource = wl_event_loop_add_idle(g_pCompositor->m_sWLEventLoop, ::onDoneSource, this); +void CMonitor::setCTM(const Mat3x3& ctm_) { + ctm = ctm_; + ctmUpdated = true; + g_pCompositor->scheduleFrameForMonitor(this, Aquamarine::IOutput::scheduleFrameReason::AQ_SCHEDULE_NEEDS_FRAME); } bool CMonitor::attemptDirectScanout() { @@ -808,28 +841,92 @@ bool CMonitor::attemptDirectScanout() { if (!PSURFACE->current.buffer || !PSURFACE->current.texture || !PSURFACE->current.texture->m_pEglImage /* dmabuf */) return false; + Debug::log(TRACE, "attemptDirectScanout: surface {:x} passed, will attempt", (uintptr_t)PSURFACE.get()); + // FIXME: make sure the buffer actually follows the available scanout dmabuf formats // and comes from the appropriate device. This may implode on multi-gpu!! + + const auto params = PSURFACE->current.buffer->buffer->dmabuf(); + // scanout buffer isn't dmabuf, so no scanout + if (!params.success) + return false; + + // entering into scanout, so save monitor format + if (lastScanout.expired()) + prevDrmFormat = drmFormat; + + if (drmFormat != params.format) { + output->state->setFormat(params.format); + drmFormat = params.format; + } + output->state->setBuffer(PSURFACE->current.buffer->buffer.lock()); output->state->setPresentationMode(tearingState.activelyTearing ? Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_IMMEDIATE : Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_VSYNC); - if (!state.test()) + if (!state.test()) { + Debug::log(TRACE, "attemptDirectScanout: failed basic test"); return false; + } + + auto explicitOptions = g_pHyprRenderer->getExplicitSyncSettings(); + + // wait for the explicit fence if present, and if kms explicit is allowed + bool DOEXPLICIT = PSURFACE->syncobj && PSURFACE->syncobj->current.acquireTimeline && PSURFACE->syncobj->current.acquireTimeline->timeline && explicitOptions.explicitKMSEnabled; + int explicitWaitFD = -1; + if (DOEXPLICIT) { + explicitWaitFD = PSURFACE->syncobj->current.acquireTimeline->timeline->exportAsSyncFileFD(PSURFACE->syncobj->current.acquirePoint); + if (explicitWaitFD < 0) + Debug::log(TRACE, "attemptDirectScanout: failed to acquire an explicit wait fd"); + } + DOEXPLICIT = DOEXPLICIT && explicitWaitFD >= 0; + + auto cleanup = CScopeGuard([explicitWaitFD, this]() { + output->state->resetExplicitFences(); + if (explicitWaitFD >= 0) + close(explicitWaitFD); + }); timespec now; clock_gettime(CLOCK_MONOTONIC, &now); - Debug::log(TRACE, "presentFeedback for DS"); - PSURFACE->presentFeedback(&now, this, true); + PSURFACE->presentFeedback(&now, self.lock()); output->state->addDamage(CBox{{}, vecPixelSize}); + output->state->resetExplicitFences(); - if (state.commit()) { + if (DOEXPLICIT) { + Debug::log(TRACE, "attemptDirectScanout: setting IN_FENCE for aq to {}", explicitWaitFD); + output->state->setExplicitInFence(explicitWaitFD); + } + + bool ok = output->commit(); + + if (!ok && DOEXPLICIT) { + Debug::log(TRACE, "attemptDirectScanout: EXPLICIT SYNC FAILED: commit() returned false. Resetting fences and retrying, might result in glitches."); + output->state->resetExplicitFences(); + + ok = output->commit(); + } + + if (ok) { if (lastScanout.expired()) { lastScanout = PCANDIDATE; Debug::log(LOG, "Entered a direct scanout to {:x}: \"{}\"", (uintptr_t)PCANDIDATE.get(), PCANDIDATE->m_szTitle); } + + // delay explicit sync feedback until kms release of the buffer + if (DOEXPLICIT) { + Debug::log(TRACE, "attemptDirectScanout: Delaying explicit sync release feedback until kms release"); + PSURFACE->current.buffer->releaser->drop(); + + PSURFACE->current.buffer->buffer->hlEvents.backendRelease2 = PSURFACE->current.buffer->buffer->events.backendRelease.registerListener([PSURFACE](std::any d) { + const bool DOEXPLICIT = PSURFACE->syncobj && PSURFACE->syncobj->current.releaseTimeline && PSURFACE->syncobj->current.releaseTimeline->timeline; + if (DOEXPLICIT) + PSURFACE->syncobj->current.releaseTimeline->timeline->signal(PSURFACE->syncobj->current.releasePoint); + }); + } } else { + Debug::log(TRACE, "attemptDirectScanout: failed to scanout surface"); lastScanout.reset(); return false; } @@ -837,6 +934,11 @@ bool CMonitor::attemptDirectScanout() { return true; } +void CMonitor::debugLastPresentation(const std::string& message) { + Debug::log(TRACE, "{} (last presentation {} - {} fps)", message, lastPresentationTimer.getMillis(), + lastPresentationTimer.getMillis() > 0 ? 1000.0f / lastPresentationTimer.getMillis() : 0.0f); +} + CMonitorState::CMonitorState(CMonitor* owner) { m_pOwner = owner; } @@ -846,17 +948,20 @@ CMonitorState::~CMonitorState() { } void CMonitorState::ensureBufferPresent() { - if (!m_pOwner->output->state->state().enabled) { + const auto STATE = m_pOwner->output->state->state(); + if (!STATE.enabled) { Debug::log(TRACE, "CMonitorState::ensureBufferPresent: Ignoring, monitor is not enabled"); return; } - if (m_pOwner->output->state->state().buffer) - return; + if (STATE.buffer) { + if (const auto params = STATE.buffer->dmabuf(); params.success && params.format == m_pOwner->drmFormat) + return; + } // this is required for modesetting being possible and might be missing in case of first tests in the renderer // where we test modes and buffers - Debug::log(LOG, "CMonitorState::ensureBufferPresent: no buffer, attaching one from the swapchain for modeset being possible"); + Debug::log(LOG, "CMonitorState::ensureBufferPresent: no buffer or mismatched format, attaching one from the swapchain for modeset being possible"); m_pOwner->output->state->setBuffer(m_pOwner->output->swapchain->next(nullptr)); m_pOwner->output->swapchain->rollback(); // restore the counter, don't advance the swapchain } @@ -890,7 +995,7 @@ bool CMonitorState::updateSwapchain() { Debug::log(WARN, "updateSwapchain: No mode?"); return true; } - options.format = STATE.drmFormat; + options.format = m_pOwner->drmFormat; options.scanout = true; options.length = 2; options.size = MODE->pixelSize; diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index 32fc768a..ad8a823b 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -59,7 +59,7 @@ class CMonitorState { class CMonitor { public: - CMonitor(); + CMonitor(SP output); ~CMonitor(); Vector2D vecPosition = Vector2D(-1, -1); // means unset @@ -70,7 +70,7 @@ class CMonitor { bool primary = false; - uint64_t ID = -1; + MONITORID ID = MONITOR_INVALID; PHLWORKSPACE activeWorkspace = nullptr; PHLWORKSPACE activeSpecialWorkspace = nullptr; float setScale = 1; // scale set by cfg @@ -96,10 +96,12 @@ class CMonitor { bool scheduledRecalc = false; wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL; float xwaylandScale = 1.f; - std::array projMatrix = {0}; + Mat3x3 projMatrix; std::optional forceSize; SP currentMode; SP cursorSwapchain; + uint32_t drmFormat = DRM_FORMAT_INVALID; + uint32_t prevDrmFormat = DRM_FORMAT_INVALID; bool dpmsStatus = true; bool vrrActive = false; // this can be TRUE even if VRR is not active in the case that this display does not support it. @@ -121,8 +123,7 @@ class CMonitor { // explicit sync SP inTimeline; SP outTimeline; - uint64_t lastWaitPoint = 0; - uint64_t commitSeq = 0; + uint64_t commitSeq = 0; WP self; @@ -130,6 +131,10 @@ class CMonitor { CMonitor* pMirrorOf = nullptr; std::vector mirrors; + // ctm + Mat3x3 ctm = Mat3x3::identity(); + bool ctmUpdated = false; + // for tearing PHLWINDOWREF solitaryClient; @@ -156,31 +161,34 @@ class CMonitor { std::array, 4> m_aLayerSurfaceLayers; // methods - void onConnect(bool noRule); - void onDisconnect(bool destroy = false); - void addDamage(const pixman_region32_t* rg); - void addDamage(const CRegion* rg); - void addDamage(const CBox* box); - bool shouldSkipScheduleFrameOnMouseEvent(); - void setMirror(const std::string&); - bool isMirror(); - bool matchesStaticSelector(const std::string& selector) const; - float getDefaultScale(); - void changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal = false, bool noMouseMove = false, bool noFocus = false); - void changeWorkspace(const int& id, bool internal = false, bool noMouseMove = false, bool noFocus = false); - void setSpecialWorkspace(const PHLWORKSPACE& pWorkspace); - void setSpecialWorkspace(const int& id); - void moveTo(const Vector2D& pos); - Vector2D middle(); - void updateMatrix(); - int64_t activeWorkspaceID(); - int64_t activeSpecialWorkspaceID(); - CBox logicalBox(); - void scheduleDone(); - bool attemptDirectScanout(); + void onConnect(bool noRule); + void onDisconnect(bool destroy = false); + void addDamage(const pixman_region32_t* rg); + void addDamage(const CRegion* rg); + void addDamage(const CBox* box); + bool shouldSkipScheduleFrameOnMouseEvent(); + void setMirror(const std::string&); + bool isMirror(); + bool matchesStaticSelector(const std::string& selector) const; + float getDefaultScale(); + void changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal = false, bool noMouseMove = false, bool noFocus = false); + void changeWorkspace(const WORKSPACEID& id, bool internal = false, bool noMouseMove = false, bool noFocus = false); + void setSpecialWorkspace(const PHLWORKSPACE& pWorkspace); + void setSpecialWorkspace(const WORKSPACEID& id); + void moveTo(const Vector2D& pos); + Vector2D middle(); + void updateMatrix(); + WORKSPACEID activeWorkspaceID(); + WORKSPACEID activeSpecialWorkspaceID(); + CBox logicalBox(); + void scheduleDone(); + bool attemptDirectScanout(); + void setCTM(const Mat3x3& ctm); - bool m_bEnabled = false; - bool m_bRenderingInitPassed = false; + void debugLastPresentation(const std::string& message); + + bool m_bEnabled = false; + bool m_bRenderingInitPassed = false; // For the list lookup @@ -189,10 +197,10 @@ class CMonitor { } private: - void setupDefaultWS(const SMonitorRule&); - int findAvailableDefaultWS(); + void setupDefaultWS(const SMonitorRule&); + WORKSPACEID findAvailableDefaultWS(); - wl_event_source* doneSource = nullptr; + bool doneScheduled = false; struct { CHyprSignalListener frame; diff --git a/src/helpers/SdDaemon.cpp b/src/helpers/SdDaemon.cpp index 25e0ca3b..80944794 100644 --- a/src/helpers/SdDaemon.cpp +++ b/src/helpers/SdDaemon.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include namespace Systemd { int SdBooted(void) { diff --git a/src/helpers/Timer.cpp b/src/helpers/Timer.cpp index ec530df4..e00c5918 100644 --- a/src/helpers/Timer.cpp +++ b/src/helpers/Timer.cpp @@ -1,4 +1,5 @@ #include "Timer.hpp" +#include void CTimer::reset() { m_tpLastReset = std::chrono::steady_clock::now(); @@ -8,8 +9,8 @@ std::chrono::steady_clock::duration CTimer::getDuration() { return std::chrono::steady_clock::now() - m_tpLastReset; } -int CTimer::getMillis() { - return std::chrono::duration_cast(getDuration()).count(); +float CTimer::getMillis() { + return std::chrono::duration_cast(getDuration()).count() / 1000.f; } float CTimer::getSeconds() { diff --git a/src/helpers/Timer.hpp b/src/helpers/Timer.hpp index a6d1aeed..a58225d0 100644 --- a/src/helpers/Timer.hpp +++ b/src/helpers/Timer.hpp @@ -6,7 +6,7 @@ class CTimer { public: void reset(); float getSeconds(); - int getMillis(); + float getMillis(); const std::chrono::steady_clock::time_point& chrono() const; private: diff --git a/src/helpers/WLListener.cpp b/src/helpers/WLListener.cpp deleted file mode 100644 index 2ea5c0b6..00000000 --- a/src/helpers/WLListener.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include "WLListener.hpp" -#include "MiscFunctions.hpp" -#include -#include "../debug/Log.hpp" -#include "Watchdog.hpp" - -void handleWrapped(wl_listener* listener, void* data) { - CHyprWLListener::SWrapper* pWrap = wl_container_of(listener, pWrap, m_sListener); - - if (g_pWatchdog) - g_pWatchdog->startWatching(); - - try { - pWrap->m_pSelf->emit(data); - } catch (std::exception& e) { Debug::log(ERR, "Listener {} threw or timed out and was killed by Watchdog!!! This is bad. what(): {}", (uintptr_t)listener, e.what()); } - - if (g_pWatchdog) - g_pWatchdog->endWatching(); -} - -CHyprWLListener::CHyprWLListener(wl_signal* pSignal, std::function const& callback, void* pOwner) { - initCallback(pSignal, callback, pOwner); -} - -CHyprWLListener::CHyprWLListener() { - m_swWrapper.m_pSelf = this; - m_swWrapper.m_sListener.notify = &handleWrapped; - wl_list_init(&m_swWrapper.m_sListener.link); -} - -CHyprWLListener::~CHyprWLListener() { - removeCallback(); -} - -void CHyprWLListener::removeCallback() { - if (isConnected()) { - Debug::log(LOG, "Callback {:x} -> {:x}, {} removed.", (uintptr_t)&m_pCallback, (uintptr_t)&m_pOwner, m_szAuthor); - wl_list_remove(&m_swWrapper.m_sListener.link); - wl_list_init(&m_swWrapper.m_sListener.link); - } -} - -bool CHyprWLListener::isConnected() { - return !wl_list_empty(&m_swWrapper.m_sListener.link); -} - -void CHyprWLListener::initCallback(wl_signal* pSignal, std::function const& callback, void* pOwner, std::string author) { - if (isConnected()) { - Debug::log(ERR, "Tried to connect a listener twice?!"); - return; - } - - m_pOwner = pOwner; - m_pCallback = callback; - m_szAuthor = author; - - addWLSignal(pSignal, &m_swWrapper.m_sListener, pOwner, m_szAuthor); -} - -void CHyprWLListener::emit(void* data) { - m_pCallback(m_pOwner, data); -} diff --git a/src/helpers/WLListener.hpp b/src/helpers/WLListener.hpp deleted file mode 100644 index 621458e6..00000000 --- a/src/helpers/WLListener.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include -#include -#include - -class CHyprWLListener { - public: - CHyprWLListener(wl_signal*, std::function const&, void* owner); - CHyprWLListener(); - ~CHyprWLListener(); - - CHyprWLListener(const CHyprWLListener&) = delete; - CHyprWLListener(CHyprWLListener&&) = delete; - CHyprWLListener& operator=(const CHyprWLListener&) = delete; - CHyprWLListener& operator=(CHyprWLListener&&) = delete; - - void initCallback(wl_signal*, std::function const&, void* owner, std::string author = ""); - - void removeCallback(); - - bool isConnected(); - - struct SWrapper { - wl_listener m_sListener; - CHyprWLListener* m_pSelf; - }; - - void emit(void*); - - private: - SWrapper m_swWrapper; - - void* m_pOwner = nullptr; - - std::function m_pCallback = nullptr; - - std::string m_szAuthor = ""; -}; \ No newline at end of file diff --git a/src/helpers/Watchdog.cpp b/src/helpers/Watchdog.cpp index b9f654da..c7ff648b 100644 --- a/src/helpers/Watchdog.cpp +++ b/src/helpers/Watchdog.cpp @@ -18,15 +18,14 @@ CWatchdog::CWatchdog() { m_pWatchdog = std::make_unique([this] { static auto PTIMEOUT = CConfigValue("debug:watchdog_timeout"); - while (1337) { - std::unique_lock lk(m_mWatchdogMutex); + m_bWatchdogInitialized = true; + while (!m_bExitThread) { + std::unique_lock lk(m_mWatchdogMutex); if (!m_bWillWatch) - m_cvWatchdogCondition.wait(lk, [this] { return m_bNotified; }); - else { - if (m_cvWatchdogCondition.wait_for(lk, std::chrono::milliseconds((int)(*PTIMEOUT * 1000.0)), [this] { return m_bNotified; }) == false) - pthread_kill(m_iMainThreadPID, SIGUSR1); - } + m_cvWatchdogCondition.wait(lk, [this] { return m_bNotified || m_bExitThread; }); + else if (m_cvWatchdogCondition.wait_for(lk, std::chrono::milliseconds((int)(*PTIMEOUT * 1000.0)), [this] { return m_bNotified || m_bExitThread; }) == false) + pthread_kill(m_iMainThreadPID, SIGUSR1); if (m_bExitThread) break; diff --git a/src/helpers/Watchdog.hpp b/src/helpers/Watchdog.hpp index 7bb499d6..b16cb518 100644 --- a/src/helpers/Watchdog.hpp +++ b/src/helpers/Watchdog.hpp @@ -11,21 +11,23 @@ class CWatchdog { CWatchdog(); ~CWatchdog(); - void startWatching(); - void endWatching(); + void startWatching(); + void endWatching(); + + std::atomic m_bWatchdogInitialized{false}; private: std::chrono::high_resolution_clock::time_point m_tTriggered; pthread_t m_iMainThreadPID = 0; - bool m_bWatching = false; - bool m_bWillWatch = false; + std::atomic m_bWatching = false; + std::atomic m_bWillWatch = false; std::unique_ptr m_pWatchdog; std::mutex m_mWatchdogMutex; - bool m_bNotified = false; - bool m_bExitThread = false; + std::atomic m_bNotified = false; + std::atomic m_bExitThread = false; std::condition_variable m_cvWatchdogCondition; }; diff --git a/src/helpers/math/Math.cpp b/src/helpers/math/Math.cpp index fccfd636..d111690e 100644 --- a/src/helpers/math/Math.cpp +++ b/src/helpers/math/Math.cpp @@ -1,6 +1,4 @@ #include "Math.hpp" -#include -#include Hyprutils::Math::eTransform wlTransformToHyprutils(wl_output_transform t) { switch (t) { @@ -17,124 +15,6 @@ Hyprutils::Math::eTransform wlTransformToHyprutils(wl_output_transform t) { return Hyprutils::Math::eTransform::HYPRUTILS_TRANSFORM_NORMAL; } -void matrixIdentity(float mat[9]) { - static const float identity[9] = { - 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, - }; - memcpy(mat, identity, sizeof(identity)); -} - -void matrixMultiply(float mat[9], const float a[9], const float b[9]) { - float product[9]; - - product[0] = a[0] * b[0] + a[1] * b[3] + a[2] * b[6]; - product[1] = a[0] * b[1] + a[1] * b[4] + a[2] * b[7]; - product[2] = a[0] * b[2] + a[1] * b[5] + a[2] * b[8]; - - product[3] = a[3] * b[0] + a[4] * b[3] + a[5] * b[6]; - product[4] = a[3] * b[1] + a[4] * b[4] + a[5] * b[7]; - product[5] = a[3] * b[2] + a[4] * b[5] + a[5] * b[8]; - - product[6] = a[6] * b[0] + a[7] * b[3] + a[8] * b[6]; - product[7] = a[6] * b[1] + a[7] * b[4] + a[8] * b[7]; - product[8] = a[6] * b[2] + a[7] * b[5] + a[8] * b[8]; - - memcpy(mat, product, sizeof(product)); -} - -void matrixTranspose(float mat[9], const float a[9]) { - float transposition[9] = { - a[0], a[3], a[6], a[1], a[4], a[7], a[2], a[5], a[8], - }; - memcpy(mat, transposition, sizeof(transposition)); -} - -void matrixTranslate(float mat[9], float x, float y) { - float translate[9] = { - 1.0f, 0.0f, x, 0.0f, 1.0f, y, 0.0f, 0.0f, 1.0f, - }; - matrixMultiply(mat, mat, translate); -} - -void matrixScale(float mat[9], float x, float y) { - float scale[9] = { - x, 0.0f, 0.0f, 0.0f, y, 0.0f, 0.0f, 0.0f, 1.0f, - }; - matrixMultiply(mat, mat, scale); -} - -void matrixRotate(float mat[9], float rad) { - float rotate[9] = { - cos(rad), -sin(rad), 0.0f, sin(rad), cos(rad), 0.0f, 0.0f, 0.0f, 1.0f, - }; - matrixMultiply(mat, mat, rotate); -} - -const std::unordered_map>& getTransforms() { - static std::unordered_map> transforms = { - {HYPRUTILS_TRANSFORM_NORMAL, {1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}}, - {HYPRUTILS_TRANSFORM_90, {0.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}}, - {HYPRUTILS_TRANSFORM_180, {-1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f}}, - {HYPRUTILS_TRANSFORM_270, {0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}}, - {HYPRUTILS_TRANSFORM_FLIPPED, {-1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f}}, - {HYPRUTILS_TRANSFORM_FLIPPED_90, {0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}}, - {HYPRUTILS_TRANSFORM_FLIPPED_180, {1.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f}}, - {HYPRUTILS_TRANSFORM_FLIPPED_270, {0.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}}, - }; - return transforms; -} - -void matrixTransform(float mat[9], eTransform transform) { - matrixMultiply(mat, mat, getTransforms().at(transform).data()); -} - -void matrixProjection(float mat[9], int width, int height, eTransform transform) { - memset(mat, 0, sizeof(*mat) * 9); - - const float* t = getTransforms().at(transform).data(); - float x = 2.0f / width; - float y = 2.0f / height; - - // Rotation + reflection - mat[0] = x * t[0]; - mat[1] = x * t[1]; - mat[3] = y * -t[3]; - mat[4] = y * -t[4]; - - // Translation - mat[2] = -copysign(1.0f, mat[0] + mat[1]); - mat[5] = -copysign(1.0f, mat[3] + mat[4]); - - // Identity - mat[8] = 1.0f; -} - -void projectBox(float mat[9], CBox& box, eTransform transform, float rotation, const float projection[9]) { - double x = box.x; - double y = box.y; - double width = box.width; - double height = box.height; - - matrixIdentity(mat); - matrixTranslate(mat, x, y); - - if (rotation != 0) { - matrixTranslate(mat, width / 2, height / 2); - matrixRotate(mat, rotation); - matrixTranslate(mat, -width / 2, -height / 2); - } - - matrixScale(mat, width, height); - - if (transform != HYPRUTILS_TRANSFORM_NORMAL) { - matrixTranslate(mat, 0.5, 0.5); - matrixTransform(mat, transform); - matrixTranslate(mat, -0.5, -0.5); - } - - matrixMultiply(mat, projection, mat); -} - wl_output_transform invertTransform(wl_output_transform tr) { if ((tr & WL_OUTPUT_TRANSFORM_90) && !(tr & WL_OUTPUT_TRANSFORM_FLIPPED)) tr = (wl_output_transform)(tr ^ (int)WL_OUTPUT_TRANSFORM_180); diff --git a/src/helpers/math/Math.hpp b/src/helpers/math/Math.hpp index f57cef93..9b73a20f 100644 --- a/src/helpers/math/Math.hpp +++ b/src/helpers/math/Math.hpp @@ -4,17 +4,9 @@ // includes box and vector as well #include +#include using namespace Hyprutils::Math; eTransform wlTransformToHyprutils(wl_output_transform t); -void projectBox(float mat[9], CBox& box, eTransform transform, float rotation, const float projection[9]); -void matrixProjection(float mat[9], int width, int height, eTransform transform); -void matrixTransform(float mat[9], eTransform transform); -void matrixRotate(float mat[9], float rad); -void matrixScale(float mat[9], float x, float y); -void matrixTranslate(float mat[9], float x, float y); -void matrixTranspose(float mat[9], const float a[9]); -void matrixMultiply(float mat[9], const float a[9], const float b[9]); -void matrixIdentity(float mat[9]); wl_output_transform invertTransform(wl_output_transform tr); diff --git a/src/helpers/sync/SyncReleaser.cpp b/src/helpers/sync/SyncReleaser.cpp new file mode 100644 index 00000000..198495ab --- /dev/null +++ b/src/helpers/sync/SyncReleaser.cpp @@ -0,0 +1,25 @@ +#include "SyncReleaser.hpp" +#include "SyncTimeline.hpp" +#include "../../render/OpenGL.hpp" + +CSyncReleaser::CSyncReleaser(WP timeline_, uint64_t point_) : timeline(timeline_), point(point_) { + ; +} + +CSyncReleaser::~CSyncReleaser() { + if (timeline.expired()) + return; + + if (sync) + timeline->importFromSyncFileFD(point, sync->fd()); + else + timeline->signal(point); +} + +void CSyncReleaser::addReleaseSync(SP sync_) { + sync = sync_; +} + +void CSyncReleaser::drop() { + timeline.reset(); +} \ No newline at end of file diff --git a/src/helpers/sync/SyncReleaser.hpp b/src/helpers/sync/SyncReleaser.hpp new file mode 100644 index 00000000..e21d2e34 --- /dev/null +++ b/src/helpers/sync/SyncReleaser.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include +#include +#include "../memory/Memory.hpp" + +/* + A helper (inspired by KDE's KWin) that will release the timeline point in the dtor +*/ + +class CSyncTimeline; +class CEGLSync; + +class CSyncReleaser { + public: + CSyncReleaser(WP timeline_, uint64_t point_); + ~CSyncReleaser(); + + // drops the releaser, will never signal anymore + void drop(); + + // wait for this gpu job to finish before releasing + void addReleaseSync(SP sync); + + private: + WP timeline; + uint64_t point = 0; + SP sync; +}; diff --git a/src/helpers/sync/SyncTimeline.cpp b/src/helpers/sync/SyncTimeline.cpp index 352120ea..16b5bd92 100644 --- a/src/helpers/sync/SyncTimeline.cpp +++ b/src/helpers/sync/SyncTimeline.cpp @@ -188,3 +188,8 @@ bool CSyncTimeline::transfer(SP from, uint64_t fromPoint, uint64_ return true; } + +void CSyncTimeline::signal(uint64_t point) { + if (drmSyncobjTimelineSignal(drmFD, &handle, &point, 1)) + Debug::log(ERR, "CSyncTimeline::signal: drmSyncobjTimelineSignal failed"); +} diff --git a/src/helpers/sync/SyncTimeline.hpp b/src/helpers/sync/SyncTimeline.hpp index 3d868a95..88ad4921 100644 --- a/src/helpers/sync/SyncTimeline.hpp +++ b/src/helpers/sync/SyncTimeline.hpp @@ -35,6 +35,7 @@ class CSyncTimeline { int exportAsSyncFileFD(uint64_t src); bool importFromSyncFileFD(uint64_t dst, int fd); bool transfer(SP from, uint64_t fromPoint, uint64_t toPoint); + void signal(uint64_t point); int drmFD = -1; uint32_t handle = 0; diff --git a/src/hyprerror/HyprError.cpp b/src/hyprerror/HyprError.cpp index bbc3a006..4044bcc9 100644 --- a/src/hyprerror/HyprError.cpp +++ b/src/hyprerror/HyprError.cpp @@ -3,6 +3,9 @@ #include "../Compositor.hpp" #include "../config/ConfigValue.hpp" +#include +using namespace Hyprutils::Utils; + CHyprError::CHyprError() { m_fFadeOpacity.create(AVARTYPE_FLOAT, g_pConfigManager->getAnimationPropertyConfig("fadeIn"), AVARDAMAGE_NONE); m_fFadeOpacity.registerVar(); @@ -130,6 +133,8 @@ void CHyprError::createQueued() { } m_szQueued = ""; + m_fLastHeight = yoffset + PAD + 1 - (TOPBAR ? 0 : Y - PAD); + pango_font_description_free(pangoFD); g_object_unref(layoutText); @@ -158,6 +163,8 @@ void CHyprError::createQueued() { m_cQueued = CColor(); g_pHyprRenderer->damageMonitor(PMONITOR); + + g_pHyprRenderer->arrangeLayersForMonitor(PMONITOR->ID); } void CHyprError::draw() { @@ -174,6 +181,11 @@ void CHyprError::draw() { m_pTexture->destroyTexture(); m_bIsCreated = false; m_szQueued = ""; + + for (auto& m : g_pCompositor->m_vMonitors) { + g_pHyprRenderer->arrangeLayersForMonitor(m->ID); + } + return; } else { m_fFadeOpacity.setConfig(g_pConfigManager->getAnimationPropertyConfig("fadeOut")); @@ -203,3 +215,11 @@ void CHyprError::destroy() { else m_szQueued = ""; } + +bool CHyprError::active() { + return m_bIsCreated; +} + +float CHyprError::height() { + return m_fLastHeight; +} diff --git a/src/hyprerror/HyprError.hpp b/src/hyprerror/HyprError.hpp index 8dbb4521..a553614c 100644 --- a/src/hyprerror/HyprError.hpp +++ b/src/hyprerror/HyprError.hpp @@ -11,9 +11,12 @@ class CHyprError { CHyprError(); ~CHyprError(); - void queueCreate(std::string message, const CColor& color); - void draw(); - void destroy(); + void queueCreate(std::string message, const CColor& color); + void draw(); + void destroy(); + + bool active(); + float height(); // logical private: void createQueued(); @@ -23,7 +26,8 @@ class CHyprError { bool m_bIsCreated = false; SP m_pTexture; CAnimatedVariable m_fFadeOpacity; - CBox m_bDamageBox = {0, 0, 0, 0}; + CBox m_bDamageBox = {0, 0, 0, 0}; + float m_fLastHeight = 0.F; bool m_bMonitorChanged = false; }; diff --git a/src/layout/DwindleLayout.cpp b/src/layout/DwindleLayout.cpp index f287056f..8a7426eb 100644 --- a/src/layout/DwindleLayout.cpp +++ b/src/layout/DwindleLayout.cpp @@ -1,7 +1,7 @@ #include "DwindleLayout.hpp" -#include "../render/decorations/CHyprGroupBarDecoration.hpp" #include "../Compositor.hpp" #include "../config/ConfigValue.hpp" +#include "../render/decorations/CHyprGroupBarDecoration.hpp" void SDwindleNodeData::recalcSizePosRecursive(bool force, bool horizontalOverride, bool verticalOverride) { if (children[0]) { @@ -47,16 +47,16 @@ void SDwindleNodeData::getAllChildrenRecursive(std::deque* pD } } -int CHyprDwindleLayout::getNodesOnWorkspace(const int& id) { +int CHyprDwindleLayout::getNodesOnWorkspace(const WORKSPACEID& id) { int no = 0; - for (auto& n : m_lDwindleNodesData) { + for (auto const& n : m_lDwindleNodesData) { if (n.workspaceID == id && n.valid) ++no; } return no; } -SDwindleNodeData* CHyprDwindleLayout::getFirstNodeOnWorkspace(const int& id) { +SDwindleNodeData* CHyprDwindleLayout::getFirstNodeOnWorkspace(const WORKSPACEID& id) { for (auto& n : m_lDwindleNodesData) { if (n.workspaceID == id && validMapped(n.pWindow)) return &n; @@ -64,7 +64,7 @@ SDwindleNodeData* CHyprDwindleLayout::getFirstNodeOnWorkspace(const int& id) { return nullptr; } -SDwindleNodeData* CHyprDwindleLayout::getClosestNodeOnWorkspace(const int& id, const Vector2D& point) { +SDwindleNodeData* CHyprDwindleLayout::getClosestNodeOnWorkspace(const WORKSPACEID& id, const Vector2D& point) { SDwindleNodeData* res = nullptr; double distClosest = -1; for (auto& n : m_lDwindleNodesData) { @@ -88,7 +88,7 @@ SDwindleNodeData* CHyprDwindleLayout::getNodeFromWindow(PHLWINDOW pWindow) { return nullptr; } -SDwindleNodeData* CHyprDwindleLayout::getMasterNodeOnWorkspace(const int& id) { +SDwindleNodeData* CHyprDwindleLayout::getMasterNodeOnWorkspace(const WORKSPACEID& id) { for (auto& n : m_lDwindleNodesData) { if (!n.pParent && n.workspaceID == id) return &n; @@ -104,7 +104,7 @@ void CHyprDwindleLayout::applyNodeDataToWindow(SDwindleNodeData* pNode, bool for CMonitor* PMONITOR = nullptr; if (g_pCompositor->isWorkspaceSpecial(pNode->workspaceID)) { - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { if (m->activeSpecialWorkspaceID() == pNode->workspaceID) { PMONITOR = m.get(); break; @@ -142,11 +142,10 @@ void CHyprDwindleLayout::applyNodeDataToWindow(SDwindleNodeData* pNode, bool for PWINDOW->unsetWindowData(PRIORITY_LAYOUT); PWINDOW->updateWindowData(); - static auto PNOGAPSWHENONLY = CConfigValue("dwindle:no_gaps_when_only"); - static auto PGAPSINDATA = CConfigValue("general:gaps_in"); - static auto PGAPSOUTDATA = CConfigValue("general:gaps_out"); - auto* const PGAPSIN = (CCssGapData*)(PGAPSINDATA.ptr())->getData(); - auto* const PGAPSOUT = (CCssGapData*)(PGAPSOUTDATA.ptr())->getData(); + static auto PGAPSINDATA = CConfigValue("general:gaps_in"); + static auto PGAPSOUTDATA = CConfigValue("general:gaps_out"); + auto* const PGAPSIN = (CCssGapData*)(PGAPSINDATA.ptr())->getData(); + auto* const PGAPSOUT = (CCssGapData*)(PGAPSOUTDATA.ptr())->getData(); auto gapsIn = WORKSPACERULE.gapsIn.value_or(*PGAPSIN); auto gapsOut = WORKSPACERULE.gapsOut.value_or(*PGAPSOUT); @@ -156,27 +155,6 @@ void CHyprDwindleLayout::applyNodeDataToWindow(SDwindleNodeData* pNode, bool for PWINDOW->m_vSize = nodeBox.size(); PWINDOW->m_vPosition = nodeBox.pos(); - const auto NODESONWORKSPACE = getNodesOnWorkspace(PWINDOW->workspaceID()); - - if (*PNOGAPSWHENONLY && !PWINDOW->onSpecialWorkspace() && (NODESONWORKSPACE == 1 || PWINDOW->isEffectiveInternalFSMode(FSMODE_MAXIMIZED))) { - - PWINDOW->m_sWindowData.decorate = CWindowOverridableVar(true, PRIORITY_LAYOUT); - PWINDOW->m_sWindowData.noBorder = CWindowOverridableVar(*PNOGAPSWHENONLY != 2, PRIORITY_LAYOUT); - PWINDOW->m_sWindowData.noRounding = CWindowOverridableVar(true, PRIORITY_LAYOUT); - PWINDOW->m_sWindowData.noShadow = CWindowOverridableVar(true, PRIORITY_LAYOUT); - - PWINDOW->updateWindowDecos(); - - const auto RESERVED = PWINDOW->getFullWindowReservedArea(); - - PWINDOW->m_vRealPosition = PWINDOW->m_vPosition + RESERVED.topLeft; - PWINDOW->m_vRealSize = PWINDOW->m_vSize - (RESERVED.topLeft + RESERVED.bottomRight); - - g_pXWaylandManager->setWindowSize(PWINDOW, PWINDOW->m_vRealSize.goal()); - - return; - } - PWINDOW->updateWindowDecos(); auto calcPos = PWINDOW->m_vPosition; @@ -246,6 +224,8 @@ void CHyprDwindleLayout::applyNodeDataToWindow(SDwindleNodeData* pNode, bool for g_pHyprRenderer->damageWindow(PWINDOW); } + + PWINDOW->updateWindowDecos(); } void CHyprDwindleLayout::onWindowCreatedTiling(PHLWINDOW pWindow, eDirection direction) { @@ -328,8 +308,6 @@ void CHyprDwindleLayout::onWindowCreatedTiling(PHLWINDOW pWindow, eDirection dir applyNodeDataToWindow(PNODE); - pWindow->applyGroupRules(); - return; } @@ -338,26 +316,7 @@ void CHyprDwindleLayout::onWindowCreatedTiling(PHLWINDOW pWindow, eDirection dir return; } - // if it's a group, add the window - if (OPENINGON->pWindow->m_sGroupData.pNextWindow.lock() // target is group - && pWindow->canBeGroupedInto(OPENINGON->pWindow.lock()) && !m_vOverrideFocalPoint) { // we are not moving window - m_lDwindleNodesData.remove(*PNODE); - - static auto USECURRPOS = CConfigValue("group:insert_after_current"); - (*USECURRPOS ? OPENINGON->pWindow.lock() : OPENINGON->pWindow->getGroupTail())->insertWindowToGroup(pWindow); - - OPENINGON->pWindow->setGroupCurrent(pWindow); - pWindow->applyGroupRules(); - pWindow->updateWindowDecos(); - recalculateWindow(pWindow); - - if (!pWindow->getDecorationByType(DECORATION_GROUPBAR)) - pWindow->addWindowDeco(std::make_unique(pWindow)); - - return; - } - - // If it's not, get the node under our cursor + // get the node under our cursor m_lDwindleNodesData.push_back(SDwindleNodeData()); const auto NEWPARENT = &m_lDwindleNodesData.back(); @@ -457,6 +416,12 @@ void CHyprDwindleLayout::onWindowCreatedTiling(PHLWINDOW pWindow, eDirection dir } } + // split in favor of a specific window + const auto first = NEWPARENT->children[0]; + static auto PSPLITBIAS = CConfigValue("dwindle:split_bias"); + if ((*PSPLITBIAS == 1 && first == PNODE) || (*PSPLITBIAS == 2 && first == OPENINGON)) + NEWPARENT->splitRatio = 2.f - NEWPARENT->splitRatio; + // and update the previous parent if it exists if (OPENINGON->pParent) { if (OPENINGON->pParent->children[0] == OPENINGON) { @@ -483,8 +448,6 @@ void CHyprDwindleLayout::onWindowCreatedTiling(PHLWINDOW pWindow, eDirection dir NEWPARENT->recalcSizePosRecursive(false, horizontalOverride, verticalOverride); recalculateMonitor(pWindow->m_iMonitorID); - - pWindow->applyGroupRules(); } void CHyprDwindleLayout::onWindowRemovedTiling(PHLWINDOW pWindow) { @@ -535,7 +498,7 @@ void CHyprDwindleLayout::onWindowRemovedTiling(PHLWINDOW pWindow) { m_lDwindleNodesData.remove(*PNODE); } -void CHyprDwindleLayout::recalculateMonitor(const int& monid) { +void CHyprDwindleLayout::recalculateMonitor(const MONITORID& monid) { const auto PMONITOR = g_pCompositor->getMonitorFromID(monid); if (!PMONITOR || !PMONITOR->activeWorkspace) @@ -872,7 +835,7 @@ void CHyprDwindleLayout::moveWindowTo(PHLWINDOW pWindow, const std::string& dir, return; const auto PNODE = getNodeFromWindow(pWindow); - const int originalWorkspaceID = pWindow->workspaceID(); + const auto originalWorkspaceID = pWindow->workspaceID(); const Vector2D originalPos = pWindow->middle(); if (!PNODE) @@ -990,6 +953,10 @@ std::any CHyprDwindleLayout::layoutMessage(SLayoutMessageHeader header, std::str toggleSplit(header.pWindow); } else if (ARGS[0] == "swapsplit") { swapSplit(header.pWindow); + } else if (ARGS[0] == "movetoroot") { + const auto WINDOW = ARGS[1].empty() ? header.pWindow : g_pCompositor->getWindowByRegex(ARGS[1]); + const auto STABLE = ARGS[2].empty() || ARGS[2] != "unstable"; + moveToRoot(WINDOW, STABLE); } else if (ARGS[0] == "preselect") { std::string direction = ARGS[1]; @@ -1057,6 +1024,44 @@ void CHyprDwindleLayout::swapSplit(PHLWINDOW pWindow) { PNODE->pParent->recalcSizePosRecursive(); } +// goal: maximize the chosen window within current dwindle layout +// impl: swap the selected window with the other sub-tree below root +void CHyprDwindleLayout::moveToRoot(PHLWINDOW pWindow, bool stable) { + const auto PNODE = getNodeFromWindow(pWindow); + + if (!PNODE || !PNODE->pParent) + return; + + if (pWindow->isFullscreen()) + return; + + // already at root + if (!PNODE->pParent->pParent) + return; + + auto& pNode = PNODE->pParent->children[0] == PNODE ? PNODE->pParent->children[0] : PNODE->pParent->children[1]; + + // instead of [getMasterNodeOnWorkspace], we walk back to root since we need + // to know which children of root is our ancestor + auto pAncestor = PNODE, pRoot = PNODE->pParent; + while (pRoot->pParent) { + pAncestor = pRoot; + pRoot = pRoot->pParent; + } + + auto& pSwap = pRoot->children[0] == pAncestor ? pRoot->children[1] : pRoot->children[0]; + std::swap(pNode, pSwap); + std::swap(pNode->pParent, pSwap->pParent); + + // [stable] in that the focused window occupies same side of screen + if (stable) + std::swap(pRoot->children[0], pRoot->children[1]); + + // if the workspace is visible, recalculate layout + if (g_pCompositor->isWorkspaceVisible(pWindow->m_pWorkspace)) + pRoot->recalcSizePosRecursive(); +} + void CHyprDwindleLayout::replaceWindowDataWith(PHLWINDOW from, PHLWINDOW to) { const auto PNODE = getNodeFromWindow(from); @@ -1073,7 +1078,7 @@ std::string CHyprDwindleLayout::getLayoutName() { } void CHyprDwindleLayout::onEnable() { - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (w->m_bIsFloating || !w->m_bIsMapped || w->isHidden()) continue; diff --git a/src/layout/DwindleLayout.hpp b/src/layout/DwindleLayout.hpp index f638f6a2..953ba3a2 100644 --- a/src/layout/DwindleLayout.hpp +++ b/src/layout/DwindleLayout.hpp @@ -24,7 +24,7 @@ struct SDwindleNodeData { CBox box = {0}; - int workspaceID = -1; + WORKSPACEID workspaceID = WORKSPACE_INVALID; float splitRatio = 1.f; @@ -48,7 +48,7 @@ class CHyprDwindleLayout : public IHyprLayout { virtual void onWindowCreatedTiling(PHLWINDOW, eDirection direction = DIRECTION_DEFAULT); virtual void onWindowRemovedTiling(PHLWINDOW); virtual bool isWindowTiled(PHLWINDOW); - virtual void recalculateMonitor(const int&); + virtual void recalculateMonitor(const MONITORID&); virtual void recalculateWindow(PHLWINDOW); virtual void onBeginDragWindow(); virtual void resizeActiveWindow(const Vector2D&, eRectCorner corner = CORNER_NONE, PHLWINDOW pWindow = nullptr); @@ -77,16 +77,17 @@ class CHyprDwindleLayout : public IHyprLayout { std::optional m_vOverrideFocalPoint; // for onWindowCreatedTiling. - int getNodesOnWorkspace(const int&); + int getNodesOnWorkspace(const WORKSPACEID&); void applyNodeDataToWindow(SDwindleNodeData*, bool force = false); void calculateWorkspace(const PHLWORKSPACE& pWorkspace); SDwindleNodeData* getNodeFromWindow(PHLWINDOW); - SDwindleNodeData* getFirstNodeOnWorkspace(const int&); - SDwindleNodeData* getClosestNodeOnWorkspace(const int&, const Vector2D&); - SDwindleNodeData* getMasterNodeOnWorkspace(const int&); + SDwindleNodeData* getFirstNodeOnWorkspace(const WORKSPACEID&); + SDwindleNodeData* getClosestNodeOnWorkspace(const WORKSPACEID&, const Vector2D&); + SDwindleNodeData* getMasterNodeOnWorkspace(const WORKSPACEID&); void toggleSplit(PHLWINDOW); void swapSplit(PHLWINDOW); + void moveToRoot(PHLWINDOW, bool stable = true); eDirection overrideDirection = DIRECTION_DEFAULT; diff --git a/src/layout/IHyprLayout.cpp b/src/layout/IHyprLayout.cpp index 233933d4..b8d92636 100644 --- a/src/layout/IHyprLayout.cpp +++ b/src/layout/IHyprLayout.cpp @@ -9,23 +9,28 @@ #include "../xwayland/XSurface.hpp" void IHyprLayout::onWindowCreated(PHLWINDOW pWindow, eDirection direction) { - if (pWindow->m_bIsFloating) { + CBox desiredGeometry = {}; + g_pXWaylandManager->getGeometryForWindow(pWindow, &desiredGeometry); + + if (desiredGeometry.width <= 5 || desiredGeometry.height <= 5) { + const auto PMONITOR = g_pCompositor->getMonitorFromID(pWindow->m_iMonitorID); + pWindow->m_vLastFloatingSize = PMONITOR->vecSize / 2.f; + } else + pWindow->m_vLastFloatingSize = Vector2D(desiredGeometry.width, desiredGeometry.height); + + pWindow->m_vPseudoSize = pWindow->m_vLastFloatingSize; + + if (!g_pXWaylandManager->shouldBeFloated(pWindow)) // do not apply group rules to child windows + pWindow->applyGroupRules(); + + bool autoGrouped = IHyprLayout::onWindowCreatedAutoGroup(pWindow); + if (autoGrouped) + return; + + if (pWindow->m_bIsFloating) onWindowCreatedFloating(pWindow); - } else { - CBox desiredGeometry = {}; - g_pXWaylandManager->getGeometryForWindow(pWindow, &desiredGeometry); - - if (desiredGeometry.width <= 5 || desiredGeometry.height <= 5) { - const auto PMONITOR = g_pCompositor->getMonitorFromID(pWindow->m_iMonitorID); - pWindow->m_vLastFloatingSize = PMONITOR->vecSize / 2.f; - } else { - pWindow->m_vLastFloatingSize = Vector2D(desiredGeometry.width, desiredGeometry.height); - } - - pWindow->m_vPseudoSize = pWindow->m_vLastFloatingSize; - + else onWindowCreatedTiling(pWindow, direction); - } } void IHyprLayout::onWindowRemoved(PHLWINDOW pWindow) { @@ -104,7 +109,7 @@ void IHyprLayout::onWindowCreatedFloating(PHLWINDOW pWindow) { pWindow->m_vRealSize = PWINDOWSURFACE->current.size; if ((desiredGeometry.width <= 1 || desiredGeometry.height <= 1) && pWindow->m_bIsX11 && - pWindow->m_iX11Type == 2) { // XDG windows should be fine. TODO: check for weird atoms? + pWindow->isX11OverrideRedirect()) { // XDG windows should be fine. TODO: check for weird atoms? pWindow->setHidden(true); return; } @@ -113,7 +118,7 @@ void IHyprLayout::onWindowCreatedFloating(PHLWINDOW pWindow) { if (pWindow->m_vRealSize.goal().x <= 5 || pWindow->m_vRealSize.goal().y <= 5) pWindow->m_vRealSize = PMONITOR->vecSize / 2.f; - if (pWindow->m_bIsX11 && pWindow->m_iX11Type == 2) { + if (pWindow->m_bIsX11 && pWindow->isX11OverrideRedirect()) { if (pWindow->m_pXWaylandSurface->geometry.x != 0 && pWindow->m_pXWaylandSurface->geometry.y != 0) pWindow->m_vRealPosition = g_pXWaylandManager->xwaylandToWaylandCoords(pWindow->m_pXWaylandSurface->geometry.pos()); @@ -163,12 +168,12 @@ void IHyprLayout::onWindowCreatedFloating(PHLWINDOW pWindow) { if (*PXWLFORCESCALEZERO && pWindow->m_bIsX11) pWindow->m_vRealSize = pWindow->m_vRealSize.goal() / PMONITOR->scale; - if (pWindow->m_bX11DoesntWantBorders || (pWindow->m_bIsX11 && pWindow->m_iX11Type == 2)) { + if (pWindow->m_bX11DoesntWantBorders || (pWindow->m_bIsX11 && pWindow->isX11OverrideRedirect())) { pWindow->m_vRealPosition.warp(); pWindow->m_vRealSize.warp(); } - if (pWindow->m_iX11Type != 2) { + if (!pWindow->isX11OverrideRedirect()) { g_pXWaylandManager->setWindowSize(pWindow, pWindow->m_vRealSize.goal()); g_pCompositor->changeWindowZOrder(pWindow, true); @@ -178,6 +183,41 @@ void IHyprLayout::onWindowCreatedFloating(PHLWINDOW pWindow) { } } +bool IHyprLayout::onWindowCreatedAutoGroup(PHLWINDOW pWindow) { + static auto PAUTOGROUP = CConfigValue("group:auto_group"); + PHLWINDOW OPENINGON = g_pCompositor->m_pLastWindow.lock() && g_pCompositor->m_pLastWindow->m_pWorkspace == pWindow->m_pWorkspace ? + g_pCompositor->m_pLastWindow.lock() : + g_pCompositor->getFirstWindowOnWorkspace(pWindow->workspaceID()); + + bool denied = false; + if (pWindow->m_bIsFloating && !OPENINGON->m_bIsFloating) + denied = true; + + if (*PAUTOGROUP // check if auto_group is enabled. + && OPENINGON->m_sGroupData.pNextWindow.lock() // check if OPENINGON is a group. + && OPENINGON != pWindow // prevent freeze when the "group set" window rule makes the new window to be already a group. + && pWindow->canBeGroupedInto(OPENINGON) // check if the new window can be grouped into OPENINGON. + && !g_pXWaylandManager->shouldBeFloated(pWindow) // don't group child windows. Fix for floated groups. Tiled groups don't need this because we check if !denied. + && !denied) { // don't group a new floated window into a tiled group (for convenience). + + pWindow->m_bIsFloating = OPENINGON->m_bIsFloating; // match the floating state. Needed to autogroup a new tiled window into a floated group. + + static auto USECURRPOS = CConfigValue("group:insert_after_current"); + (*USECURRPOS ? OPENINGON : OPENINGON->getGroupTail())->insertWindowToGroup(pWindow); + + OPENINGON->setGroupCurrent(pWindow); + pWindow->updateWindowDecos(); + recalculateWindow(pWindow); + + if (!pWindow->getDecorationByType(DECORATION_GROUPBAR)) + pWindow->addWindowDeco(std::make_unique(pWindow)); + + return true; + } + + return false; +} + void IHyprLayout::onBeginDragWindow() { const auto DRAGGINGWINDOW = g_pInputManager->currentlyDraggedWindow.lock(); @@ -291,21 +331,47 @@ void IHyprLayout::onEndDragWindow() { g_pInputManager->currentlyDraggedWindow.reset(); g_pInputManager->m_bWasDraggingWindow = true; - if (DRAGGINGWINDOW->m_bDraggingTiled) { - DRAGGINGWINDOW->m_bIsFloating = false; - g_pInputManager->refocus(); - changeWindowFloatingMode(DRAGGINGWINDOW); - DRAGGINGWINDOW->m_vLastFloatingSize = m_vDraggingWindowOriginalFloatSize; - } else if (g_pInputManager->dragMode == MBIND_MOVE) { + if (g_pInputManager->dragMode == MBIND_MOVE) { g_pHyprRenderer->damageWindow(DRAGGINGWINDOW); const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal(); - PHLWINDOW pWindow = g_pCompositor->vectorToWindowUnified(MOUSECOORDS, RESERVED_EXTENTS | INPUT_EXTENTS | ALLOW_FLOATING | FLOATING_ONLY, DRAGGINGWINDOW); + PHLWINDOW pWindow = g_pCompositor->vectorToWindowUnified(MOUSECOORDS, RESERVED_EXTENTS | INPUT_EXTENTS | ALLOW_FLOATING, DRAGGINGWINDOW); if (pWindow) { if (pWindow->checkInputOnDecos(INPUT_TYPE_DRAG_END, MOUSECOORDS, DRAGGINGWINDOW)) return; - if (pWindow->m_sGroupData.pNextWindow.lock() && DRAGGINGWINDOW->canBeGroupedInto(pWindow)) { + bool denied = false; + if (!pWindow->m_bIsFloating && !DRAGGINGWINDOW->m_bDraggingTiled) + denied = true; + + static auto PDRAGINTOGROUP = CConfigValue("group:drag_into_group"); + if (pWindow->m_sGroupData.pNextWindow.lock() && DRAGGINGWINDOW->canBeGroupedInto(pWindow) && *PDRAGINTOGROUP == 1 && !denied) { + if (DRAGGINGWINDOW->m_bDraggingTiled) { + changeWindowFloatingMode(DRAGGINGWINDOW); + DRAGGINGWINDOW->m_vLastFloatingSize = m_vDraggingWindowOriginalFloatSize; + DRAGGINGWINDOW->m_bDraggingTiled = false; + } + + if (DRAGGINGWINDOW->m_sGroupData.pNextWindow.lock()) { + std::vector members; + PHLWINDOW curr = DRAGGINGWINDOW->getGroupHead(); + do { + members.push_back(curr); + curr = curr->m_sGroupData.pNextWindow.lock(); + } while (curr != members[0]); + + for (auto it = members.begin(); it != members.end(); ++it) { + (*it)->m_bIsFloating = pWindow->m_bIsFloating; // match the floating state of group members + if (pWindow->m_bIsFloating) + (*it)->m_vRealSize = pWindow->m_vRealSize.goal(); // match the size of group members + } + } + + DRAGGINGWINDOW->m_bIsFloating = pWindow->m_bIsFloating; // match the floating state of the window + + if (pWindow->m_bIsFloating) + g_pXWaylandManager->setWindowSize(DRAGGINGWINDOW, pWindow->m_vRealSize.goal()); // match the size of the window + static auto USECURRPOS = CConfigValue("group:insert_after_current"); (*USECURRPOS ? pWindow : pWindow->getGroupTail())->insertWindowToGroup(DRAGGINGWINDOW); pWindow->setGroupCurrent(DRAGGINGWINDOW); @@ -317,6 +383,13 @@ void IHyprLayout::onEndDragWindow() { } } + if (DRAGGINGWINDOW->m_bDraggingTiled) { + DRAGGINGWINDOW->m_bIsFloating = false; + g_pInputManager->refocus(); + changeWindowFloatingMode(DRAGGINGWINDOW); + DRAGGINGWINDOW->m_vLastFloatingSize = m_vDraggingWindowOriginalFloatSize; + } + g_pHyprRenderer->damageWindow(DRAGGINGWINDOW); g_pCompositor->focusWindow(DRAGGINGWINDOW); @@ -591,8 +664,8 @@ PHLWINDOW IHyprLayout::getNextWindowCandidate(PHLWINDOW pWindow) { if (pWindow->m_bIsFloating) { // find whether there is a floating window below this one - for (auto& w : g_pCompositor->m_vWindows) { - if (w->m_bIsMapped && !w->isHidden() && w->m_bIsFloating && w->m_iX11Type != 2 && w->m_pWorkspace == pWindow->m_pWorkspace && !w->m_bX11ShouldntFocus && + for (auto const& w : g_pCompositor->m_vWindows) { + if (w->m_bIsMapped && !w->isHidden() && w->m_bIsFloating && !w->isX11OverrideRedirect() && w->m_pWorkspace == pWindow->m_pWorkspace && !w->m_bX11ShouldntFocus && !w->m_sWindowData.noFocus.valueOrDefault() && w != pWindow) { if (VECINRECT((pWindow->m_vSize / 2.f + pWindow->m_vPosition), w->m_vPosition.x, w->m_vPosition.y, w->m_vPosition.x + w->m_vSize.x, w->m_vPosition.y + w->m_vSize.y)) { @@ -611,8 +684,8 @@ PHLWINDOW IHyprLayout::getNextWindowCandidate(PHLWINDOW pWindow) { return PWINDOWCANDIDATE; // if not, floating window - for (auto& w : g_pCompositor->m_vWindows) { - if (w->m_bIsMapped && !w->isHidden() && w->m_bIsFloating && w->m_iX11Type != 2 && w->m_pWorkspace == pWindow->m_pWorkspace && !w->m_bX11ShouldntFocus && + for (auto const& w : g_pCompositor->m_vWindows) { + if (w->m_bIsMapped && !w->isHidden() && w->m_bIsFloating && !w->isX11OverrideRedirect() && w->m_pWorkspace == pWindow->m_pWorkspace && !w->m_bX11ShouldntFocus && !w->m_sWindowData.noFocus.valueOrDefault() && w != pWindow) return w; } @@ -631,7 +704,7 @@ PHLWINDOW IHyprLayout::getNextWindowCandidate(PHLWINDOW pWindow) { pWindowCandidate = g_pCompositor->getFirstWindowOnWorkspace(pWindow->workspaceID()); if (!pWindowCandidate || pWindow == pWindowCandidate || !pWindowCandidate->m_bIsMapped || pWindowCandidate->isHidden() || pWindowCandidate->m_bX11ShouldntFocus || - pWindowCandidate->m_iX11Type == 2 || pWindowCandidate->m_iMonitorID != g_pCompositor->m_pLastMonitor->ID) + pWindowCandidate->isX11OverrideRedirect() || pWindowCandidate->m_iMonitorID != g_pCompositor->m_pLastMonitor->ID) return nullptr; return pWindowCandidate; @@ -660,7 +733,7 @@ void IHyprLayout::requestFocusForWindow(PHLWINDOW pWindow) { Vector2D IHyprLayout::predictSizeForNewWindowFloating(PHLWINDOW pWindow) { // get all rules, see if we have any size overrides. Vector2D sizeOverride = {}; if (g_pCompositor->m_pLastMonitor) { - for (auto& r : g_pConfigManager->getMatchingRules(pWindow, true, true)) { + for (auto const& r : g_pConfigManager->getMatchingRules(pWindow, true, true)) { if (r.szRule.starts_with("size")) { try { const auto VALUE = r.szRule.substr(r.szRule.find(' ') + 1); @@ -691,7 +764,7 @@ Vector2D IHyprLayout::predictSizeForNewWindow(PHLWINDOW pWindow) { bool shouldBeFloated = g_pXWaylandManager->shouldBeFloated(pWindow, true); if (!shouldBeFloated) { - for (auto& r : g_pConfigManager->getMatchingRules(pWindow, true, true)) { + for (auto const& r : g_pConfigManager->getMatchingRules(pWindow, true, true)) { if (r.szRule.starts_with("float")) { shouldBeFloated = true; break; diff --git a/src/layout/IHyprLayout.hpp b/src/layout/IHyprLayout.hpp index 4b1b59e3..f9e2de0d 100644 --- a/src/layout/IHyprLayout.hpp +++ b/src/layout/IHyprLayout.hpp @@ -47,6 +47,7 @@ class IHyprLayout { virtual void onWindowCreated(PHLWINDOW, eDirection direction = DIRECTION_DEFAULT); virtual void onWindowCreatedTiling(PHLWINDOW, eDirection direction = DIRECTION_DEFAULT) = 0; virtual void onWindowCreatedFloating(PHLWINDOW); + virtual bool onWindowCreatedAutoGroup(PHLWINDOW); /* Return tiled status @@ -63,7 +64,7 @@ class IHyprLayout { Called when the monitor requires a layout recalculation this usually means reserved area changes */ - virtual void recalculateMonitor(const int&) = 0; + virtual void recalculateMonitor(const MONITORID&) = 0; /* Called when the compositor requests a window diff --git a/src/layout/MasterLayout.cpp b/src/layout/MasterLayout.cpp index be00168f..f983770b 100644 --- a/src/layout/MasterLayout.cpp +++ b/src/layout/MasterLayout.cpp @@ -14,9 +14,9 @@ SMasterNodeData* CHyprMasterLayout::getNodeFromWindow(PHLWINDOW pWindow) { return nullptr; } -int CHyprMasterLayout::getNodesOnWorkspace(const int& ws) { +int CHyprMasterLayout::getNodesOnWorkspace(const WORKSPACEID& ws) { int no = 0; - for (auto& n : m_lMasterNodesData) { + for (auto const& n : m_lMasterNodesData) { if (n.workspaceID == ws) no++; } @@ -24,9 +24,9 @@ int CHyprMasterLayout::getNodesOnWorkspace(const int& ws) { return no; } -int CHyprMasterLayout::getMastersOnWorkspace(const int& ws) { +int CHyprMasterLayout::getMastersOnWorkspace(const WORKSPACEID& ws) { int no = 0; - for (auto& n : m_lMasterNodesData) { + for (auto const& n : m_lMasterNodesData) { if (n.workspaceID == ws && n.isMaster) no++; } @@ -34,7 +34,7 @@ int CHyprMasterLayout::getMastersOnWorkspace(const int& ws) { return no; } -SMasterWorkspaceData* CHyprMasterLayout::getMasterWorkspaceData(const int& ws) { +SMasterWorkspaceData* CHyprMasterLayout::getMasterWorkspaceData(const WORKSPACEID& ws) { for (auto& n : m_lMasterWorkspacesData) { if (n.workspaceID == ws) return &n; @@ -63,7 +63,7 @@ std::string CHyprMasterLayout::getLayoutName() { return "Master"; } -SMasterNodeData* CHyprMasterLayout::getMasterNodeOnWorkspace(const int& ws) { +SMasterNodeData* CHyprMasterLayout::getMasterNodeOnWorkspace(const WORKSPACEID& ws) { for (auto& n : m_lMasterNodesData) { if (n.isMaster && n.workspaceID == ws) return &n; @@ -116,28 +116,6 @@ void CHyprMasterLayout::onWindowCreatedTiling(PHLWINDOW pWindow, eDirection dire return; } - // if it's a group, add the window - if (OPENINGON && OPENINGON != PNODE && OPENINGON->pWindow->m_sGroupData.pNextWindow.lock() // target is group - && pWindow->canBeGroupedInto(OPENINGON->pWindow.lock())) { - - m_lMasterNodesData.remove(*PNODE); - - static auto USECURRPOS = CConfigValue("group:insert_after_current"); - (*USECURRPOS ? OPENINGON->pWindow.lock() : OPENINGON->pWindow->getGroupTail())->insertWindowToGroup(pWindow); - - OPENINGON->pWindow->setGroupCurrent(pWindow); - pWindow->applyGroupRules(); - pWindow->updateWindowDecos(); - recalculateWindow(pWindow); - - if (!pWindow->getDecorationByType(DECORATION_GROUPBAR)) - pWindow->addWindowDeco(std::make_unique(pWindow)); - - return; - } - - pWindow->applyGroupRules(); - static auto PDROPATCURSOR = CConfigValue("master:drop_at_cursor"); eOrientation orientation = getDynamicOrientation(pWindow->m_pWorkspace); const auto NODEIT = std::find(m_lMasterNodesData.begin(), m_lMasterNodesData.end(), *PNODE); @@ -172,7 +150,7 @@ void CHyprMasterLayout::onWindowCreatedTiling(PHLWINDOW pWindow, eDirection dire } else if (WINDOWSONWORKSPACE == 2) { // when dropping as the second tiled window in the workspace, // make it the master only if the cursor is on the master side of the screen - for (auto& nd : m_lMasterNodesData) { + for (auto const& nd : m_lMasterNodesData) { if (nd.isMaster && nd.workspaceID == PNODE->workspaceID) { switch (orientation) { case ORIENTATION_LEFT: @@ -304,7 +282,7 @@ void CHyprMasterLayout::onWindowRemovedTiling(PHLWINDOW pWindow) { recalculateMonitor(pWindow->m_iMonitorID); } -void CHyprMasterLayout::recalculateMonitor(const int& monid) { +void CHyprMasterLayout::recalculateMonitor(const MONITORID& monid) { const auto PMONITOR = g_pCompositor->getMonitorFromID(monid); if (!PMONITOR || !PMONITOR->activeWorkspace) @@ -381,7 +359,7 @@ void CHyprMasterLayout::calculateWorkspace(PHLWORKSPACE pWorkspace) { if (*PSMARTRESIZING) { // check the total width and height so that later // if larger/smaller than screen size them down/up - for (auto& nd : m_lMasterNodesData) { + for (auto const& nd : m_lMasterNodesData) { if (nd.workspaceID == pWorkspace->m_iID) { if (nd.isMaster) masterAccumulatedSize += totalSize / MASTERS * nd.percSize; @@ -552,7 +530,7 @@ void CHyprMasterLayout::calculateWorkspace(PHLWORKSPACE pWorkspace) { float slaveAccumulatedHeightL = 0; float slaveAccumulatedHeightR = 0; if (*PSMARTRESIZING) { - for (auto& nd : m_lMasterNodesData) { + for (auto const& nd : m_lMasterNodesData) { if (nd.workspaceID != pWorkspace->m_iID || nd.isMaster) continue; @@ -619,7 +597,7 @@ void CHyprMasterLayout::applyNodeDataToWindow(SMasterNodeData* pNode) { CMonitor* PMONITOR = nullptr; if (g_pCompositor->isWorkspaceSpecial(pNode->workspaceID)) { - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { if (m->activeSpecialWorkspaceID() == pNode->workspaceID) { PMONITOR = m.get(); break; @@ -651,12 +629,11 @@ void CHyprMasterLayout::applyNodeDataToWindow(SMasterNodeData* pNode) { PWINDOW->unsetWindowData(PRIORITY_LAYOUT); PWINDOW->updateWindowData(); - static auto PNOGAPSWHENONLY = CConfigValue("master:no_gaps_when_only"); - static auto PANIMATE = CConfigValue("misc:animate_manual_resizes"); - static auto PGAPSINDATA = CConfigValue("general:gaps_in"); - static auto PGAPSOUTDATA = CConfigValue("general:gaps_out"); - auto* PGAPSIN = (CCssGapData*)(PGAPSINDATA.ptr())->getData(); - auto* PGAPSOUT = (CCssGapData*)(PGAPSOUTDATA.ptr())->getData(); + static auto PANIMATE = CConfigValue("misc:animate_manual_resizes"); + static auto PGAPSINDATA = CConfigValue("general:gaps_in"); + static auto PGAPSOUTDATA = CConfigValue("general:gaps_out"); + auto* PGAPSIN = (CCssGapData*)(PGAPSINDATA.ptr())->getData(); + auto* PGAPSOUT = (CCssGapData*)(PGAPSOUTDATA.ptr())->getData(); auto gapsIn = WORKSPACERULE.gapsIn.value_or(*PGAPSIN); auto gapsOut = WORKSPACERULE.gapsOut.value_or(*PGAPSOUT); @@ -669,25 +646,6 @@ void CHyprMasterLayout::applyNodeDataToWindow(SMasterNodeData* pNode) { PWINDOW->m_vSize = pNode->size; PWINDOW->m_vPosition = pNode->position; - if (*PNOGAPSWHENONLY && !PWINDOW->onSpecialWorkspace() && (getNodesOnWorkspace(PWINDOW->workspaceID()) == 1 || PWINDOW->isEffectiveInternalFSMode(FSMODE_MAXIMIZED))) { - - PWINDOW->m_sWindowData.decorate = CWindowOverridableVar(true, PRIORITY_LAYOUT); - PWINDOW->m_sWindowData.noBorder = CWindowOverridableVar(*PNOGAPSWHENONLY != 2, PRIORITY_LAYOUT); - PWINDOW->m_sWindowData.noRounding = CWindowOverridableVar(true, PRIORITY_LAYOUT); - PWINDOW->m_sWindowData.noShadow = CWindowOverridableVar(true, PRIORITY_LAYOUT); - - PWINDOW->updateWindowDecos(); - - const auto RESERVED = PWINDOW->getFullWindowReservedArea(); - - PWINDOW->m_vRealPosition = PWINDOW->m_vPosition + RESERVED.topLeft; - PWINDOW->m_vRealSize = PWINDOW->m_vSize - (RESERVED.topLeft + RESERVED.bottomRight); - - g_pXWaylandManager->setWindowSize(PWINDOW, PWINDOW->m_vRealSize.goal()); - - return; - } - PWINDOW->updateWindowDecos(); auto calcPos = PWINDOW->m_vPosition; @@ -732,6 +690,8 @@ void CHyprMasterLayout::applyNodeDataToWindow(SMasterNodeData* pNode) { g_pHyprRenderer->damageWindow(PWINDOW); } + + PWINDOW->updateWindowDecos(); } bool CHyprMasterLayout::isWindowTiled(PHLWINDOW pWindow) { @@ -1104,7 +1064,7 @@ std::any CHyprMasterLayout::layoutMessage(SLayoutMessageHeader header, std::stri const auto NEWFOCUS = newFocusToChild ? NEWCHILD : NEWMASTER; switchToWindow(NEWFOCUS); } else { - for (auto& n : m_lMasterNodesData) { + for (auto const& n : m_lMasterNodesData) { if (n.workspaceID == PMASTER->workspaceID && !n.isMaster) { const auto NEWMASTER = n.pWindow.lock(); switchWindows(NEWMASTER, NEWCHILD); @@ -1139,7 +1099,7 @@ std::any CHyprMasterLayout::layoutMessage(SLayoutMessageHeader header, std::stri return 0; } else { // if master is focused keep master focused (don't do anything) - for (auto& n : m_lMasterNodesData) { + for (auto const& n : m_lMasterNodesData) { if (n.workspaceID == PMASTER->workspaceID && !n.isMaster) { switchToWindow(n.pWindow.lock()); break; @@ -1467,7 +1427,7 @@ Vector2D CHyprMasterLayout::predictSizeForNewWindowTiled() { } void CHyprMasterLayout::onEnable() { - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (w->m_bIsFloating || !w->m_bIsMapped || w->isHidden()) continue; diff --git a/src/layout/MasterLayout.hpp b/src/layout/MasterLayout.hpp index fdb916e5..b72be74f 100644 --- a/src/layout/MasterLayout.hpp +++ b/src/layout/MasterLayout.hpp @@ -30,7 +30,7 @@ struct SMasterNodeData { float percSize = 1.f; // size multiplier for resizing children - int workspaceID = -1; + WORKSPACEID workspaceID = WORKSPACE_INVALID; bool ignoreFullscreenChecks = false; @@ -41,7 +41,7 @@ struct SMasterNodeData { }; struct SMasterWorkspaceData { - int workspaceID = -1; + WORKSPACEID workspaceID = WORKSPACE_INVALID; eOrientation orientation = ORIENTATION_LEFT; // @@ -55,7 +55,7 @@ class CHyprMasterLayout : public IHyprLayout { virtual void onWindowCreatedTiling(PHLWINDOW, eDirection direction = DIRECTION_DEFAULT); virtual void onWindowRemovedTiling(PHLWINDOW); virtual bool isWindowTiled(PHLWINDOW); - virtual void recalculateMonitor(const int&); + virtual void recalculateMonitor(const MONITORID&); virtual void recalculateWindow(PHLWINDOW); virtual void resizeActiveWindow(const Vector2D&, eRectCorner corner = CORNER_NONE, PHLWINDOW pWindow = nullptr); virtual void fullscreenRequestForWindow(PHLWINDOW pWindow, const eFullscreenMode CURRENT_EFFECTIVE_MODE, const eFullscreenMode EFFECTIVE_MODE); @@ -81,14 +81,14 @@ class CHyprMasterLayout : public IHyprLayout { void buildOrientationCycleVectorFromEOperation(std::vector& cycle); void runOrientationCycle(SLayoutMessageHeader& header, CVarList* vars, int next); eOrientation getDynamicOrientation(PHLWORKSPACE); - int getNodesOnWorkspace(const int&); + int getNodesOnWorkspace(const WORKSPACEID&); void applyNodeDataToWindow(SMasterNodeData*); SMasterNodeData* getNodeFromWindow(PHLWINDOW); - SMasterNodeData* getMasterNodeOnWorkspace(const int&); - SMasterWorkspaceData* getMasterWorkspaceData(const int&); + SMasterNodeData* getMasterNodeOnWorkspace(const WORKSPACEID&); + SMasterWorkspaceData* getMasterWorkspaceData(const WORKSPACEID&); void calculateWorkspace(PHLWORKSPACE); PHLWINDOW getNextWindow(PHLWINDOW, bool); - int getMastersOnWorkspace(const int&); + int getMastersOnWorkspace(const WORKSPACEID&); friend struct SMasterNodeData; friend struct SMasterWorkspaceData; diff --git a/src/macros.hpp b/src/macros.hpp index b2adb036..44014085 100644 --- a/src/macros.hpp +++ b/src/macros.hpp @@ -27,6 +27,8 @@ #define WORKSPACE_INVALID -1L #define WORKSPACE_NOT_CHANGED -101 +#define MONITOR_INVALID -1L + #define LISTENER(name) \ void listener_##name(wl_listener*, void*); \ inline wl_listener listen_##name = {.notify = listener_##name} diff --git a/src/main.cpp b/src/main.cpp index e85b0a22..6bbf73fd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,6 +3,12 @@ #include "Compositor.hpp" #include "config/ConfigManager.hpp" #include "init/initHelpers.hpp" +#include "debug/HyprCtl.hpp" + +#include +#include +#include +using namespace Hyprutils::String; #include #include @@ -13,13 +19,15 @@ #include void help() { - std::cout << "usage: Hyprland [arg [...]].\n"; - std::cout << "\nArguments:\n"; - std::cout << " --help -h - Show this message again\n"; - std::cout << " --config FILE -c FILE - Specify config file to use\n"; - std::cout << " --socket NAME - Sets the Wayland socket name (for Wayland socket handover)\n"; - std::cout << " --wayland-fd FD - Sets the Wayland socket fd (for Wayland socket handover)\n"; - std::cout << " --i-am-really-stupid - Omits root user privileges check (why would you do that?)\n"; + std::println("usage: Hyprland [arg [...]].\n"); + std::println(R"(Arguments: + --help -h - Show this message again + --config FILE -c FILE - Specify config file to use + --socket NAME - Sets the Wayland socket name (for Wayland socket handover) + --wayland-fd FD - Sets the Wayland socket fd (for Wayland socket handover) + --systeminfo - Prints system infos + --i-am-really-stupid - Omits root user privileges check (why would you do that?) + --version -v - Print this binary's version)"); } int main(int argc, char** argv) { @@ -28,15 +36,14 @@ int main(int argc, char** argv) { throwError("XDG_RUNTIME_DIR is not set!"); // export HYPRLAND_CMD - std::string cmd = ""; - for (auto i = 0; i < argc; ++i) - cmd += std::string(i == 0 ? "" : " ") + argv[i]; + std::string cmd = argv[0]; + for (int i = 1; i < argc; ++i) + cmd += std::string(" ") + argv[i]; setenv("HYPRLAND_CMD", cmd.c_str(), 1); setenv("XDG_BACKEND", "wayland", 1); setenv("_JAVA_AWT_WM_NONREPARENTING", "1", 1); setenv("MOZ_ENABLE_WAYLAND", "1", 1); - setenv("XDG_CURRENT_DESKTOP", "Hyprland", 1); // parse some args std::string configPath; @@ -48,7 +55,7 @@ int main(int argc, char** argv) { for (auto it = args.begin(); it != args.end(); it++) { if (it->compare("--i-am-really-stupid") == 0 && !ignoreSudo) { - std::cout << "[ WARNING ] Running Hyprland with superuser privileges might damage your system\n"; + std::println("[ WARNING ] Running Hyprland with superuser privileges might damage your system"); ignoreSudo = true; } else if (it->compare("--socket") == 0) { @@ -74,7 +81,7 @@ int main(int argc, char** argv) { if (fcntl(socketFd, F_GETFD) == -1) throw std::exception(); } catch (...) { - std::cerr << "[ ERROR ] Invalid Wayland FD!\n"; + std::println(stderr, "[ ERROR ] Invalid Wayland FD!"); help(); return 1; @@ -96,7 +103,7 @@ int main(int argc, char** argv) { throw std::exception(); } } catch (...) { - std::cerr << "[ ERROR ] Config file '" << configPath << "' doesn't exist!\n"; + std::println(stderr, "[ ERROR ] Config file '{}' doesn't exist!", configPath); help(); return 1; @@ -110,9 +117,15 @@ int main(int argc, char** argv) { } else if (it->compare("-h") == 0 || it->compare("--help") == 0) { help(); + return 0; + } else if (it->compare("-v") == 0 || it->compare("--version") == 0) { + std::println("{}", versionRequest(eHyprCtlOutputFormat::FORMAT_NORMAL, "")); + return 0; + } else if (it->compare("--systeminfo") == 0) { + std::println("{}", systemInfoRequest(eHyprCtlOutputFormat::FORMAT_NORMAL, "")); return 0; } else { - std::cerr << "[ ERROR ] Unknown option '" << it->c_str() << "'!\n"; + std::println(stderr, "[ ERROR ] Unknown option '{}' !", it->c_str()); help(); return 1; @@ -120,30 +133,32 @@ int main(int argc, char** argv) { } if (!ignoreSudo && Init::isSudo()) { - std::cerr << "[ ERROR ] Hyprland was launched with superuser privileges, but the privileges check is not omitted.\n"; - std::cerr << " Hint: Use the --i-am-really-stupid flag to omit that check.\n"; + std::println(stderr, + "[ ERROR ] Hyprland was launched with superuser privileges, but the privileges check is not omitted.\n" + " Hint: Use the --i-am-really-stupid flag to omit that check."); return 1; } else if (ignoreSudo && Init::isSudo()) { - std::cout << "Superuser privileges check is omitted. I hope you know what you're doing.\n"; + std::println("Superuser privileges check is omitted. I hope you know what you're doing."); } if (socketName.empty() ^ (socketFd == -1)) { - std::cerr << "[ ERROR ] Hyprland was launched with only one of --socket and --wayland-fd.\n"; - std::cerr << " Hint: Pass both --socket and --wayland-fd to perform Wayland socket handover.\n"; + std::println(stderr, + "[ ERROR ] Hyprland was launched with only one of --socket and --wayland-fd.\n" + " Hint: Pass both --socket and --wayland-fd to perform Wayland socket handover."); return 1; } - std::cout << "Welcome to Hyprland!\n"; + std::println("Welcome to Hyprland!"); // let's init the compositor. // it initializes basic Wayland stuff in the constructor. try { g_pCompositor = std::make_unique(); g_pCompositor->explicitConfigPath = configPath; - } catch (std::exception& e) { - std::cout << "Hyprland threw in ctor: " << e.what() << "\nCannot continue.\n"; + } catch (const std::exception& e) { + std::println(stderr, "Hyprland threw in ctor: {}\nCannot continue.", e.what()); return 1; } diff --git a/src/managers/AnimationManager.cpp b/src/managers/AnimationManager.cpp index dcc7a45f..0ff94f1f 100644 --- a/src/managers/AnimationManager.cpp +++ b/src/managers/AnimationManager.cpp @@ -70,7 +70,7 @@ void CAnimationManager::tick() { std::vector animationEndedVars; - for (auto& av : m_vActiveAnimatedVariables) { + for (auto const& av : m_vActiveAnimatedVariables) { if (av->m_eDamagePolicy == AVARDAMAGE_SHADOW && !*PSHADOWSENABLED) { av->warp(false); @@ -113,7 +113,7 @@ void CAnimationManager::tick() { g_pHyprRenderer->damageMonitor(PMONITOR); // TODO: just make this into a damn callback already vax... - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (!w->m_bIsMapped || w->isHidden() || w->m_pWorkspace != PWORKSPACE) continue; @@ -131,7 +131,7 @@ void CAnimationManager::tick() { } // damage any workspace window that is on any monitor - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (!validMapped(w) || w->m_pWorkspace != PWORKSPACE || w->m_bPinned) continue; @@ -194,7 +194,7 @@ void CAnimationManager::tick() { default: UNREACHABLE(); } // set size and pos if valid, but only if damage policy entire (dont if border for example) - if (validMapped(PWINDOW) && av->m_eDamagePolicy == AVARDAMAGE_ENTIRE && PWINDOW->m_iX11Type != 2) + if (validMapped(PWINDOW) && av->m_eDamagePolicy == AVARDAMAGE_ENTIRE && !PWINDOW->isX11OverrideRedirect()) g_pXWaylandManager->setWindowSize(PWINDOW, PWINDOW->m_vRealSize.goal()); // check if we did not finish animating. If so, trigger onAnimationEnd. @@ -214,7 +214,7 @@ void CAnimationManager::tick() { PWINDOW->updateWindowDecos(); g_pHyprRenderer->damageWindow(PWINDOW); } else if (PWORKSPACE) { - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (!validMapped(w) || w->m_pWorkspace != PWORKSPACE) continue; @@ -259,11 +259,11 @@ void CAnimationManager::tick() { // manually schedule a frame if (PMONITOR) - g_pCompositor->scheduleFrameForMonitor(PMONITOR, Aquamarine::IOutput::AQ_SCHEDULE_CURSOR_SHAPE); + g_pCompositor->scheduleFrameForMonitor(PMONITOR, Aquamarine::IOutput::AQ_SCHEDULE_ANIMATION); } // do it here, because if this alters the animation vars deque we would be in trouble above. - for (auto& ave : animationEndedVars) { + for (auto const& ave : animationEndedVars) { ave->onAnimationEnd(); } } @@ -293,7 +293,7 @@ bool CAnimationManager::deltazero(const CColor& a, const CColor& b) { } bool CAnimationManager::bezierExists(const std::string& bezier) { - for (auto& [bc, bz] : m_mBezierCurves) { + for (auto const& [bc, bz] : m_mBezierCurves) { if (bc == bezier) return true; } @@ -471,7 +471,7 @@ std::string CAnimationManager::styleValidInConfigVar(const std::string& config, } return "unknown style"; - } else if (config == "workspaces" || config == "specialWorkspace") { + } else if (config.starts_with("workspaces") || config.starts_with("specialWorkspace")) { if (style == "slide" || style == "slidevert" || style == "fade") return ""; else if (style.starts_with("slidefade")) { diff --git a/src/managers/CursorManager.cpp b/src/managers/CursorManager.cpp index 9b574901..6f8292f7 100644 --- a/src/managers/CursorManager.cpp +++ b/src/managers/CursorManager.cpp @@ -17,11 +17,61 @@ static void hcLogger(enum eHyprcursorLogLevel level, char* message) { Debug::log(NONE, "[hc] {}", message); } -CCursorManager::CCursorManager() { - m_pHyprcursor = std::make_unique(m_szTheme.empty() ? nullptr : m_szTheme.c_str(), hcLogger); - m_pXcursor = std::make_unique(); +CCursorBuffer::CCursorBuffer(cairo_surface_t* surf, const Vector2D& size_, const Vector2D& hot_) : hotspot(hot_) { + surface = surf; + size = size_; + stride = cairo_image_surface_get_stride(surf); +} - if (m_pHyprcursor->valid()) { +CCursorBuffer::CCursorBuffer(uint8_t* pixelData_, const Vector2D& size_, const Vector2D& hot_) : hotspot(hot_) { + pixelData = pixelData_; + size = size_; + stride = 4 * size_.x; +} + +Aquamarine::eBufferCapability CCursorBuffer::caps() { + return Aquamarine::eBufferCapability::BUFFER_CAPABILITY_DATAPTR; +} + +Aquamarine::eBufferType CCursorBuffer::type() { + return Aquamarine::eBufferType::BUFFER_TYPE_SHM; +} + +void CCursorBuffer::update(const Hyprutils::Math::CRegion& damage) { + ; +} + +bool CCursorBuffer::isSynchronous() { + return true; +} + +bool CCursorBuffer::good() { + return true; +} + +Aquamarine::SSHMAttrs CCursorBuffer::shm() { + Aquamarine::SSHMAttrs attrs; + attrs.success = true; + attrs.format = DRM_FORMAT_ARGB8888; + attrs.size = size; + attrs.stride = stride; + return attrs; +} + +std::tuple CCursorBuffer::beginDataPtr(uint32_t flags) { + return {pixelData ? pixelData : cairo_image_surface_get_data(surface), DRM_FORMAT_ARGB8888, stride}; +} + +void CCursorBuffer::endDataPtr() { + ; +} + +CCursorManager::CCursorManager() { + m_pHyprcursor = std::make_unique(m_szTheme.empty() ? nullptr : m_szTheme.c_str(), hcLogger); + m_pXcursor = std::make_unique(); + static auto PUSEHYPRCURSOR = CConfigValue("cursor:enable_hyprcursor"); + + if (m_pHyprcursor->valid() && *PUSEHYPRCURSOR) { // find default size. First, HYPRCURSOR_SIZE then default to 24 auto const* SIZE = getenv("HYPRCURSOR_SIZE"); if (SIZE) { @@ -48,10 +98,12 @@ CCursorManager::CCursorManager() { Debug::log(WARN, "XCURSOR_SIZE size not set, defaulting to size 24"); m_iSize = 24; } - - m_pXcursor->loadTheme(getenv("XCURSOR_THEME") ? getenv("XCURSOR_THEME") : "default", m_iSize * std::ceil(m_fCursorScale)); } + // since we fallback to xcursor always load it on startup. otherwise we end up with a empty theme if hyprcursor is enabled in the config + // and then later is disabled. + m_pXcursor->loadTheme(getenv("XCURSOR_THEME") ? getenv("XCURSOR_THEME") : "default", m_iSize, m_fCursorScale); + m_pAnimationTimer = makeShared(std::nullopt, cursorAnimTimer, this); g_pEventLoopManager->addTimer(m_pAnimationTimer); @@ -65,63 +117,9 @@ CCursorManager::~CCursorManager() { g_pEventLoopManager->removeTimer(m_pAnimationTimer); m_pAnimationTimer.reset(); } -} -void CCursorManager::dropBufferRef(CCursorManager::CCursorBuffer* ref) { - std::erase_if(m_vCursorBuffers, [ref](const auto& buf) { return buf.get() == ref; }); -} - -CCursorManager::CCursorBuffer::CCursorBuffer(cairo_surface_t* surf, const Vector2D& size_, const Vector2D& hot_) : hotspot(hot_) { - surface = surf; - size = size_; - stride = cairo_image_surface_get_stride(surf); -} - -CCursorManager::CCursorBuffer::CCursorBuffer(uint8_t* pixelData_, const Vector2D& size_, const Vector2D& hot_) : hotspot(hot_) { - pixelData = pixelData_; - size = size_; - stride = 4 * size_.x; -} - -CCursorManager::CCursorBuffer::~CCursorBuffer() { - ; -} - -Aquamarine::eBufferCapability CCursorManager::CCursorBuffer::caps() { - return Aquamarine::eBufferCapability::BUFFER_CAPABILITY_DATAPTR; -} - -Aquamarine::eBufferType CCursorManager::CCursorBuffer::type() { - return Aquamarine::eBufferType::BUFFER_TYPE_SHM; -} - -void CCursorManager::CCursorBuffer::update(const Hyprutils::Math::CRegion& damage) { - ; -} - -bool CCursorManager::CCursorBuffer::isSynchronous() { - return true; -} - -bool CCursorManager::CCursorBuffer::good() { - return true; -} - -Aquamarine::SSHMAttrs CCursorManager::CCursorBuffer::shm() { - Aquamarine::SSHMAttrs attrs; - attrs.success = true; - attrs.format = DRM_FORMAT_ARGB8888; - attrs.size = size; - attrs.stride = stride; - return attrs; -} - -std::tuple CCursorManager::CCursorBuffer::beginDataPtr(uint32_t flags) { - return {pixelData ? pixelData : cairo_image_surface_get_data(surface), DRM_FORMAT_ARGB8888, stride}; -} - -void CCursorManager::CCursorBuffer::endDataPtr() { - ; + if (m_pHyprcursor->valid() && m_sCurrentStyleInfo.size > 0) + m_pHyprcursor->cursorSurfaceStyleDone(m_sCurrentStyleInfo); } SP CCursorManager::getCursorBuffer() { @@ -137,91 +135,101 @@ void CCursorManager::setCursorSurface(SP surf, const Vector2D& hotsp m_bOurBufferConnected = false; } -void CCursorManager::setXCursor(const std::string& name) { - float scale = std::ceil(m_fCursorScale); - - auto xcursor = m_pXcursor->getShape(name, m_iSize * scale); - auto& icon = xcursor->images.front(); - - m_vCursorBuffers.emplace_back(makeShared((uint8_t*)icon.pixels.data(), icon.size, icon.hotspot)); - - g_pPointerManager->setCursorBuffer(getCursorBuffer(), icon.hotspot / scale, scale); +void CCursorManager::setCursorBuffer(SP buf, const Vector2D& hotspot, const float& scale) { + m_vCursorBuffers.emplace_back(buf); + g_pPointerManager->setCursorBuffer(getCursorBuffer(), hotspot, scale); if (m_vCursorBuffers.size() > 1) - dropBufferRef(m_vCursorBuffers.at(0).get()); + std::erase_if(m_vCursorBuffers, [this](const auto& buf) { return buf.get() == m_vCursorBuffers.front().get(); }); - m_currentXcursor = xcursor; m_bOurBufferConnected = true; +} - if (m_currentXcursor->images.size() > 1) { - // animated - m_pAnimationTimer->updateTimeout(std::chrono::milliseconds(m_currentXcursor->images[0].delay)); - m_iCurrentAnimationFrame = 0; +void CCursorManager::setAnimationTimer(const int& frame, const int& delay) { + if (delay > 0) { + // arm + m_pAnimationTimer->updateTimeout(std::chrono::milliseconds(delay)); } else { // disarm m_pAnimationTimer->updateTimeout(std::nullopt); } + + m_iCurrentAnimationFrame = frame; } void CCursorManager::setCursorFromName(const std::string& name) { static auto PUSEHYPRCURSOR = CConfigValue("cursor:enable_hyprcursor"); - if (!m_pHyprcursor->valid() || !*PUSEHYPRCURSOR) { - setXCursor(name); - return; - } + auto setXCursor = [this](auto const& name) { + float scale = std::ceil(m_fCursorScale); - m_sCurrentCursorShapeData = m_pHyprcursor->getShape(name.c_str(), m_sCurrentStyleInfo); + auto xcursor = m_pXcursor->getShape(name, m_iSize, m_fCursorScale); + auto& icon = xcursor->images.front(); + auto buf = makeShared((uint8_t*)icon.pixels.data(), icon.size, icon.hotspot); + setCursorBuffer(buf, icon.hotspot / scale, scale); - if (m_sCurrentCursorShapeData.images.size() < 1) { - // try with '_' first (old hc, etc) - std::string newName = name; - std::replace(newName.begin(), newName.end(), '-', '_'); + m_currentXcursor = xcursor; - m_sCurrentCursorShapeData = m_pHyprcursor->getShape(newName.c_str(), m_sCurrentStyleInfo); - } + int delay = 0; + int frame = 0; + if (m_currentXcursor->images.size() > 1) + delay = m_currentXcursor->images[frame].delay; - if (m_sCurrentCursorShapeData.images.size() < 1) { - // fallback to a default if available - constexpr const std::array fallbackShapes = {"default", "left_ptr", "left-ptr"}; + setAnimationTimer(frame, delay); + }; - for (auto& s : fallbackShapes) { - m_sCurrentCursorShapeData = m_pHyprcursor->getShape(s, m_sCurrentStyleInfo); + auto setHyprCursor = [this](auto const& name) { + m_sCurrentCursorShapeData = m_pHyprcursor->getShape(name.c_str(), m_sCurrentStyleInfo); - if (m_sCurrentCursorShapeData.images.size() > 0) - break; + if (m_sCurrentCursorShapeData.images.size() < 1) { + // try with '_' first (old hc, etc) + std::string newName = name; + std::replace(newName.begin(), newName.end(), '-', '_'); + + m_sCurrentCursorShapeData = m_pHyprcursor->getShape(newName.c_str(), m_sCurrentStyleInfo); } if (m_sCurrentCursorShapeData.images.size() < 1) { - Debug::log(ERR, "BUG THIS: No fallback found for a cursor in setCursorFromName"); - setXCursor(name); - return; + // fallback to a default if available + constexpr const std::array fallbackShapes = {"default", "left_ptr", "left-ptr"}; + + for (auto const& s : fallbackShapes) { + m_sCurrentCursorShapeData = m_pHyprcursor->getShape(s, m_sCurrentStyleInfo); + + if (m_sCurrentCursorShapeData.images.size() > 0) + break; + } + + if (m_sCurrentCursorShapeData.images.size() < 1) { + Debug::log(ERR, "BUG THIS: No fallback found for a cursor in setCursorFromName"); + return false; + } } - } - m_vCursorBuffers.emplace_back(makeShared(m_sCurrentCursorShapeData.images[0].surface, - Vector2D{m_sCurrentCursorShapeData.images[0].size, m_sCurrentCursorShapeData.images[0].size}, - Vector2D{m_sCurrentCursorShapeData.images[0].hotspotX, m_sCurrentCursorShapeData.images[0].hotspotY})); + auto buf = + makeShared(m_sCurrentCursorShapeData.images[0].surface, Vector2D{m_sCurrentCursorShapeData.images[0].size, m_sCurrentCursorShapeData.images[0].size}, + Vector2D{m_sCurrentCursorShapeData.images[0].hotspotX, m_sCurrentCursorShapeData.images[0].hotspotY}); + auto hotspot = Vector2D{m_sCurrentCursorShapeData.images[0].hotspotX, m_sCurrentCursorShapeData.images[0].hotspotY} / m_fCursorScale; + setCursorBuffer(buf, hotspot, m_fCursorScale); - g_pPointerManager->setCursorBuffer(getCursorBuffer(), Vector2D{m_sCurrentCursorShapeData.images[0].hotspotX, m_sCurrentCursorShapeData.images[0].hotspotY} / m_fCursorScale, - m_fCursorScale); - if (m_vCursorBuffers.size() > 1) - dropBufferRef(m_vCursorBuffers.at(0).get()); + int delay = 0; + int frame = 0; + if (m_sCurrentCursorShapeData.images.size() > 1) + delay = m_sCurrentCursorShapeData.images[frame].delay; - m_bOurBufferConnected = true; + setAnimationTimer(frame, delay); + return true; + }; - if (m_sCurrentCursorShapeData.images.size() > 1) { - // animated - m_pAnimationTimer->updateTimeout(std::chrono::milliseconds(m_sCurrentCursorShapeData.images[0].delay)); - m_iCurrentAnimationFrame = 0; - } else { - // disarm - m_pAnimationTimer->updateTimeout(std::nullopt); - } + if (!m_pHyprcursor->valid() || !*PUSEHYPRCURSOR || !setHyprCursor(name)) + setXCursor(name); } void CCursorManager::tickAnimatedCursor() { - if (!m_pHyprcursor->valid() && m_currentXcursor->images.size() > 1 && m_bOurBufferConnected) { + if (!m_bOurBufferConnected) + return; + + if (!m_pHyprcursor->valid() && m_currentXcursor->images.size() > 1) { m_iCurrentAnimationFrame++; if ((size_t)m_iCurrentAnimationFrame >= m_currentXcursor->images.size()) @@ -229,39 +237,24 @@ void CCursorManager::tickAnimatedCursor() { float scale = std::ceil(m_fCursorScale); auto& icon = m_currentXcursor->images.at(m_iCurrentAnimationFrame); - m_vCursorBuffers.emplace_back(makeShared((uint8_t*)icon.pixels.data(), icon.size, icon.hotspot)); + auto buf = makeShared((uint8_t*)icon.pixels.data(), icon.size, icon.hotspot); + setCursorBuffer(buf, icon.hotspot / scale, scale); + setAnimationTimer(m_iCurrentAnimationFrame, m_currentXcursor->images[m_iCurrentAnimationFrame].delay); + } else if (m_sCurrentCursorShapeData.images.size() > 1) { + m_iCurrentAnimationFrame++; - g_pPointerManager->setCursorBuffer(getCursorBuffer(), icon.hotspot / scale, scale); + if ((size_t)m_iCurrentAnimationFrame >= m_sCurrentCursorShapeData.images.size()) + m_iCurrentAnimationFrame = 0; - if (m_vCursorBuffers.size() > 1) - dropBufferRef(m_vCursorBuffers.at(0).get()); - - m_pAnimationTimer->updateTimeout(std::chrono::milliseconds(m_currentXcursor->images[m_iCurrentAnimationFrame].delay)); - - return; + auto hotspot = + Vector2D{m_sCurrentCursorShapeData.images[m_iCurrentAnimationFrame].hotspotX, m_sCurrentCursorShapeData.images[m_iCurrentAnimationFrame].hotspotY} / m_fCursorScale; + auto buf = makeShared( + m_sCurrentCursorShapeData.images[m_iCurrentAnimationFrame].surface, + Vector2D{m_sCurrentCursorShapeData.images[m_iCurrentAnimationFrame].size, m_sCurrentCursorShapeData.images[m_iCurrentAnimationFrame].size}, + Vector2D{m_sCurrentCursorShapeData.images[m_iCurrentAnimationFrame].hotspotX, m_sCurrentCursorShapeData.images[m_iCurrentAnimationFrame].hotspotY}); + setCursorBuffer(buf, hotspot, m_fCursorScale); + setAnimationTimer(m_iCurrentAnimationFrame, m_sCurrentCursorShapeData.images[m_iCurrentAnimationFrame].delay); } - - if (m_sCurrentCursorShapeData.images.size() < 2 || !m_bOurBufferConnected) - return; - - m_iCurrentAnimationFrame++; - if ((size_t)m_iCurrentAnimationFrame >= m_sCurrentCursorShapeData.images.size()) - m_iCurrentAnimationFrame = 0; - - m_vCursorBuffers.emplace_back(makeShared( - m_sCurrentCursorShapeData.images[m_iCurrentAnimationFrame].surface, - Vector2D{m_sCurrentCursorShapeData.images[m_iCurrentAnimationFrame].size, m_sCurrentCursorShapeData.images[m_iCurrentAnimationFrame].size}, - Vector2D{m_sCurrentCursorShapeData.images[m_iCurrentAnimationFrame].hotspotX, m_sCurrentCursorShapeData.images[m_iCurrentAnimationFrame].hotspotY})); - - g_pPointerManager->setCursorBuffer( - getCursorBuffer(), - Vector2D{m_sCurrentCursorShapeData.images[m_iCurrentAnimationFrame].hotspotX, m_sCurrentCursorShapeData.images[m_iCurrentAnimationFrame].hotspotY} / m_fCursorScale, - m_fCursorScale); - - if (m_vCursorBuffers.size() > 1) - dropBufferRef(m_vCursorBuffers.at(0).get()); - - m_pAnimationTimer->updateTimeout(std::chrono::milliseconds(m_sCurrentCursorShapeData.images[m_iCurrentAnimationFrame].delay)); } SCursorImageData CCursorManager::dataFor(const std::string& name) { @@ -278,12 +271,13 @@ SCursorImageData CCursorManager::dataFor(const std::string& name) { } void CCursorManager::setXWaylandCursor() { - const auto CURSOR = dataFor("left_ptr"); - if (CURSOR.surface) { + static auto PUSEHYPRCURSOR = CConfigValue("cursor:enable_hyprcursor"); + const auto CURSOR = dataFor("left_ptr"); + if (CURSOR.surface && *PUSEHYPRCURSOR) g_pXWayland->setCursor(cairo_image_surface_get_data(CURSOR.surface), cairo_image_surface_get_stride(CURSOR.surface), {CURSOR.size, CURSOR.size}, {CURSOR.hotspotX, CURSOR.hotspotY}); - } else { - auto xcursor = m_pXcursor->getShape("left_ptr", m_iSize * std::ceil(m_fCursorScale)); + else { + auto xcursor = m_pXcursor->getShape("left_ptr", m_iSize, 1); auto& icon = xcursor->images.front(); g_pXWayland->setCursor((uint8_t*)icon.pixels.data(), icon.size.x * 4, icon.size, icon.hotspot); @@ -291,49 +285,60 @@ void CCursorManager::setXWaylandCursor() { } void CCursorManager::updateTheme() { - float highestScale = 1.0; + static auto PUSEHYPRCURSOR = CConfigValue("cursor:enable_hyprcursor"); + float highestScale = 1.0; - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { if (m->scale > highestScale) highestScale = m->scale; } - if (m_sCurrentStyleInfo.size && m_pHyprcursor->valid()) - m_pHyprcursor->cursorSurfaceStyleDone(m_sCurrentStyleInfo); + m_fCursorScale = highestScale; - m_sCurrentStyleInfo.size = std::round(m_iSize * highestScale); - m_fCursorScale = highestScale; + if (*PUSEHYPRCURSOR) { + if (m_sCurrentStyleInfo.size > 0 && m_pHyprcursor->valid()) + m_pHyprcursor->cursorSurfaceStyleDone(m_sCurrentStyleInfo); - if (m_pHyprcursor->valid()) - m_pHyprcursor->loadThemeStyle(m_sCurrentStyleInfo); + m_sCurrentStyleInfo.size = std::round(m_iSize * highestScale); + + if (m_pHyprcursor->valid()) + m_pHyprcursor->loadThemeStyle(m_sCurrentStyleInfo); + } setCursorFromName("left_ptr"); - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { m->forceFullFrames = 5; g_pCompositor->scheduleFrameForMonitor(m.get(), Aquamarine::IOutput::AQ_SCHEDULE_CURSOR_SHAPE); } } bool CCursorManager::changeTheme(const std::string& name, const int size) { - auto options = Hyprcursor::SManagerOptions(); - options.logFn = hcLogger; - options.allowDefaultFallback = false; + static auto PUSEHYPRCURSOR = CConfigValue("cursor:enable_hyprcursor"); + m_szTheme = name.empty() ? "" : name; + m_iSize = size <= 0 ? 24 : size; + auto xcursor_theme = getenv("XCURSOR_THEME") ? getenv("XCURSOR_THEME") : "default"; - m_pHyprcursor = std::make_unique(name.empty() ? "" : name.c_str(), options); - if (m_pHyprcursor->valid()) { - m_szTheme = name; - m_iSize = size; - updateTheme(); - return true; - } + if (*PUSEHYPRCURSOR) { + auto options = Hyprcursor::SManagerOptions(); + options.logFn = hcLogger; + options.allowDefaultFallback = false; + m_szTheme = name.empty() ? "" : name; + m_iSize = size; - Debug::log(ERR, "Hyprcursor failed loading theme \"{}\", falling back to XCursor.", name); + m_pHyprcursor = std::make_unique(m_szTheme.empty() ? nullptr : m_szTheme.c_str(), options); + if (!m_pHyprcursor->valid()) { + Debug::log(ERR, "Hyprcursor failed loading theme \"{}\", falling back to XCursor.", m_szTheme); + m_pXcursor->loadTheme(m_szTheme.empty() ? xcursor_theme : m_szTheme, m_iSize, m_fCursorScale); + } + } else + m_pXcursor->loadTheme(m_szTheme.empty() ? xcursor_theme : m_szTheme, m_iSize, m_fCursorScale); - m_pXcursor->loadTheme(name, size); - - m_szTheme = name; - m_iSize = size; updateTheme(); + return true; -} \ No newline at end of file +} + +void CCursorManager::syncGsettings() { + m_pXcursor->syncGsettings(); +} diff --git a/src/managers/CursorManager.hpp b/src/managers/CursorManager.hpp index a114b6c2..796ab10e 100644 --- a/src/managers/CursorManager.hpp +++ b/src/managers/CursorManager.hpp @@ -15,6 +15,28 @@ class CWLSurface; AQUAMARINE_FORWARD(IBuffer); +class CCursorBuffer : public Aquamarine::IBuffer { + public: + CCursorBuffer(cairo_surface_t* surf, const Vector2D& size, const Vector2D& hotspot); + CCursorBuffer(uint8_t* pixelData, const Vector2D& size, const Vector2D& hotspot); + ~CCursorBuffer() = default; + + virtual Aquamarine::eBufferCapability caps(); + virtual Aquamarine::eBufferType type(); + virtual void update(const Hyprutils::Math::CRegion& damage); + virtual bool isSynchronous(); // whether the updates to this buffer are synchronous, aka happen over cpu + virtual bool good(); + virtual Aquamarine::SSHMAttrs shm(); + virtual std::tuple beginDataPtr(uint32_t flags); + virtual void endDataPtr(); + + private: + Vector2D hotspot; + cairo_surface_t* surface = nullptr; + uint8_t* pixelData = nullptr; + size_t stride = 0; +}; + class CCursorManager { public: CCursorManager(); @@ -24,44 +46,19 @@ class CCursorManager { void setCursorFromName(const std::string& name); void setCursorSurface(SP surf, const Vector2D& hotspot); - void setXCursor(const std::string& name); + void setCursorBuffer(SP buf, const Vector2D& hotspot, const float& scale); + void setAnimationTimer(const int& frame, const int& delay); bool changeTheme(const std::string& name, const int size); void updateTheme(); SCursorImageData dataFor(const std::string& name); // for xwayland void setXWaylandCursor(); + void syncGsettings(); void tickAnimatedCursor(); - class CCursorBuffer : public Aquamarine::IBuffer { - public: - CCursorBuffer(cairo_surface_t* surf, const Vector2D& size, const Vector2D& hotspot); - CCursorBuffer(uint8_t* pixelData, const Vector2D& size, const Vector2D& hotspot); - ~CCursorBuffer(); - - virtual Aquamarine::eBufferCapability caps(); - virtual Aquamarine::eBufferType type(); - virtual void update(const Hyprutils::Math::CRegion& damage); - virtual bool isSynchronous(); // whether the updates to this buffer are synchronous, aka happen over cpu - virtual bool good(); - virtual Aquamarine::SSHMAttrs shm(); - virtual std::tuple beginDataPtr(uint32_t flags); - virtual void endDataPtr(); - - private: - Vector2D hotspot; - cairo_surface_t* surface = nullptr; - uint8_t* pixelData = nullptr; - size_t stride = 0; - - friend class CCursorManager; - }; - - void dropBufferRef(CCursorBuffer* ref); - - bool m_bOurBufferConnected = false; - private: + bool m_bOurBufferConnected = false; std::vector> m_vCursorBuffers; std::unique_ptr m_pHyprcursor; @@ -79,4 +76,4 @@ class CCursorManager { Hyprcursor::SCursorShapeData m_sCurrentCursorShapeData; }; -inline std::unique_ptr g_pCursorManager; \ No newline at end of file +inline std::unique_ptr g_pCursorManager; diff --git a/src/managers/EventManager.cpp b/src/managers/EventManager.cpp index 75c98e2a..079a6b68 100644 --- a/src/managers/EventManager.cpp +++ b/src/managers/EventManager.cpp @@ -8,6 +8,7 @@ #include #include #include +#include CEventManager::CEventManager() { m_iSocketFD = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); diff --git a/src/managers/HookSystemManager.cpp b/src/managers/HookSystemManager.cpp index 208c79ae..624d2fe1 100644 --- a/src/managers/HookSystemManager.cpp +++ b/src/managers/HookSystemManager.cpp @@ -30,7 +30,7 @@ void CHookSystemManager::emit(std::vector* const callbacks, SCal std::vector faultyHandles; volatile bool needsDeadCleanup = false; - for (auto& cb : *callbacks) { + for (auto const& cb : *callbacks) { m_bCurrentEventPlugin = false; @@ -70,7 +70,7 @@ void CHookSystemManager::emit(std::vector* const callbacks, SCal std::erase_if(*callbacks, [](const auto& fn) { return !fn.fn.lock(); }); if (!faultyHandles.empty()) { - for (auto& h : faultyHandles) + for (auto const& h : faultyHandles) g_pPluginSystem->unloadPlugin(g_pPluginSystem->getPluginByHandle(h), true); } } diff --git a/src/managers/KeybindManager.cpp b/src/managers/KeybindManager.cpp index 09ba7d50..d893e258 100644 --- a/src/managers/KeybindManager.cpp +++ b/src/managers/KeybindManager.cpp @@ -34,7 +34,7 @@ using namespace Hyprutils::String; #include #endif -static std::vector> getHyprlandLaunchEnv() { +static std::vector> getHyprlandLaunchEnv(PHLWORKSPACE pInitialWorkspace) { static auto PINITIALWSTRACKING = CConfigValue("misc:initial_workspace_tracking"); if (!*PINITIALWSTRACKING || g_pConfigManager->isLaunchingExecOnce) @@ -46,11 +46,15 @@ static std::vector> getHyprlandLaunchEnv() { std::vector> result; - result.push_back(std::make_pair<>( - "HL_INITIAL_WORKSPACE_TOKEN", - g_pTokenManager->registerNewToken( - SInitialWorkspaceToken{{}, PMONITOR->activeSpecialWorkspace ? PMONITOR->activeSpecialWorkspace->getConfigName() : PMONITOR->activeWorkspace->getConfigName()}, - std::chrono::months(1337)))); + if (!pInitialWorkspace) { + if (PMONITOR->activeSpecialWorkspace) + pInitialWorkspace = PMONITOR->activeSpecialWorkspace; + else + pInitialWorkspace = PMONITOR->activeWorkspace; + } + + result.push_back(std::make_pair<>("HL_INITIAL_WORKSPACE_TOKEN", + g_pTokenManager->registerNewToken(SInitialWorkspaceToken{{}, pInitialWorkspace->getConfigName()}, std::chrono::months(1337)))); return result; } @@ -180,6 +184,9 @@ uint32_t CKeybindManager::stringToModMask(std::string mods) { } uint32_t CKeybindManager::keycodeToModifier(xkb_keycode_t keycode) { + if (keycode == 0) + return 0; + switch (keycode - 8) { case KEY_LEFTMETA: return HL_MODIFIER_META; case KEY_RIGHTMETA: return HL_MODIFIER_META; @@ -369,7 +376,7 @@ bool CKeybindManager::onKeyEvent(std::any event, SP pKeyboard) { const auto KEYCODE = e.keycode + 8; // Because to xkbcommon it's +8 from libinput - const xkb_keysym_t keysym = xkb_state_key_get_one_sym(pKeyboard->resolveBindsBySym ? pKeyboard->xkbStaticState : m_pXKBTranslationState, KEYCODE); + const xkb_keysym_t keysym = xkb_state_key_get_one_sym(pKeyboard->resolveBindsBySym ? pKeyboard->xkbSymState : m_pXKBTranslationState, KEYCODE); const xkb_keysym_t internalKeysym = xkb_state_key_get_one_sym(pKeyboard->xkbState, KEYCODE); if (handleInternalKeybinds(internalKeysym)) @@ -402,7 +409,7 @@ bool CKeybindManager::onKeyEvent(std::any event, SP pKeyboard) { m_dPressedKeys.push_back(KEY); - suppressEvent = handleKeybinds(MODS, KEY, true); + suppressEvent = !handleKeybinds(MODS, KEY, true).passEvent; if (suppressEvent) shadowKeybinds(keysym, KEYCODE); @@ -424,7 +431,7 @@ bool CKeybindManager::onKeyEvent(std::any event, SP pKeyboard) { if (!foundInPressedKeys) { Debug::log(ERR, "BUG THIS: key not found in m_dPressedKeys"); // fallback with wrong `KEY.modmaskAtPressTime`, this can be buggy - suppressEvent = handleKeybinds(MODS, KEY, false); + suppressEvent = !handleKeybinds(MODS, KEY, false).passEvent; } shadowKeybinds(); @@ -454,14 +461,14 @@ bool CKeybindManager::onAxisEvent(const IPointer::SAxisEvent& e) { bool found = false; if (e.source == WL_POINTER_AXIS_SOURCE_WHEEL && e.axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { if (e.delta < 0) - found = handleKeybinds(MODS, SPressedKeyWithMods{.keyName = "mouse_down"}, true); + found = !handleKeybinds(MODS, SPressedKeyWithMods{.keyName = "mouse_down"}, true).passEvent; else - found = handleKeybinds(MODS, SPressedKeyWithMods{.keyName = "mouse_up"}, true); + found = !handleKeybinds(MODS, SPressedKeyWithMods{.keyName = "mouse_up"}, true).passEvent; } else if (e.source == WL_POINTER_AXIS_SOURCE_WHEEL && e.axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) { if (e.delta < 0) - found = handleKeybinds(MODS, SPressedKeyWithMods{.keyName = "mouse_left"}, true); + found = !handleKeybinds(MODS, SPressedKeyWithMods{.keyName = "mouse_left"}, true).passEvent; else - found = handleKeybinds(MODS, SPressedKeyWithMods{.keyName = "mouse_right"}, true); + found = !handleKeybinds(MODS, SPressedKeyWithMods{.keyName = "mouse_right"}, true).passEvent; } if (found) @@ -497,7 +504,7 @@ bool CKeybindManager::onMouseEvent(const IPointer::SButtonEvent& e) { if (e.state == WL_POINTER_BUTTON_STATE_PRESSED) { m_dPressedKeys.push_back(KEY); - suppressEvent = handleKeybinds(MODS, KEY, true); + suppressEvent = !handleKeybinds(MODS, KEY, true).passEvent; if (suppressEvent) shadowKeybinds(); @@ -507,7 +514,7 @@ bool CKeybindManager::onMouseEvent(const IPointer::SButtonEvent& e) { bool foundInPressedKeys = false; for (auto it = m_dPressedKeys.begin(); it != m_dPressedKeys.end();) { if (it->keyName == KEY_NAME) { - suppressEvent = handleKeybinds(MODS, *it, false); + suppressEvent = !handleKeybinds(MODS, *it, false).passEvent; foundInPressedKeys = true; suppressEvent = !it->sent; it = m_dPressedKeys.erase(it); @@ -518,7 +525,7 @@ bool CKeybindManager::onMouseEvent(const IPointer::SButtonEvent& e) { if (!foundInPressedKeys) { Debug::log(ERR, "BUG THIS: key not found in m_dPressedKeys (2)"); // fallback with wrong `KEY.modmaskAtPressTime`, this can be buggy - suppressEvent = handleKeybinds(MODS, KEY, false); + suppressEvent = !handleKeybinds(MODS, KEY, false).passEvent; } shadowKeybinds(); @@ -587,8 +594,14 @@ eMultiKeyCase CKeybindManager::mkBindMatches(const SKeybind keybind) { return mkKeysymSetMatches(keybind.sMkKeys, m_sMkKeys); } -bool CKeybindManager::handleKeybinds(const uint32_t modmask, const SPressedKeyWithMods& key, bool pressed) { - bool found = false; +std::string CKeybindManager::getCurrentSubmap() { + return m_szCurrentSelectedSubmap; +} + +SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SPressedKeyWithMods& key, bool pressed) { + static auto PDISABLEINHIBIT = CConfigValue("binds:disable_keybind_grabbing"); + bool found = false; + SDispatchResult res; if (pressed) { if (keycodeToModifier(key.keycode)) @@ -602,11 +615,6 @@ bool CKeybindManager::handleKeybinds(const uint32_t modmask, const SPressedKeyWi m_sMkKeys.erase(key.keysym); } - static auto PDISABLEINHIBIT = CConfigValue("binds:disable_keybind_grabbing"); - - if (!*PDISABLEINHIBIT && PROTO::shortcutsInhibit->isInhibited()) - Debug::log(LOG, "Keybind handling is disabled due to an inhibitor"); - for (auto& k : m_lKeybinds) { const bool SPECIALDISPATCHER = k.handler == "global" || k.handler == "pass" || k.handler == "sendshortcut" || k.handler == "mouse"; const bool SPECIALTRIGGERED = @@ -639,18 +647,17 @@ bool CKeybindManager::handleKeybinds(const uint32_t modmask, const SPressedKeyWi if (found || key.submapAtPress != m_szCurrentSelectedSubmap) continue; } else { - // in this case, we only have the keysym to go off. - // if the keysym failed resolving, we can't do anything. It's likely missing - // from the keymap. - if (key.keysym == 0) - return false; + // in this case, we only have the keysym to go off of for this keybind, and it's invalid + // since there might be something like keycode to match with other keybinds, try the next + if (key.keysym == XKB_KEY_NoSymbol) + continue; // oMg such performance hit!!11! // this little maneouver is gonna cost us 4µs const auto KBKEY = xkb_keysym_from_name(k.key.c_str(), XKB_KEYSYM_NO_FLAGS); const auto KBKEYLOWER = xkb_keysym_from_name(k.key.c_str(), XKB_KEYSYM_CASE_INSENSITIVE); - if (KBKEY == 0 && KBKEYLOWER == 0) { + if (KBKEY == XKB_KEY_NoSymbol && KBKEYLOWER == XKB_KEY_NoSymbol) { // Keysym failed to resolve from the key name of the currently iterated bind. // This happens for names such as `switch:off:Lid Switch` as well as some keys // (such as yen and ro). @@ -709,10 +716,11 @@ bool CKeybindManager::handleKeybinds(const uint32_t modmask, const SPressedKeyWi m_iPassPressed = (int)pressed; + // if the dispatchers says to pass event then we will if (k.handler == "mouse") - DISPATCHER->second((pressed ? "1" : "0") + k.arg); + res = DISPATCHER->second((pressed ? "1" : "0") + k.arg); else - DISPATCHER->second(k.arg); + res = DISPATCHER->second(k.arg); m_iPassPressed = -1; @@ -735,7 +743,18 @@ bool CKeybindManager::handleKeybinds(const uint32_t modmask, const SPressedKeyWi found = true; } - return found; + // if keybind wasn't found (or dispatcher said to) then pass event + res.passEvent |= !found; + + if (!found && !*PDISABLEINHIBIT && PROTO::shortcutsInhibit->isInhibited()) { + Debug::log(LOG, "Keybind handling is disabled due to an inhibitor"); + + res.success = false; + if (res.error.empty()) + res.error = "Keybind handling is disabled due to an inhibitor"; + } + + return res; } void CKeybindManager::shadowKeybinds(const xkb_keysym_t& doesntHave, const uint32_t doesntHaveCode) { @@ -754,7 +773,7 @@ void CKeybindManager::shadowKeybinds(const xkb_keysym_t& doesntHave, const uint3 const auto KBKEY = xkb_keysym_from_name(k.key.c_str(), XKB_KEYSYM_CASE_INSENSITIVE); const auto KBKEYUPPER = xkb_keysym_to_upper(KBKEY); - for (auto& pk : m_dPressedKeys) { + for (auto const& pk : m_dPressedKeys) { if ((pk.keysym != 0 && (pk.keysym == KBKEY || pk.keysym == KBKEYUPPER))) { shadow = true; @@ -835,8 +854,12 @@ bool CKeybindManager::handleInternalKeybinds(xkb_keysym_t keysym) { } // Dispatchers +SDispatchResult CKeybindManager::spawn(std::string args) { + const uint64_t PROC = spawnWithRules(args, nullptr); + return {.success = PROC > 0, .error = std::format("Failed to start process {}", args)}; +} -void CKeybindManager::spawn(std::string args) { +uint64_t CKeybindManager::spawnWithRules(std::string args, PHLWORKSPACE pInitialWorkspace) { args = trim(args); @@ -848,23 +871,30 @@ void CKeybindManager::spawn(std::string args) { args = args.substr(args.find_first_of(']') + 1); } - const uint64_t PROC = spawnRaw(args); + const uint64_t PROC = spawnRawProc(args, pInitialWorkspace); if (!RULES.empty()) { const auto RULESLIST = CVarList(RULES, 0, ';'); - for (auto& r : RULESLIST) { + for (auto const& r : RULESLIST) { g_pConfigManager->addExecRule({r, (unsigned long)PROC}); } Debug::log(LOG, "Applied {} rule arguments for exec.", RULESLIST.size()); } + + return PROC; } -uint64_t CKeybindManager::spawnRaw(std::string args) { +SDispatchResult CKeybindManager::spawnRaw(std::string args) { + const uint64_t PROC = spawnRawProc(args, nullptr); + return {.success = PROC > 0, .error = std::format("Failed to start process {}", args)}; +} + +uint64_t CKeybindManager::spawnRawProc(std::string args, PHLWORKSPACE pInitialWorkspace) { Debug::log(LOG, "Executing {}", args); - const auto HLENV = getHyprlandLaunchEnv(); + const auto HLENV = getHyprlandLaunchEnv(pInitialWorkspace); int socket[2]; if (pipe(socket) != 0) { @@ -890,7 +920,7 @@ uint64_t CKeybindManager::spawnRaw(std::string args) { grandchild = fork(); if (grandchild == 0) { // run in grandchild - for (auto& e : HLENV) { + for (auto const& e : HLENV) { setenv(e.first.c_str(), e.second.c_str(), 1); } setenv("WAYLAND_DISPLAY", g_pCompositor->m_szWLDisplaySocket.c_str(), 1); @@ -922,26 +952,30 @@ uint64_t CKeybindManager::spawnRaw(std::string args) { return grandchild; } -void CKeybindManager::killActive(std::string args) { +SDispatchResult CKeybindManager::killActive(std::string args) { g_pCompositor->closeWindow(g_pCompositor->m_pLastWindow.lock()); + + return {}; } -void CKeybindManager::kill(std::string args) { +SDispatchResult CKeybindManager::kill(std::string args) { const auto PWINDOW = g_pCompositor->getWindowByRegex(args); if (!PWINDOW) { Debug::log(ERR, "kill: no window found"); - return; + return {.success = false, .error = "kill: no window found"}; } g_pCompositor->closeWindow(PWINDOW); + + return {}; } void CKeybindManager::clearKeybinds() { m_lKeybinds.clear(); } -static void toggleActiveFloatingCore(std::string args, std::optional floatState) { +static SDispatchResult toggleActiveFloatingCore(std::string args, std::optional floatState) { PHLWINDOW PWINDOW = nullptr; if (args != "active" && args.length() > 1) @@ -950,10 +984,10 @@ static void toggleActiveFloatingCore(std::string args, std::optional float PWINDOW = g_pCompositor->m_pLastWindow.lock(); if (!PWINDOW) - return; + return {.success = false, .error = "Window not found"}; if (floatState.has_value() && floatState == PWINDOW->m_bIsFloating) - return; + return {}; // remove drag status if (!g_pInputManager->currentlyDraggedWindow.expired()) @@ -979,25 +1013,27 @@ static void toggleActiveFloatingCore(std::string args, std::optional float g_pCompositor->updateWorkspaceWindowData(PWINDOW->workspaceID()); g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->m_iMonitorID); g_pCompositor->updateAllWindowsAnimatedDecorationValues(); + + return {}; } -void CKeybindManager::toggleActiveFloating(std::string args) { +SDispatchResult CKeybindManager::toggleActiveFloating(std::string args) { return toggleActiveFloatingCore(args, std::nullopt); } -void CKeybindManager::setActiveFloating(std::string args) { +SDispatchResult CKeybindManager::setActiveFloating(std::string args) { return toggleActiveFloatingCore(args, true); } -void CKeybindManager::setActiveTiled(std::string args) { +SDispatchResult CKeybindManager::setActiveTiled(std::string args) { return toggleActiveFloatingCore(args, false); } -void CKeybindManager::centerWindow(std::string args) { +SDispatchResult CKeybindManager::centerWindow(std::string args) { const auto PWINDOW = g_pCompositor->m_pLastWindow.lock(); if (!PWINDOW || !PWINDOW->m_bIsFloating || PWINDOW->isFullscreen()) - return; + return {.success = false, .error = "No floating window found"}; const auto PMONITOR = g_pCompositor->getMonitorFromID(PWINDOW->m_iMonitorID); @@ -1007,9 +1043,11 @@ void CKeybindManager::centerWindow(std::string args) { PWINDOW->m_vRealPosition = PMONITOR->middle() - PWINDOW->m_vRealSize.goal() / 2.f + RESERVEDOFFSET; PWINDOW->m_vPosition = PWINDOW->m_vRealPosition.goal(); + + return {}; } -void CKeybindManager::toggleActivePseudo(std::string args) { +SDispatchResult CKeybindManager::toggleActivePseudo(std::string args) { PHLWINDOW PWINDOW = nullptr; if (args != "active" && args.length() > 1) @@ -1018,12 +1056,14 @@ void CKeybindManager::toggleActivePseudo(std::string args) { PWINDOW = g_pCompositor->m_pLastWindow.lock(); if (!PWINDOW) - return; + return {.success = false, .error = "Window not found"}; PWINDOW->m_bIsPseudotiled = !PWINDOW->m_bIsPseudotiled; if (!PWINDOW->isFullscreen()) g_pLayoutManager->getCurrentLayout()->recalculateWindow(PWINDOW); + + return {}; } SWorkspaceIDName getWorkspaceToChangeFromArgs(std::string args, PHLWORKSPACE PCURRENTWORKSPACE) { @@ -1049,7 +1089,7 @@ SWorkspaceIDName getWorkspaceToChangeFromArgs(std::string args, PHLWORKSPACE PCU return {ID, PPREVWS.name.empty() ? std::to_string(PPREVWS.id) : PPREVWS.name}; } -void CKeybindManager::changeworkspace(std::string args) { +SDispatchResult CKeybindManager::changeworkspace(std::string args) { // Workspace_back_and_forth being enabled means that an attempt to switch to // the current workspace will instead switch to the previous. static auto PBACKANDFORTH = CConfigValue("binds:workspace_back_and_forth"); @@ -1059,7 +1099,7 @@ void CKeybindManager::changeworkspace(std::string args) { const auto PMONITOR = g_pCompositor->m_pLastMonitor.get(); if (!PMONITOR) - return; + return {.success = false, .error = "Last monitor not found"}; const auto PCURRENTWORKSPACE = PMONITOR->activeWorkspace; const bool EXPLICITPREVIOUS = args.contains("previous"); @@ -1067,18 +1107,17 @@ void CKeybindManager::changeworkspace(std::string args) { const auto& [workspaceToChangeTo, workspaceName] = getWorkspaceToChangeFromArgs(args, PCURRENTWORKSPACE); if (workspaceToChangeTo == WORKSPACE_INVALID) { Debug::log(ERR, "Error in changeworkspace, invalid value"); - return; + return {.success = false, .error = "Error in changeworkspace, invalid value"}; } - if (workspaceToChangeTo == WORKSPACE_NOT_CHANGED) { - return; - } + if (workspaceToChangeTo == WORKSPACE_NOT_CHANGED) + return {}; const auto PREVWS = PCURRENTWORKSPACE->getPrevWorkspaceIDName(args.contains("_per_monitor")); const bool BISWORKSPACECURRENT = workspaceToChangeTo == PCURRENTWORKSPACE->m_iID; if (BISWORKSPACECURRENT && (!(*PBACKANDFORTH || EXPLICITPREVIOUS) || PREVWS.id == -1)) - return; + return {.success = false, .error = "Previous workspace doesn't exist"}; g_pInputManager->unconstrainMouse(); g_pInputManager->m_bEmptyFocusCursorSet = false; @@ -1091,7 +1130,7 @@ void CKeybindManager::changeworkspace(std::string args) { if (!BISWORKSPACECURRENT && pWorkspaceToChangeTo->m_bIsSpecialWorkspace) { PMONITOR->setSpecialWorkspace(pWorkspaceToChangeTo); g_pInputManager->simulateMouseMovement(); - return; + return {}; } g_pInputManager->releaseAllMouseButtons(); @@ -1099,7 +1138,7 @@ void CKeybindManager::changeworkspace(std::string args) { const auto PMONITORWORKSPACEOWNER = PMONITOR->ID == pWorkspaceToChangeTo->m_iMonitorID ? PMONITOR : g_pCompositor->getMonitorFromID(pWorkspaceToChangeTo->m_iMonitorID); if (!PMONITORWORKSPACEOWNER) - return; + return {.success = false, .error = "Workspace to switch to has no monitor"}; updateRelativeCursorCoords(); @@ -1141,13 +1180,15 @@ void CKeybindManager::changeworkspace(std::string args) { if (PLAST && (!HLSurface || HLSurface->getWindow())) PLAST->warpCursor(); } + + return {}; } -void CKeybindManager::fullscreenActive(std::string args) { +SDispatchResult CKeybindManager::fullscreenActive(std::string args) { const auto PWINDOW = g_pCompositor->m_pLastWindow.lock(); if (!PWINDOW) - return; + return {.success = false, .error = "Window not found"}; const eFullscreenMode MODE = args == "1" ? FSMODE_MAXIMIZED : FSMODE_FULLSCREEN; @@ -1155,14 +1196,18 @@ void CKeybindManager::fullscreenActive(std::string args) { g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE); else g_pCompositor->setWindowFullscreenInternal(PWINDOW, MODE); + + return {}; } -void CKeybindManager::fullscreenStateActive(std::string args) { +SDispatchResult CKeybindManager::fullscreenStateActive(std::string args) { const auto PWINDOW = g_pCompositor->m_pLastWindow.lock(); const auto ARGS = CVarList(args, 2, ' '); if (!PWINDOW) - return; + return {.success = false, .error = "Window not found"}; + + PWINDOW->m_sWindowData.syncFullscreen = CWindowOverridableVar(false, PRIORITY_SET_PROP); int internalMode, clientMode; try { @@ -1175,29 +1220,21 @@ void CKeybindManager::fullscreenStateActive(std::string args) { const sFullscreenState STATE = sFullscreenState{.internal = (internalMode != -1 ? (eFullscreenMode)internalMode : PWINDOW->m_sFullscreenState.internal), .client = (clientMode != -1 ? (eFullscreenMode)clientMode : PWINDOW->m_sFullscreenState.client)}; - if (internalMode != -1 && clientMode != -1 && PWINDOW->m_sFullscreenState.internal == STATE.internal && PWINDOW->m_sFullscreenState.client == STATE.client) { + if (internalMode != -1 && clientMode != -1 && PWINDOW->m_sFullscreenState.internal == STATE.internal && PWINDOW->m_sFullscreenState.client == STATE.client) g_pCompositor->setWindowFullscreenState(PWINDOW, sFullscreenState{.internal = FSMODE_NONE, .client = FSMODE_NONE}); - PWINDOW->m_sWindowData.syncFullscreen = CWindowOverridableVar(true, PRIORITY_SET_PROP); - return; - } + else if (internalMode != -1 && clientMode == -1 && PWINDOW->m_sFullscreenState.internal == STATE.internal) + g_pCompositor->setWindowFullscreenState(PWINDOW, sFullscreenState{.internal = FSMODE_NONE, .client = PWINDOW->m_sFullscreenState.client}); + else if (internalMode == -1 && clientMode != -1 && PWINDOW->m_sFullscreenState.client == STATE.client) + g_pCompositor->setWindowFullscreenState(PWINDOW, sFullscreenState{.internal = PWINDOW->m_sFullscreenState.internal, .client = FSMODE_NONE}); + else + g_pCompositor->setWindowFullscreenState(PWINDOW, STATE); - if (internalMode != -1 && clientMode == -1 && PWINDOW->m_sFullscreenState.internal == STATE.internal) { - g_pCompositor->setWindowFullscreenState(PWINDOW, sFullscreenState{.internal = PWINDOW->m_sFullscreenState.client, .client = PWINDOW->m_sFullscreenState.client}); - PWINDOW->m_sWindowData.syncFullscreen = CWindowOverridableVar(true, PRIORITY_SET_PROP); - return; - } + PWINDOW->m_sWindowData.syncFullscreen = CWindowOverridableVar(PWINDOW->m_sFullscreenState.internal == PWINDOW->m_sFullscreenState.client, PRIORITY_SET_PROP); - if (internalMode == -1 && clientMode != -1 && PWINDOW->m_sFullscreenState.client == STATE.client) { - g_pCompositor->setWindowFullscreenState(PWINDOW, sFullscreenState{.internal = PWINDOW->m_sFullscreenState.internal, .client = PWINDOW->m_sFullscreenState.internal}); - PWINDOW->m_sWindowData.syncFullscreen = CWindowOverridableVar(true, PRIORITY_SET_PROP); - return; - } - - PWINDOW->m_sWindowData.syncFullscreen = CWindowOverridableVar(false, PRIORITY_SET_PROP); - g_pCompositor->setWindowFullscreenState(PWINDOW, STATE); + return {}; } -void CKeybindManager::moveActiveToWorkspace(std::string args) { +SDispatchResult CKeybindManager::moveActiveToWorkspace(std::string args) { PHLWINDOW PWINDOW = nullptr; @@ -1209,17 +1246,17 @@ void CKeybindManager::moveActiveToWorkspace(std::string args) { } if (!PWINDOW) - return; + return {.success = false, .error = "Window not found"}; const auto& [WORKSPACEID, workspaceName] = getWorkspaceIDNameFromString(args); if (WORKSPACEID == WORKSPACE_INVALID) { Debug::log(LOG, "Invalid workspace in moveActiveToWorkspace"); - return; + return {.success = false, .error = "Invalid workspace in moveActiveToWorkspace"}; } if (WORKSPACEID == PWINDOW->workspaceID()) { Debug::log(LOG, "Not moving to workspace because it didn't change."); - return; + return {.success = false, .error = "Not moving to workspace because it didn't change."}; } auto pWorkspace = g_pCompositor->getWorkspaceByID(WORKSPACEID); @@ -1255,9 +1292,11 @@ void CKeybindManager::moveActiveToWorkspace(std::string args) { g_pCompositor->focusWindow(PWINDOW); PWINDOW->warpCursor(); + + return {}; } -void CKeybindManager::moveActiveToWorkspaceSilent(std::string args) { +SDispatchResult CKeybindManager::moveActiveToWorkspaceSilent(std::string args) { PHLWINDOW PWINDOW = nullptr; const auto ORIGINALARGS = args; @@ -1270,16 +1309,16 @@ void CKeybindManager::moveActiveToWorkspaceSilent(std::string args) { } if (!PWINDOW) - return; + return {.success = false, .error = "Window not found"}; const auto& [WORKSPACEID, workspaceName] = getWorkspaceIDNameFromString(args); if (WORKSPACEID == WORKSPACE_INVALID) { Debug::log(ERR, "Error in moveActiveToWorkspaceSilent, invalid value"); - return; + return {.success = false, .error = "Error in moveActiveToWorkspaceSilent, invalid value"}; } if (WORKSPACEID == PWINDOW->workspaceID()) - return; + return {}; g_pHyprRenderer->damageWindow(PWINDOW); @@ -1299,16 +1338,18 @@ void CKeybindManager::moveActiveToWorkspaceSilent(std::string args) { else g_pInputManager->refocus(); } + + return {}; } -void CKeybindManager::moveFocusTo(std::string args) { +SDispatchResult CKeybindManager::moveFocusTo(std::string args) { static auto PFULLCYCLE = CConfigValue("binds:movefocus_cycles_fullscreen"); static auto PMONITORFALLBACK = CConfigValue("binds:window_direction_monitor_fallback"); char arg = args[0]; if (!isDirection(args)) { Debug::log(ERR, "Cannot move focus in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg); - return; + return {.success = false, .error = std::format("Cannot move focus in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg)}; } const auto PLASTWINDOW = g_pCompositor->m_pLastWindow.lock(); @@ -1316,7 +1357,7 @@ void CKeybindManager::moveFocusTo(std::string args) { if (*PMONITORFALLBACK) tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(arg)); - return; + return {}; } const auto PWINDOWTOCHANGETO = *PFULLCYCLE && PLASTWINDOW->isFullscreen() ? @@ -1326,69 +1367,81 @@ void CKeybindManager::moveFocusTo(std::string args) { // Found window in direction, switch to it if (PWINDOWTOCHANGETO) { switchToWindow(PWINDOWTOCHANGETO); - return; + return {}; } Debug::log(LOG, "No window found in direction {}, looking for a monitor", arg); if (*PMONITORFALLBACK && tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(arg))) - return; + return {}; static auto PNOFALLBACK = CConfigValue("general:no_focus_fallback"); if (*PNOFALLBACK) - return; + return {.success = false, .error = std::format("Nothing to focus to in direction {}", arg)}; Debug::log(LOG, "No monitor found in direction {}, falling back to next window on current workspace", arg); const auto PWINDOWNEXT = g_pCompositor->getNextWindowOnWorkspace(PLASTWINDOW, true); if (PWINDOWNEXT) switchToWindow(PWINDOWNEXT); + + return {}; } -void CKeybindManager::focusUrgentOrLast(std::string args) { +SDispatchResult CKeybindManager::focusUrgentOrLast(std::string args) { const auto PWINDOWURGENT = g_pCompositor->getUrgentWindow(); const auto PWINDOWPREV = g_pCompositor->m_pLastWindow.lock() ? (g_pCompositor->m_vWindowFocusHistory.size() < 2 ? nullptr : g_pCompositor->m_vWindowFocusHistory[1].lock()) : (g_pCompositor->m_vWindowFocusHistory.empty() ? nullptr : g_pCompositor->m_vWindowFocusHistory[0].lock()); if (!PWINDOWURGENT && !PWINDOWPREV) - return; + return {.success = false, .error = "Window not found"}; switchToWindow(PWINDOWURGENT ? PWINDOWURGENT : PWINDOWPREV); + + return {}; } -void CKeybindManager::focusCurrentOrLast(std::string args) { +SDispatchResult CKeybindManager::focusCurrentOrLast(std::string args) { const auto PWINDOWPREV = g_pCompositor->m_pLastWindow.lock() ? (g_pCompositor->m_vWindowFocusHistory.size() < 2 ? nullptr : g_pCompositor->m_vWindowFocusHistory[1].lock()) : (g_pCompositor->m_vWindowFocusHistory.empty() ? nullptr : g_pCompositor->m_vWindowFocusHistory[0].lock()); if (!PWINDOWPREV) - return; + return {.success = false, .error = "Window not found"}; switchToWindow(PWINDOWPREV); + + return {}; } -void CKeybindManager::swapActive(std::string args) { +SDispatchResult CKeybindManager::swapActive(std::string args) { char arg = args[0]; if (!isDirection(args)) { Debug::log(ERR, "Cannot move window in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg); - return; + return {.success = false, .error = std::format("Cannot move window in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg)}; } Debug::log(LOG, "Swapping active window in direction {}", arg); const auto PLASTWINDOW = g_pCompositor->m_pLastWindow.lock(); - if (!PLASTWINDOW || PLASTWINDOW->isFullscreen()) - return; + + if (!PLASTWINDOW) + return {.success = false, .error = "Window to swap with not found"}; + + if (PLASTWINDOW->isFullscreen()) + return {.success = false, .error = "Can't swap fullscreen window"}; const auto PWINDOWTOCHANGETO = g_pCompositor->getWindowInDirection(PLASTWINDOW, arg); if (!PWINDOWTOCHANGETO) - return; + return {.success = false, .error = "Window to swap with not found"}; updateRelativeCursorCoords(); g_pLayoutManager->getCurrentLayout()->switchWindows(PLASTWINDOW, PWINDOWTOCHANGETO); PLASTWINDOW->warpCursor(); + + return {}; } -void CKeybindManager::moveActiveTo(std::string args) { +SDispatchResult CKeybindManager::moveActiveTo(std::string args) { char arg = args[0]; bool silent = args.ends_with(" silent"); if (silent) @@ -1397,25 +1450,28 @@ void CKeybindManager::moveActiveTo(std::string args) { if (args.starts_with("mon:")) { const auto PNEWMONITOR = g_pCompositor->getMonitorFromString(args.substr(4)); if (!PNEWMONITOR) - return; + return {.success = false, .error = std::format("Monitor {} not found", args.substr(4))}; if (silent) moveActiveToWorkspaceSilent(PNEWMONITOR->activeWorkspace->getConfigName()); else moveActiveToWorkspace(PNEWMONITOR->activeWorkspace->getConfigName()); - return; + return {}; } if (!isDirection(args)) { Debug::log(ERR, "Cannot move window in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg); - return; + return {.success = false, .error = std::format("Cannot move window in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg)}; } const auto PLASTWINDOW = g_pCompositor->m_pLastWindow.lock(); - if (!PLASTWINDOW || PLASTWINDOW->isFullscreen()) - return; + if (!PLASTWINDOW) + return {.success = false, .error = "Window to move not found"}; + + if (PLASTWINDOW->isFullscreen()) + return {.success = false, .error = "Can't move fullscreen window"}; if (PLASTWINDOW->m_bIsFloating) { std::optional vPosx, vPosy; @@ -1433,7 +1489,7 @@ void CKeybindManager::moveActiveTo(std::string args) { PLASTWINDOW->m_vRealPosition = Vector2D(vPosx.value_or(PLASTWINDOW->m_vRealPosition.goal().x), vPosy.value_or(PLASTWINDOW->m_vRealPosition.goal().y)); - return; + return {}; } // If the window to change to is on the same workspace, switch them @@ -1444,30 +1500,32 @@ void CKeybindManager::moveActiveTo(std::string args) { g_pLayoutManager->getCurrentLayout()->moveWindowTo(PLASTWINDOW, args, silent); if (!silent) PLASTWINDOW->warpCursor(); - return; + return {}; } static auto PMONITORFALLBACK = CConfigValue("binds:window_direction_monitor_fallback"); if (!*PMONITORFALLBACK) - return; + return {}; // Otherwise, we always want to move to the next monitor in that direction const auto PMONITORTOCHANGETO = g_pCompositor->getMonitorInDirection(arg); if (!PMONITORTOCHANGETO) - return; + return {.success = false, .error = "Nowhere to move active window to"}; const auto PWORKSPACE = PMONITORTOCHANGETO->activeWorkspace; if (silent) moveActiveToWorkspaceSilent(PWORKSPACE->getConfigName()); else moveActiveToWorkspace(PWORKSPACE->getConfigName()); + + return {}; } -void CKeybindManager::toggleGroup(std::string args) { +SDispatchResult CKeybindManager::toggleGroup(std::string args) { const auto PWINDOW = g_pCompositor->m_pLastWindow.lock(); if (!PWINDOW) - return; + return {.success = false, .error = "Window not found"}; if (PWINDOW->isFullscreen()) g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE); @@ -1476,30 +1534,32 @@ void CKeybindManager::toggleGroup(std::string args) { PWINDOW->createGroup(); else PWINDOW->destroyGroup(); + + return {}; } -void CKeybindManager::changeGroupActive(std::string args) { +SDispatchResult CKeybindManager::changeGroupActive(std::string args) { const auto PWINDOW = g_pCompositor->m_pLastWindow.lock(); if (!PWINDOW) - return; + return {.success = false, .error = "Window not found"}; if (PWINDOW->m_sGroupData.pNextWindow.expired()) - return; + return {.success = false, .error = "No next window in group"}; if (PWINDOW->m_sGroupData.pNextWindow.lock() == PWINDOW) - return; + return {.success = false, .error = "Only one window in group"}; if (isNumber(args, false)) { // index starts from '1'; '0' means last window const int INDEX = std::stoi(args); if (INDEX > PWINDOW->getGroupSize()) - return; + return {}; if (INDEX == 0) PWINDOW->setGroupCurrent(PWINDOW->getGroupTail()); else PWINDOW->setGroupCurrent(PWINDOW->getGroupWindowByIndex(INDEX - 1)); - return; + return {}; } if (args != "b" && args != "prev") { @@ -1507,39 +1567,45 @@ void CKeybindManager::changeGroupActive(std::string args) { } else { PWINDOW->setGroupCurrent(PWINDOW->getGroupPrevious()); } + + return {}; } -void CKeybindManager::toggleSplit(std::string args) { +SDispatchResult CKeybindManager::toggleSplit(std::string args) { SLayoutMessageHeader header; header.pWindow = g_pCompositor->m_pLastWindow.lock(); if (!header.pWindow) - return; + return {.success = false, .error = "Window not found"}; const auto PWORKSPACE = header.pWindow->m_pWorkspace; if (PWORKSPACE->m_bHasFullscreenWindow) - return; + return {.success = false, .error = "Can't split windows that already split"}; g_pLayoutManager->getCurrentLayout()->layoutMessage(header, "togglesplit"); + + return {}; } -void CKeybindManager::swapSplit(std::string args) { +SDispatchResult CKeybindManager::swapSplit(std::string args) { SLayoutMessageHeader header; header.pWindow = g_pCompositor->m_pLastWindow.lock(); if (!header.pWindow) - return; + return {.success = false, .error = "Window not found"}; const auto PWORKSPACE = header.pWindow->m_pWorkspace; if (PWORKSPACE->m_bHasFullscreenWindow) - return; + return {.success = false, .error = "Can't split windows that already split"}; g_pLayoutManager->getCurrentLayout()->layoutMessage(header, "swapsplit"); + + return {}; } -void CKeybindManager::alterSplitRatio(std::string args) { +SDispatchResult CKeybindManager::alterSplitRatio(std::string args) { std::optional splitResult; bool exact = false; @@ -1551,39 +1617,43 @@ void CKeybindManager::alterSplitRatio(std::string args) { if (!splitResult.has_value()) { Debug::log(ERR, "Splitratio invalid in alterSplitRatio!"); - return; + return {.success = false, .error = "Splitratio invalid in alterSplitRatio!"}; } const auto PLASTWINDOW = g_pCompositor->m_pLastWindow.lock(); if (!PLASTWINDOW) - return; + return {.success = false, .error = "Window not found"}; g_pLayoutManager->getCurrentLayout()->alterSplitRatio(PLASTWINDOW, splitResult.value(), exact); + + return {}; } -void CKeybindManager::focusMonitor(std::string arg) { +SDispatchResult CKeybindManager::focusMonitor(std::string arg) { const auto PMONITOR = g_pCompositor->getMonitorFromString(arg); tryMoveFocusToMonitor(PMONITOR); + + return {}; } -void CKeybindManager::moveCursorToCorner(std::string arg) { +SDispatchResult CKeybindManager::moveCursorToCorner(std::string arg) { if (!isNumber(arg)) { Debug::log(ERR, "moveCursorToCorner, arg has to be a number."); - return; + return {.success = false, .error = "moveCursorToCorner, arg has to be a number."}; } const auto CORNER = std::stoi(arg); if (CORNER < 0 || CORNER > 3) { Debug::log(ERR, "moveCursorToCorner, corner not 0 - 3."); - return; + return {.success = false, .error = "moveCursorToCorner, corner not 0 - 3."}; } const auto PWINDOW = g_pCompositor->m_pLastWindow.lock(); if (!PWINDOW) - return; + return {.success = false, .error = "Window not found"}; switch (CORNER) { case 0: @@ -1604,16 +1674,18 @@ void CKeybindManager::moveCursorToCorner(std::string arg) { g_pCompositor->warpCursorTo({PWINDOW->m_vRealPosition.value().x, PWINDOW->m_vRealPosition.value().y}, true); break; } + + return {}; } -void CKeybindManager::moveCursor(std::string args) { +SDispatchResult CKeybindManager::moveCursor(std::string args) { std::string x_str, y_str; int x, y; size_t i = args.find_first_of(' '); if (i == std::string::npos) { Debug::log(ERR, "moveCursor, takes 2 arguments."); - return; + return {.success = false, .error = "moveCursor, takes 2 arguments"}; } x_str = args.substr(0, i); @@ -1621,32 +1693,34 @@ void CKeybindManager::moveCursor(std::string args) { if (!isNumber(x_str)) { Debug::log(ERR, "moveCursor, x argument has to be a number."); - return; + return {.success = false, .error = "moveCursor, x argument has to be a number."}; } if (!isNumber(y_str)) { Debug::log(ERR, "moveCursor, y argument has to be a number."); - return; + return {.success = false, .error = "moveCursor, y argument has to be a number."}; } x = std::stoi(x_str); y = std::stoi(y_str); g_pCompositor->warpCursorTo({x, y}, true); + + return {}; } -void CKeybindManager::workspaceOpt(std::string args) { +SDispatchResult CKeybindManager::workspaceOpt(std::string args) { // current workspace const auto PWORKSPACE = g_pCompositor->m_pLastMonitor->activeWorkspace; if (!PWORKSPACE) - return; // ???? + return {.success = false, .error = "Workspace not found"}; // ???? if (args == "allpseudo") { PWORKSPACE->m_bDefaultPseudo = !PWORKSPACE->m_bDefaultPseudo; // apply - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (!w->m_bIsMapped || w->m_pWorkspace != PWORKSPACE) continue; @@ -1658,10 +1732,10 @@ void CKeybindManager::workspaceOpt(std::string args) { // we make a copy because changeWindowFloatingMode might invalidate the iterator std::deque ptrs; - for (auto& w : g_pCompositor->m_vWindows) + for (auto const& w : g_pCompositor->m_vWindows) ptrs.push_back(w); - for (auto& w : ptrs) { + for (auto const& w : ptrs) { if (!w->m_bIsMapped || w->m_pWorkspace != PWORKSPACE || w->isHidden()) continue; @@ -1683,14 +1757,16 @@ void CKeybindManager::workspaceOpt(std::string args) { } } else { Debug::log(ERR, "Invalid arg in workspaceOpt, opt \"{}\" doesn't exist.", args); - return; + return {.success = false, .error = std::format("Invalid arg in workspaceOpt, opt \"{}\" doesn't exist.", args)}; } // recalc mon g_pLayoutManager->getCurrentLayout()->recalculateMonitor(g_pCompositor->m_pLastMonitor->ID); + + return {}; } -void CKeybindManager::renameWorkspace(std::string args) { +SDispatchResult CKeybindManager::renameWorkspace(std::string args) { try { const auto FIRSTSPACEPOS = args.find_first_of(' '); if (FIRSTSPACEPOS != std::string::npos) { @@ -1700,34 +1776,47 @@ void CKeybindManager::renameWorkspace(std::string args) { } else { g_pCompositor->renameWorkspace(std::stoi(args), ""); } - } catch (std::exception& e) { Debug::log(ERR, "Invalid arg in renameWorkspace, expected numeric id only or a numeric id and string name. \"{}\": \"{}\"", args, e.what()); } + } catch (std::exception& e) { + Debug::log(ERR, "Invalid arg in renameWorkspace, expected numeric id only or a numeric id and string name. \"{}\": \"{}\"", args, e.what()); + return {.success = false, .error = std::format("Invalid arg in renameWorkspace, expected numeric id only or a numeric id and string name. \"{}\": \"{}\"", args, e.what())}; + } + + return {}; } -void CKeybindManager::exitHyprland(std::string argz) { +SDispatchResult CKeybindManager::exitHyprland(std::string argz) { + g_pConfigManager->dispatchExecShutdown(); + + if (g_pCompositor->m_bFinalRequests) + return {}; // Exiting deferred until requests complete + g_pCompositor->stopCompositor(); + return {}; } -void CKeybindManager::moveCurrentWorkspaceToMonitor(std::string args) { +SDispatchResult CKeybindManager::moveCurrentWorkspaceToMonitor(std::string args) { CMonitor* PMONITOR = g_pCompositor->getMonitorFromString(args); if (!PMONITOR) { Debug::log(ERR, "Ignoring moveCurrentWorkspaceToMonitor: monitor doesnt exist"); - return; + return {.success = false, .error = "Ignoring moveCurrentWorkspaceToMonitor: monitor doesnt exist"}; } // get the current workspace const auto PCURRENTWORKSPACE = g_pCompositor->m_pLastMonitor->activeWorkspace; if (!PCURRENTWORKSPACE) { Debug::log(ERR, "moveCurrentWorkspaceToMonitor invalid workspace!"); - return; + return {.success = false, .error = "moveCurrentWorkspaceToMonitor invalid workspace!"}; } g_pCompositor->moveWorkspaceToMonitor(PCURRENTWORKSPACE, PMONITOR); + + return {}; } -void CKeybindManager::moveWorkspaceToMonitor(std::string args) { +SDispatchResult CKeybindManager::moveWorkspaceToMonitor(std::string args) { if (!args.contains(' ')) - return; + return {}; std::string workspace = args.substr(0, args.find_first_of(' ')); std::string monitor = args.substr(args.find_first_of(' ') + 1); @@ -1736,38 +1825,40 @@ void CKeybindManager::moveWorkspaceToMonitor(std::string args) { if (!PMONITOR) { Debug::log(ERR, "Ignoring moveWorkspaceToMonitor: monitor doesnt exist"); - return; + return {.success = false, .error = "Ignoring moveWorkspaceToMonitor: monitor doesnt exist"}; } - const int WORKSPACEID = getWorkspaceIDNameFromString(workspace).id; + const auto WORKSPACEID = getWorkspaceIDNameFromString(workspace).id; if (WORKSPACEID == WORKSPACE_INVALID) { Debug::log(ERR, "moveWorkspaceToMonitor invalid workspace!"); - return; + return {.success = false, .error = "moveWorkspaceToMonitor invalid workspace!"}; } const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(WORKSPACEID); if (!PWORKSPACE) { Debug::log(ERR, "moveWorkspaceToMonitor workspace doesn't exist!"); - return; + return {.success = false, .error = "moveWorkspaceToMonitor workspace doesn't exist!"}; } g_pCompositor->moveWorkspaceToMonitor(PWORKSPACE, PMONITOR); + + return {}; } -void CKeybindManager::focusWorkspaceOnCurrentMonitor(std::string args) { - int workspaceID = getWorkspaceIDNameFromString(args).id; +SDispatchResult CKeybindManager::focusWorkspaceOnCurrentMonitor(std::string args) { + auto workspaceID = getWorkspaceIDNameFromString(args).id; if (workspaceID == WORKSPACE_INVALID) { Debug::log(ERR, "focusWorkspaceOnCurrentMonitor invalid workspace!"); - return; + return {.success = false, .error = "focusWorkspaceOnCurrentMonitor invalid workspace!"}; } const auto PCURRMONITOR = g_pCompositor->m_pLastMonitor.get(); if (!PCURRMONITOR) { Debug::log(ERR, "focusWorkspaceOnCurrentMonitor monitor doesn't exist!"); - return; + return {.success = false, .error = "focusWorkspaceOnCurrentMonitor monitor doesn't exist!"}; } auto pWorkspace = g_pCompositor->getWorkspaceByID(workspaceID); @@ -1776,7 +1867,7 @@ void CKeybindManager::focusWorkspaceOnCurrentMonitor(std::string args) { pWorkspace = g_pCompositor->createNewWorkspace(workspaceID, PCURRMONITOR->ID); // we can skip the moving, since it's already on the current monitor changeworkspace(pWorkspace->getConfigName()); - return; + return {}; } static auto PBACKANDFORTH = CConfigValue("binds:workspace_back_and_forth"); @@ -1795,31 +1886,33 @@ void CKeybindManager::focusWorkspaceOnCurrentMonitor(std::string args) { const auto POLDMONITOR = g_pCompositor->getMonitorFromID(pWorkspace->m_iMonitorID); if (!POLDMONITOR) { // wat Debug::log(ERR, "focusWorkspaceOnCurrentMonitor old monitor doesn't exist!"); - return; + return {.success = false, .error = "focusWorkspaceOnCurrentMonitor old monitor doesn't exist!"}; } if (POLDMONITOR->activeWorkspaceID() == workspaceID) { g_pCompositor->swapActiveWorkspaces(POLDMONITOR, PCURRMONITOR); - return; + return {}; } else { g_pCompositor->moveWorkspaceToMonitor(pWorkspace, PCURRMONITOR, true); } } changeworkspace(pWorkspace->getConfigName()); + + return {}; } -void CKeybindManager::toggleSpecialWorkspace(std::string args) { +SDispatchResult CKeybindManager::toggleSpecialWorkspace(std::string args) { const auto& [workspaceID, workspaceName] = getWorkspaceIDNameFromString("special:" + args); if (workspaceID == WORKSPACE_INVALID || !g_pCompositor->isWorkspaceSpecial(workspaceID)) { Debug::log(ERR, "Invalid workspace passed to special"); - return; + return {.success = false, .error = "Invalid workspace passed to special"}; } bool requestedWorkspaceIsAlreadyOpen = false; const auto PMONITOR = g_pCompositor->m_pLastMonitor; - int specialOpenOnMonitor = PMONITOR->activeSpecialWorkspaceID(); + auto specialOpenOnMonitor = PMONITOR->activeSpecialWorkspaceID(); - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { if (m->activeSpecialWorkspaceID() == workspaceID) { requestedWorkspaceIsAlreadyOpen = true; break; @@ -1839,16 +1932,18 @@ void CKeybindManager::toggleSpecialWorkspace(std::string args) { PMONITOR->setSpecialWorkspace(PSPECIALWORKSPACE); } + + return {}; } -void CKeybindManager::forceRendererReload(std::string args) { +SDispatchResult CKeybindManager::forceRendererReload(std::string args) { bool overAgain = false; - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { if (!m->output) continue; - auto rule = g_pConfigManager->getMonitorRuleFor(*m); + auto rule = g_pConfigManager->getMonitorRuleFor(m); if (!g_pHyprRenderer->applyMonitorRule(m.get(), &rule, true)) { overAgain = true; break; @@ -1857,37 +1952,43 @@ void CKeybindManager::forceRendererReload(std::string args) { if (overAgain) forceRendererReload(args); + + return {}; } -void CKeybindManager::resizeActive(std::string args) { +SDispatchResult CKeybindManager::resizeActive(std::string args) { const auto PLASTWINDOW = g_pCompositor->m_pLastWindow.lock(); if (!PLASTWINDOW || PLASTWINDOW->isFullscreen()) - return; + return {}; const auto SIZ = g_pCompositor->parseWindowVectorArgsRelative(args, PLASTWINDOW->m_vRealSize.goal()); if (SIZ.x < 1 || SIZ.y < 1) - return; + return {}; g_pLayoutManager->getCurrentLayout()->resizeActiveWindow(SIZ - PLASTWINDOW->m_vRealSize.goal()); if (PLASTWINDOW->m_vRealSize.goal().x > 1 && PLASTWINDOW->m_vRealSize.goal().y > 1) PLASTWINDOW->setHidden(false); + + return {}; } -void CKeybindManager::moveActive(std::string args) { +SDispatchResult CKeybindManager::moveActive(std::string args) { const auto PLASTWINDOW = g_pCompositor->m_pLastWindow.lock(); if (!PLASTWINDOW || PLASTWINDOW->isFullscreen()) - return; + return {}; const auto POS = g_pCompositor->parseWindowVectorArgsRelative(args, PLASTWINDOW->m_vRealPosition.goal()); g_pLayoutManager->getCurrentLayout()->moveActiveWindow(POS - PLASTWINDOW->m_vRealPosition.goal()); + + return {}; } -void CKeybindManager::moveWindow(std::string args) { +SDispatchResult CKeybindManager::moveWindow(std::string args) { const auto WINDOWREGEX = args.substr(args.find_first_of(',') + 1); const auto MOVECMD = args.substr(0, args.find_first_of(',')); @@ -1896,18 +1997,20 @@ void CKeybindManager::moveWindow(std::string args) { if (!PWINDOW) { Debug::log(ERR, "moveWindow: no window"); - return; + return {.success = false, .error = "moveWindow: no window"}; } if (PWINDOW->isFullscreen()) - return; + return {}; const auto POS = g_pCompositor->parseWindowVectorArgsRelative(MOVECMD, PWINDOW->m_vRealPosition.goal()); g_pLayoutManager->getCurrentLayout()->moveActiveWindow(POS - PWINDOW->m_vRealPosition.goal(), PWINDOW); + + return {}; } -void CKeybindManager::resizeWindow(std::string args) { +SDispatchResult CKeybindManager::resizeWindow(std::string args) { const auto WINDOWREGEX = args.substr(args.find_first_of(',') + 1); const auto MOVECMD = args.substr(0, args.find_first_of(',')); @@ -1916,24 +2019,26 @@ void CKeybindManager::resizeWindow(std::string args) { if (!PWINDOW) { Debug::log(ERR, "resizeWindow: no window"); - return; + return {.success = false, .error = "resizeWindow: no window"}; } if (PWINDOW->isFullscreen()) - return; + return {}; const auto SIZ = g_pCompositor->parseWindowVectorArgsRelative(MOVECMD, PWINDOW->m_vRealSize.goal()); if (SIZ.x < 1 || SIZ.y < 1) - return; + return {}; g_pLayoutManager->getCurrentLayout()->resizeActiveWindow(SIZ - PWINDOW->m_vRealSize.goal(), CORNER_NONE, PWINDOW); if (PWINDOW->m_vRealSize.goal().x > 1 && PWINDOW->m_vRealSize.goal().y > 1) PWINDOW->setHidden(false); + + return {}; } -void CKeybindManager::circleNext(std::string arg) { +SDispatchResult CKeybindManager::circleNext(std::string arg) { if (g_pCompositor->m_pLastWindow.expired()) { // if we have a clear focus, find the first window and get the next focusable. @@ -1943,7 +2048,7 @@ void CKeybindManager::circleNext(std::string arg) { switchToWindow(PWINDOW); } - return; + return {}; } CVarList args{arg, 0, 's', true}; @@ -1958,20 +2063,22 @@ void CKeybindManager::circleNext(std::string arg) { switchToWindow(g_pCompositor->getPrevWindowOnWorkspace(g_pCompositor->m_pLastWindow.lock(), true, floatStatus)); else switchToWindow(g_pCompositor->getNextWindowOnWorkspace(g_pCompositor->m_pLastWindow.lock(), true, floatStatus)); + + return {}; } -void CKeybindManager::focusWindow(std::string regexp) { +SDispatchResult CKeybindManager::focusWindow(std::string regexp) { const auto PWINDOW = g_pCompositor->getWindowByRegex(regexp); if (!PWINDOW) - return; + return {}; Debug::log(LOG, "Focusing to window name: {}", PWINDOW->m_szTitle); const auto PWORKSPACE = PWINDOW->m_pWorkspace; if (!PWORKSPACE) { Debug::log(ERR, "BUG THIS: null workspace in focusWindow"); - return; + return {.success = false, .error = "BUG THIS: null workspace in focusWindow"}; } updateRelativeCursorCoords(); @@ -2007,9 +2114,11 @@ void CKeybindManager::focusWindow(std::string regexp) { g_pCompositor->focusWindow(PWINDOW); PWINDOW->warpCursor(); + + return {}; } -void CKeybindManager::tagWindow(std::string args) { +SDispatchResult CKeybindManager::tagWindow(std::string args) { PHLWINDOW PWINDOW = nullptr; CVarList vars{args, 0, 's', true}; @@ -2018,49 +2127,52 @@ void CKeybindManager::tagWindow(std::string args) { else if (vars.size() == 2) PWINDOW = g_pCompositor->getWindowByRegex(vars[1]); else - return; + return {}; if (PWINDOW && PWINDOW->m_tags.applyTag(vars[0])) { PWINDOW->updateDynamicRules(); g_pCompositor->updateWindowAnimatedDecorationValues(PWINDOW->m_pSelf.lock()); } + + return {}; } -void CKeybindManager::setSubmap(std::string submap) { +SDispatchResult CKeybindManager::setSubmap(std::string submap) { if (submap == "reset" || submap == "") { m_szCurrentSelectedSubmap = ""; Debug::log(LOG, "Reset active submap to the default one."); g_pEventManager->postEvent(SHyprIPCEvent{"submap", ""}); EMIT_HOOK_EVENT("submap", m_szCurrentSelectedSubmap); - return; + return {}; } - for (auto& k : g_pKeybindManager->m_lKeybinds) { + for (auto const& k : g_pKeybindManager->m_lKeybinds) { if (k.submap == submap) { m_szCurrentSelectedSubmap = submap; Debug::log(LOG, "Changed keybind submap to {}", submap); g_pEventManager->postEvent(SHyprIPCEvent{"submap", submap}); EMIT_HOOK_EVENT("submap", m_szCurrentSelectedSubmap); - return; + return {}; } } Debug::log(ERR, "Cannot set submap {}, submap doesn't exist (wasn't registered!)", submap); + return {.success = false, .error = std::format("Cannot set submap {}, submap doesn't exist (wasn't registered!)", submap)}; } -void CKeybindManager::pass(std::string regexp) { +SDispatchResult CKeybindManager::pass(std::string regexp) { // find the first window passing the regex const auto PWINDOW = g_pCompositor->getWindowByRegex(regexp); if (!PWINDOW) { Debug::log(ERR, "pass: window not found"); - return; + return {.success = false, .error = "pass: window not found"}; } if (!g_pSeatManager->keyboard) { Debug::log(ERR, "No kb in pass?"); - return; + return {.success = false, .error = "No kb in pass?"}; } const auto XWTOXW = PWINDOW->m_bIsX11 && g_pCompositor->m_pLastWindow.lock() && g_pCompositor->m_pLastWindow->m_bIsX11; @@ -2099,7 +2211,7 @@ void CKeybindManager::pass(std::string regexp) { } if (XWTOXW) - return; + return {}; // Massive hack: // this will make g_pSeatManager NOT send the leave event to XWayland apps, provided we are not on an XWayland window already. @@ -2120,14 +2232,16 @@ void CKeybindManager::pass(std::string regexp) { g_pSeatManager->setKeyboardFocus(LASTKBSURF); else g_pSeatManager->setPointerFocus(LASTMOUSESURF, SL); + + return {}; } -void CKeybindManager::sendshortcut(std::string args) { +SDispatchResult CKeybindManager::sendshortcut(std::string args) { // args=[,WINDOW_RULES] const auto ARGS = CVarList(args, 3); if (ARGS.size() != 3) { Debug::log(ERR, "sendshortcut: invalid args"); - return; + return {.success = false, .error = "sendshortcut: invalid args"}; } const auto MOD = g_pKeybindManager->stringToModMask(ARGS[0]); @@ -2145,7 +2259,7 @@ void CKeybindManager::sendshortcut(std::string args) { isMouse = 1; if (keycode < 272) { Debug::log(ERR, "sendshortcut: invalid mouse button"); - return; + return {.success = false, .error = "sendshortcut: invalid mouse button"}; } } else { @@ -2160,7 +2274,7 @@ void CKeybindManager::sendshortcut(std::string args) { if (!KB) { Debug::log(ERR, "sendshortcut: no kb"); - return; + return {.success = false, .error = "sendshortcut: no kb"}; } const auto KEYPAIRSTRING = std::format("{}{}", (uintptr_t)KB.get(), KEY); @@ -2184,7 +2298,7 @@ void CKeybindManager::sendshortcut(std::string args) { if (!keycode) { Debug::log(ERR, "sendshortcut: key not found"); - return; + return {.success = false, .error = "sendshortcut: key not found"}; } } else @@ -2193,7 +2307,7 @@ void CKeybindManager::sendshortcut(std::string args) { if (!keycode) { Debug::log(ERR, "sendshortcut: invalid key"); - return; + return {.success = false, .error = "sendshortcut: invalid key"}; } const std::string regexp = ARGS[2]; @@ -2207,12 +2321,12 @@ void CKeybindManager::sendshortcut(std::string args) { if (!PWINDOW) { Debug::log(ERR, "sendshortcut: window not found"); - return; + return {.success = false, .error = "sendshortcut: window not found"}; } if (!g_pSeatManager->keyboard) { Debug::log(ERR, "No kb in sendshortcut?"); - return; + return {.success = false, .error = "No kb in sendshortcut?"}; } if (!isMouse) @@ -2252,8 +2366,10 @@ void CKeybindManager::sendshortcut(std::string args) { } } + g_pSeatManager->sendKeyboardMods(0, 0, 0, 0); + if (!PWINDOW) - return; + return {}; if (PWINDOW->m_bIsX11) { //xwayland hack, see pass if (!isMouse) { @@ -2271,34 +2387,43 @@ void CKeybindManager::sendshortcut(std::string args) { g_pSeatManager->setKeyboardFocus(LASTSURFACE); else g_pSeatManager->setPointerFocus(LASTSURFACE, SL); + + return {}; } -void CKeybindManager::layoutmsg(std::string msg) { +SDispatchResult CKeybindManager::layoutmsg(std::string msg) { SLayoutMessageHeader hd = {g_pCompositor->m_pLastWindow.lock()}; g_pLayoutManager->getCurrentLayout()->layoutMessage(hd, msg); + + return {}; } -void CKeybindManager::dpms(std::string arg) { - bool enable = arg.starts_with("on"); - std::string port = ""; - - if (arg.starts_with("toggle")) - enable = !std::any_of(g_pCompositor->m_vMonitors.begin(), g_pCompositor->m_vMonitors.end(), [&](const auto& other) { return !other->dpmsStatus; }); // enable if any is off +SDispatchResult CKeybindManager::dpms(std::string arg) { + SDispatchResult res; + bool enable = arg.starts_with("on"); + std::string port = ""; + bool isToggle = arg.starts_with("toggle"); if (arg.find_first_of(' ') != std::string::npos) port = arg.substr(arg.find_first_of(' ') + 1); - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { if (!port.empty() && m->szName != port) continue; + if (isToggle) + enable = !m->dpmsStatus; + + m->output->state->resetExplicitFences(); m->output->state->setEnabled(enable); m->dpmsStatus = enable; if (!m->state.commit()) { Debug::log(ERR, "Couldn't commit output {}", m->szName); + res.success = false; + res.error = "Couldn't commit output {}"; } if (enable) @@ -2310,14 +2435,16 @@ void CKeybindManager::dpms(std::string arg) { g_pCompositor->m_bDPMSStateON = enable; g_pPointerManager->recheckEnteredOutputs(); + + return res; } -void CKeybindManager::swapnext(std::string arg) { +SDispatchResult CKeybindManager::swapnext(std::string arg) { PHLWINDOW toSwap = nullptr; if (g_pCompositor->m_pLastWindow.expired()) - return; + return {}; const auto PLASTWINDOW = g_pCompositor->m_pLastWindow.lock(); @@ -2334,7 +2461,7 @@ void CKeybindManager::swapnext(std::string arg) { // sometimes we may come back to ourselves. if (toSwap == PLASTWINDOW) { if (arg == "last" || arg == "l" || arg == "prev" || arg == "p") - toSwap = g_pCompositor->getPrevWindowOnWorkspace(PLASTWINDOW), true; + toSwap = g_pCompositor->getPrevWindowOnWorkspace(PLASTWINDOW, true); else toSwap = g_pCompositor->getNextWindowOnWorkspace(PLASTWINDOW, true); } @@ -2344,9 +2471,11 @@ void CKeybindManager::swapnext(std::string arg) { PLASTWINDOW->m_pLastCycledWindow = toSwap; g_pCompositor->focusWindow(PLASTWINDOW); + + return {}; } -void CKeybindManager::swapActiveWorkspaces(std::string args) { +SDispatchResult CKeybindManager::swapActiveWorkspaces(std::string args) { const auto MON1 = args.substr(0, args.find_first_of(' ')); const auto MON2 = args.substr(args.find_first_of(' ') + 1); @@ -2354,12 +2483,14 @@ void CKeybindManager::swapActiveWorkspaces(std::string args) { const auto PMON2 = g_pCompositor->getMonitorFromString(MON2); if (!PMON1 || !PMON2 || PMON1 == PMON2) - return; + return {}; g_pCompositor->swapActiveWorkspaces(PMON1, PMON2); + + return {}; } -void CKeybindManager::pinActive(std::string args) { +SDispatchResult CKeybindManager::pinActive(std::string args) { PHLWINDOW PWINDOW = nullptr; @@ -2370,11 +2501,11 @@ void CKeybindManager::pinActive(std::string args) { if (!PWINDOW) { Debug::log(ERR, "pin: window not found"); - return; + return {.success = false, .error = "pin: window not found"}; } if (!PWINDOW->m_bIsFloating || PWINDOW->isFullscreen()) - return; + return {}; PWINDOW->m_bPinned = !PWINDOW->m_bPinned; @@ -2382,7 +2513,7 @@ void CKeybindManager::pinActive(std::string args) { if (!PMONITOR) { Debug::log(ERR, "pin: monitor not found"); - return; + return {.success = false, .error = "pin: window not found"}; } PWINDOW->m_pWorkspace = PMONITOR->activeWorkspace; @@ -2396,40 +2527,41 @@ void CKeybindManager::pinActive(std::string args) { g_pEventManager->postEvent(SHyprIPCEvent{"pin", std::format("{:x},{}", (uintptr_t)PWINDOW.get(), (int)PWINDOW->m_bPinned)}); EMIT_HOOK_EVENT("pin", PWINDOW); + + return {}; } -void CKeybindManager::mouse(std::string args) { +SDispatchResult CKeybindManager::mouse(std::string args) { const auto ARGS = CVarList(args.substr(1), 2, ' '); const auto PRESSED = args[0] == '1'; if (!PRESSED) { - changeMouseBindMode(MBIND_INVALID); - return; + return changeMouseBindMode(MBIND_INVALID); } if (ARGS[0] == "movewindow") { - changeMouseBindMode(MBIND_MOVE); + return changeMouseBindMode(MBIND_MOVE); } else { try { switch (std::stoi(ARGS[1])) { - case 1: changeMouseBindMode(MBIND_RESIZE_FORCE_RATIO); break; - case 2: changeMouseBindMode(MBIND_RESIZE_BLOCK_RATIO); break; - default: changeMouseBindMode(MBIND_RESIZE); + case 1: return changeMouseBindMode(MBIND_RESIZE_FORCE_RATIO); break; + case 2: return changeMouseBindMode(MBIND_RESIZE_BLOCK_RATIO); break; + default: return changeMouseBindMode(MBIND_RESIZE); } - } catch (std::exception& e) { changeMouseBindMode(MBIND_RESIZE); } + } catch (std::exception& e) { return changeMouseBindMode(MBIND_RESIZE); } } } -void CKeybindManager::changeMouseBindMode(const eMouseBindMode MODE) { +SDispatchResult CKeybindManager::changeMouseBindMode(const eMouseBindMode MODE) { if (MODE != MBIND_INVALID) { if (!g_pInputManager->currentlyDraggedWindow.expired() || g_pInputManager->dragMode != MBIND_INVALID) - return; + return {}; const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal(); const PHLWINDOW PWINDOW = g_pCompositor->vectorToWindowUnified(MOUSECOORDS, RESERVED_EXTENTS | INPUT_EXTENTS | ALLOW_FLOATING); if (!PWINDOW) - return; + return SDispatchResult{.passEvent = true}; if (!PWINDOW->isFullscreen() && MODE == MBIND_MOVE) PWINDOW->checkInputOnDecos(INPUT_TYPE_DRAG_START, MOUSECOORDS); @@ -2442,19 +2574,23 @@ void CKeybindManager::changeMouseBindMode(const eMouseBindMode MODE) { g_pLayoutManager->getCurrentLayout()->onBeginDragWindow(); } else { if (g_pInputManager->currentlyDraggedWindow.expired() || g_pInputManager->dragMode == MBIND_INVALID) - return; + return {}; g_pLayoutManager->getCurrentLayout()->onEndDragWindow(); g_pInputManager->dragMode = MODE; } + + return {}; } -void CKeybindManager::bringActiveToTop(std::string args) { +SDispatchResult CKeybindManager::bringActiveToTop(std::string args) { if (g_pCompositor->m_pLastWindow.lock() && g_pCompositor->m_pLastWindow->m_bIsFloating) g_pCompositor->changeWindowZOrder(g_pCompositor->m_pLastWindow.lock(), true); + + return {}; } -void CKeybindManager::alterZOrder(std::string args) { +SDispatchResult CKeybindManager::alterZOrder(std::string args) { const auto WINDOWREGEX = args.substr(args.find_first_of(',') + 1); const auto POSITION = args.substr(0, args.find_first_of(',')); auto PWINDOW = g_pCompositor->getWindowByRegex(WINDOWREGEX); @@ -2464,7 +2600,7 @@ void CKeybindManager::alterZOrder(std::string args) { if (!PWINDOW) { Debug::log(ERR, "alterZOrder: no window"); - return; + return {.success = false, .error = "alterZOrder: no window"}; } if (POSITION == "top") @@ -2473,13 +2609,15 @@ void CKeybindManager::alterZOrder(std::string args) { g_pCompositor->changeWindowZOrder(PWINDOW, 0); else { Debug::log(ERR, "alterZOrder: bad position: {}", POSITION); - return; + return {.success = false, .error = "alterZOrder: bad position: {}"}; } g_pInputManager->simulateMouseMovement(); + + return {}; } -void CKeybindManager::lockGroups(std::string args) { +SDispatchResult CKeybindManager::lockGroups(std::string args) { if (args == "lock" || args.empty() || args == "lockgroups") g_pKeybindManager->m_bGroupsLocked = true; else if (args == "toggle") @@ -2488,13 +2626,15 @@ void CKeybindManager::lockGroups(std::string args) { g_pKeybindManager->m_bGroupsLocked = false; g_pEventManager->postEvent(SHyprIPCEvent{"lockgroups", g_pKeybindManager->m_bGroupsLocked ? "1" : "0"}); + + return {}; } -void CKeybindManager::lockActiveGroup(std::string args) { +SDispatchResult CKeybindManager::lockActiveGroup(std::string args) { const auto PWINDOW = g_pCompositor->m_pLastWindow.lock(); if (!PWINDOW || !PWINDOW->m_sGroupData.pNextWindow.lock()) - return; + return {}; const auto PHEAD = PWINDOW->getGroupHead(); @@ -2506,6 +2646,8 @@ void CKeybindManager::lockActiveGroup(std::string args) { PHEAD->m_sGroupData.locked = false; g_pCompositor->updateWindowAnimatedDecorationValues(PWINDOW); + + return {}; } void CKeybindManager::moveWindowIntoGroup(PHLWINDOW pWindow, PHLWINDOW pWindowInDirection) { @@ -2578,41 +2720,43 @@ void CKeybindManager::moveWindowOutOfGroup(PHLWINDOW pWindow, const std::string& g_pEventManager->postEvent(SHyprIPCEvent{"moveoutofgroup", std::format("{:x}", (uintptr_t)pWindow.get())}); } -void CKeybindManager::moveIntoGroup(std::string args) { +SDispatchResult CKeybindManager::moveIntoGroup(std::string args) { char arg = args[0]; static auto PIGNOREGROUPLOCK = CConfigValue("binds:ignore_group_lock"); if (!*PIGNOREGROUPLOCK && g_pKeybindManager->m_bGroupsLocked) - return; + return {}; if (!isDirection(args)) { Debug::log(ERR, "Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg); - return; + return {.success = false, .error = std::format("Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg)}; } const auto PWINDOW = g_pCompositor->m_pLastWindow.lock(); - if (!PWINDOW || PWINDOW->m_bIsFloating || PWINDOW->m_sGroupData.deny) - return; + if (!PWINDOW || PWINDOW->m_sGroupData.deny) + return {}; auto PWINDOWINDIR = g_pCompositor->getWindowInDirection(PWINDOW, arg); if (!PWINDOWINDIR || !PWINDOWINDIR->m_sGroupData.pNextWindow.lock()) - return; + return {}; // Do not move window into locked group if binds:ignore_group_lock is false if (!*PIGNOREGROUPLOCK && (PWINDOWINDIR->getGroupHead()->m_sGroupData.locked || (PWINDOW->m_sGroupData.pNextWindow.lock() && PWINDOW->getGroupHead()->m_sGroupData.locked))) - return; + return {}; moveWindowIntoGroup(PWINDOW, PWINDOWINDIR); + + return {}; } -void CKeybindManager::moveOutOfGroup(std::string args) { +SDispatchResult CKeybindManager::moveOutOfGroup(std::string args) { static auto PIGNOREGROUPLOCK = CConfigValue("binds:ignore_group_lock"); if (!*PIGNOREGROUPLOCK && g_pKeybindManager->m_bGroupsLocked) - return; + return {}; PHLWINDOW PWINDOW = nullptr; @@ -2622,28 +2766,30 @@ void CKeybindManager::moveOutOfGroup(std::string args) { PWINDOW = g_pCompositor->m_pLastWindow.lock(); if (!PWINDOW || !PWINDOW->m_sGroupData.pNextWindow.lock()) - return; + return {}; moveWindowOutOfGroup(PWINDOW); + + return {}; } -void CKeybindManager::moveWindowOrGroup(std::string args) { +SDispatchResult CKeybindManager::moveWindowOrGroup(std::string args) { char arg = args[0]; static auto PIGNOREGROUPLOCK = CConfigValue("binds:ignore_group_lock"); if (!isDirection(args)) { Debug::log(ERR, "Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg); - return; + return {.success = false, .error = std::format("Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg)}; } const auto PWINDOW = g_pCompositor->m_pLastWindow.lock(); if (!PWINDOW || PWINDOW->isFullscreen()) - return; + return {}; if (!*PIGNOREGROUPLOCK && g_pKeybindManager->m_bGroupsLocked) { g_pLayoutManager->getCurrentLayout()->moveWindowTo(PWINDOW, args); - return; + return {}; } const auto PWINDOWINDIR = g_pCompositor->getWindowInDirection(PWINDOW, arg); @@ -2675,9 +2821,11 @@ void CKeybindManager::moveWindowOrGroup(std::string args) { } g_pCompositor->updateWindowAnimatedDecorationValues(PWINDOW); + + return {}; } -void CKeybindManager::setIgnoreGroupLock(std::string args) { +SDispatchResult CKeybindManager::setIgnoreGroupLock(std::string args) { static auto PIGNOREGROUPLOCK = (Hyprlang::INT* const*)g_pConfigManager->getConfigValuePtr("binds:ignore_group_lock"); if (args == "toggle") @@ -2686,12 +2834,14 @@ void CKeybindManager::setIgnoreGroupLock(std::string args) { **PIGNOREGROUPLOCK = args == "on"; g_pEventManager->postEvent(SHyprIPCEvent{"ignoregrouplock", std::to_string(**PIGNOREGROUPLOCK)}); + + return {}; } -void CKeybindManager::denyWindowFromGroup(std::string args) { +SDispatchResult CKeybindManager::denyWindowFromGroup(std::string args) { const auto PWINDOW = g_pCompositor->m_pLastWindow.lock(); if (!PWINDOW || (PWINDOW && PWINDOW->m_sGroupData.pNextWindow.lock())) - return; + return {}; if (args == "toggle") PWINDOW->m_sGroupData.deny = !PWINDOW->m_sGroupData.deny; @@ -2699,28 +2849,32 @@ void CKeybindManager::denyWindowFromGroup(std::string args) { PWINDOW->m_sGroupData.deny = args == "on"; g_pCompositor->updateWindowAnimatedDecorationValues(PWINDOW); + + return {}; } -void CKeybindManager::global(std::string args) { +SDispatchResult CKeybindManager::global(std::string args) { const auto APPID = args.substr(0, args.find_first_of(':')); const auto NAME = args.substr(args.find_first_of(':') + 1); if (NAME.empty()) - return; + return {}; if (!PROTO::globalShortcuts->isTaken(APPID, NAME)) - return; + return {}; PROTO::globalShortcuts->sendGlobalShortcutEvent(APPID, NAME, g_pKeybindManager->m_iPassPressed); + + return {}; } -void CKeybindManager::moveGroupWindow(std::string args) { +SDispatchResult CKeybindManager::moveGroupWindow(std::string args) { const auto BACK = args == "b" || args == "prev"; const auto PLASTWINDOW = g_pCompositor->m_pLastWindow.lock(); if (!PLASTWINDOW || !PLASTWINDOW->m_sGroupData.pNextWindow.lock()) - return; + return {}; if ((!BACK && PLASTWINDOW->m_sGroupData.pNextWindow->m_sGroupData.head) || (BACK && PLASTWINDOW->m_sGroupData.head)) { std::swap(PLASTWINDOW->m_sGroupData.head, PLASTWINDOW->m_sGroupData.pNextWindow->m_sGroupData.head); @@ -2729,8 +2883,11 @@ void CKeybindManager::moveGroupWindow(std::string args) { PLASTWINDOW->switchWithWindowInGroup(BACK ? PLASTWINDOW->getGroupPrevious() : PLASTWINDOW->m_sGroupData.pNextWindow.lock()); PLASTWINDOW->updateWindowDecos(); + + return {}; } -void CKeybindManager::event(std::string args) { +SDispatchResult CKeybindManager::event(std::string args) { g_pEventManager->postEvent(SHyprIPCEvent{"custom", args}); + return {}; } diff --git a/src/managers/KeybindManager.hpp b/src/managers/KeybindManager.hpp index 26b42b00..c81ca86f 100644 --- a/src/managers/KeybindManager.hpp +++ b/src/managers/KeybindManager.hpp @@ -71,33 +71,40 @@ enum eMultiKeyCase { MK_FULL_MATCH }; +struct SDispatchResult { + bool passEvent = false; + bool success = true; + std::string error; +}; + class CKeybindManager { public: CKeybindManager(); ~CKeybindManager(); - bool onKeyEvent(std::any, SP); - bool onAxisEvent(const IPointer::SAxisEvent&); - bool onMouseEvent(const IPointer::SButtonEvent&); - void resizeWithBorder(const IPointer::SButtonEvent&); - void onSwitchEvent(const std::string&); - void onSwitchOnEvent(const std::string&); - void onSwitchOffEvent(const std::string&); + bool onKeyEvent(std::any, SP); + bool onAxisEvent(const IPointer::SAxisEvent&); + bool onMouseEvent(const IPointer::SButtonEvent&); + void resizeWithBorder(const IPointer::SButtonEvent&); + void onSwitchEvent(const std::string&); + void onSwitchOnEvent(const std::string&); + void onSwitchOffEvent(const std::string&); - void addKeybind(SKeybind); - void removeKeybind(uint32_t, const SParsedKey&); - uint32_t stringToModMask(std::string); - uint32_t keycodeToModifier(xkb_keycode_t); - void clearKeybinds(); - void shadowKeybinds(const xkb_keysym_t& doesntHave = 0, const uint32_t doesntHaveCode = 0); + void addKeybind(SKeybind); + void removeKeybind(uint32_t, const SParsedKey&); + uint32_t stringToModMask(std::string); + uint32_t keycodeToModifier(xkb_keycode_t); + void clearKeybinds(); + void shadowKeybinds(const xkb_keysym_t& doesntHave = 0, const uint32_t doesntHaveCode = 0); + std::string getCurrentSubmap(); - std::unordered_map> m_mDispatchers; + std::unordered_map> m_mDispatchers; - wl_event_source* m_pActiveKeybindEventSource = nullptr; + wl_event_source* m_pActiveKeybindEventSource = nullptr; - bool m_bGroupsLocked = false; + bool m_bGroupsLocked = false; - std::list m_lKeybinds; + std::list m_lKeybinds; //since we cant find keycode through keyname in xkb: //on sendshortcut call, we once search for keyname (e.g. "g") the correct keycode (e.g. 42) @@ -105,7 +112,7 @@ class CKeybindManager { //we also store the keyboard pointer (in the string) to differentiate between different keyboard (layouts) std::unordered_map m_mKeyToCodeCache; - static void changeMouseBindMode(const eMouseBindMode mode); + static SDispatchResult changeMouseBindMode(const eMouseBindMode mode); private: std::deque m_dPressedKeys; @@ -124,7 +131,7 @@ class CKeybindManager { CTimer m_tScrollTimer; - bool handleKeybinds(const uint32_t, const SPressedKeyWithMods&, bool); + SDispatchResult handleKeybinds(const uint32_t, const SPressedKeyWithMods&, bool); std::set m_sMkKeys = {}; std::set m_sMkMods = {}; @@ -143,76 +150,79 @@ class CKeybindManager { static void moveWindowOutOfGroup(PHLWINDOW pWindow, const std::string& dir = ""); static void moveWindowIntoGroup(PHLWINDOW pWindow, PHLWINDOW pWindowInDirection); static void switchToWindow(PHLWINDOW PWINDOWTOCHANGETO); + static uint64_t spawnRawProc(std::string, PHLWORKSPACE pInitialWorkspace); + static uint64_t spawnWithRules(std::string, PHLWORKSPACE pInitialWorkspace); // -------------- Dispatchers -------------- // - static void killActive(std::string); - static void kill(std::string); - static void spawn(std::string); - static uint64_t spawnRaw(std::string); - static void toggleActiveFloating(std::string); - static void toggleActivePseudo(std::string); - static void setActiveFloating(std::string); - static void setActiveTiled(std::string); - static void changeworkspace(std::string); - static void fullscreenActive(std::string); - static void fullscreenStateActive(std::string args); - static void moveActiveToWorkspace(std::string); - static void moveActiveToWorkspaceSilent(std::string); - static void moveFocusTo(std::string); - static void focusUrgentOrLast(std::string); - static void focusCurrentOrLast(std::string); - static void centerWindow(std::string); - static void moveActiveTo(std::string); - static void swapActive(std::string); - static void toggleGroup(std::string); - static void changeGroupActive(std::string); - static void alterSplitRatio(std::string); - static void focusMonitor(std::string); - static void toggleSplit(std::string); - static void swapSplit(std::string); - static void moveCursorToCorner(std::string); - static void moveCursor(std::string); - static void workspaceOpt(std::string); - static void renameWorkspace(std::string); - static void exitHyprland(std::string); - static void moveCurrentWorkspaceToMonitor(std::string); - static void moveWorkspaceToMonitor(std::string); - static void focusWorkspaceOnCurrentMonitor(std::string); - static void toggleSpecialWorkspace(std::string); - static void forceRendererReload(std::string); - static void resizeActive(std::string); - static void moveActive(std::string); - static void moveWindow(std::string); - static void resizeWindow(std::string); - static void circleNext(std::string); - static void focusWindow(std::string); - static void tagWindow(std::string); - static void setSubmap(std::string); - static void pass(std::string); - static void sendshortcut(std::string); - static void layoutmsg(std::string); - static void dpms(std::string); - static void swapnext(std::string); - static void swapActiveWorkspaces(std::string); - static void pinActive(std::string); - static void mouse(std::string); - static void bringActiveToTop(std::string); - static void alterZOrder(std::string); - static void lockGroups(std::string); - static void lockActiveGroup(std::string); - static void moveIntoGroup(std::string); - static void moveOutOfGroup(std::string); - static void moveGroupWindow(std::string); - static void moveWindowOrGroup(std::string); - static void setIgnoreGroupLock(std::string); - static void denyWindowFromGroup(std::string); - static void global(std::string); - static void event(std::string); + static SDispatchResult killActive(std::string); + static SDispatchResult kill(std::string); + static SDispatchResult spawn(std::string); + static SDispatchResult spawnRaw(std::string); + static SDispatchResult toggleActiveFloating(std::string); + static SDispatchResult toggleActivePseudo(std::string); + static SDispatchResult setActiveFloating(std::string); + static SDispatchResult setActiveTiled(std::string); + static SDispatchResult changeworkspace(std::string); + static SDispatchResult fullscreenActive(std::string); + static SDispatchResult fullscreenStateActive(std::string args); + static SDispatchResult moveActiveToWorkspace(std::string); + static SDispatchResult moveActiveToWorkspaceSilent(std::string); + static SDispatchResult moveFocusTo(std::string); + static SDispatchResult focusUrgentOrLast(std::string); + static SDispatchResult focusCurrentOrLast(std::string); + static SDispatchResult centerWindow(std::string); + static SDispatchResult moveActiveTo(std::string); + static SDispatchResult swapActive(std::string); + static SDispatchResult toggleGroup(std::string); + static SDispatchResult changeGroupActive(std::string); + static SDispatchResult alterSplitRatio(std::string); + static SDispatchResult focusMonitor(std::string); + static SDispatchResult toggleSplit(std::string); + static SDispatchResult swapSplit(std::string); + static SDispatchResult moveCursorToCorner(std::string); + static SDispatchResult moveCursor(std::string); + static SDispatchResult workspaceOpt(std::string); + static SDispatchResult renameWorkspace(std::string); + static SDispatchResult exitHyprland(std::string); + static SDispatchResult moveCurrentWorkspaceToMonitor(std::string); + static SDispatchResult moveWorkspaceToMonitor(std::string); + static SDispatchResult focusWorkspaceOnCurrentMonitor(std::string); + static SDispatchResult toggleSpecialWorkspace(std::string); + static SDispatchResult forceRendererReload(std::string); + static SDispatchResult resizeActive(std::string); + static SDispatchResult moveActive(std::string); + static SDispatchResult moveWindow(std::string); + static SDispatchResult resizeWindow(std::string); + static SDispatchResult circleNext(std::string); + static SDispatchResult focusWindow(std::string); + static SDispatchResult tagWindow(std::string); + static SDispatchResult setSubmap(std::string); + static SDispatchResult pass(std::string); + static SDispatchResult sendshortcut(std::string); + static SDispatchResult layoutmsg(std::string); + static SDispatchResult dpms(std::string); + static SDispatchResult swapnext(std::string); + static SDispatchResult swapActiveWorkspaces(std::string); + static SDispatchResult pinActive(std::string); + static SDispatchResult mouse(std::string); + static SDispatchResult bringActiveToTop(std::string); + static SDispatchResult alterZOrder(std::string); + static SDispatchResult lockGroups(std::string); + static SDispatchResult lockActiveGroup(std::string); + static SDispatchResult moveIntoGroup(std::string); + static SDispatchResult moveOutOfGroup(std::string); + static SDispatchResult moveGroupWindow(std::string); + static SDispatchResult moveWindowOrGroup(std::string); + static SDispatchResult setIgnoreGroupLock(std::string); + static SDispatchResult denyWindowFromGroup(std::string); + static SDispatchResult global(std::string); + static SDispatchResult event(std::string); friend class CCompositor; friend class CInputManager; friend class CConfigManager; friend class CWorkspace; + friend class CPointerManager; }; inline std::unique_ptr g_pKeybindManager; diff --git a/src/managers/PointerManager.cpp b/src/managers/PointerManager.cpp index 3ba34c11..8c2a1bad 100644 --- a/src/managers/PointerManager.cpp +++ b/src/managers/PointerManager.cpp @@ -2,7 +2,9 @@ #include "../Compositor.hpp" #include "../config/ConfigValue.hpp" #include "../protocols/PointerGestures.hpp" +#include "../protocols/RelativePointer.hpp" #include "../protocols/FractionalScale.hpp" +#include "../protocols/IdleNotify.hpp" #include "../protocols/core/Compositor.hpp" #include "../protocols/core/Seat.hpp" #include "eventLoop/EventLoopManager.hpp" @@ -11,8 +13,8 @@ #include CPointerManager::CPointerManager() { - hooks.monitorAdded = g_pHookSystem->hookDynamic("newMonitor", [this](void* self, SCallbackInfo& info, std::any data) { - auto PMONITOR = std::any_cast>(data); + hooks.monitorAdded = g_pHookSystem->hookDynamic("monitorAdded", [this](void* self, SCallbackInfo& info, std::any data) { + auto PMONITOR = std::any_cast(data)->self.lock(); onMonitorLayoutChange(); @@ -36,21 +38,21 @@ CPointerManager::CPointerManager() { } void CPointerManager::lockSoftwareAll() { - for (auto& state : monitorStates) + for (auto const& state : monitorStates) state->softwareLocks++; updateCursorBackend(); } void CPointerManager::unlockSoftwareAll() { - for (auto& state : monitorStates) + for (auto const& state : monitorStates) state->softwareLocks--; updateCursorBackend(); } void CPointerManager::lockSoftwareForMonitor(CMonitor* Monitor) { - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { if (m->ID == Monitor->ID) { lockSoftwareForMonitor(m); return; @@ -67,7 +69,7 @@ void CPointerManager::lockSoftwareForMonitor(SP mon) { } void CPointerManager::unlockSoftwareForMonitor(CMonitor* Monitor) { - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { if (m->ID == Monitor->ID) { unlockSoftwareForMonitor(m); return; @@ -185,7 +187,7 @@ void CPointerManager::recheckEnteredOutputs() { auto box = getCursorBoxGlobal(); - for (auto& s : monitorStates) { + for (auto const& s : monitorStates) { if (s->monitor.expired() || s->monitor->isMirror() || !s->monitor->m_bEnabled) continue; @@ -205,9 +207,8 @@ void CPointerManager::recheckEnteredOutputs() { // if we are using hw cursors, prevent // the cursor from being stuck at the last point. - // if we are leaving it, move it to narnia. if (!s->hardwareFailed && (s->monitor->output->getBackend()->capabilities() & Aquamarine::IBackendImplementation::eBackendCapabilities::AQ_BACKEND_CAPABILITY_POINTER)) - s->monitor->output->moveCursor({-1337, -420}); + setHWCursorBuffer(s, nullptr); if (!currentCursorImage.surface) continue; @@ -221,7 +222,7 @@ void CPointerManager::resetCursorImage(bool apply) { damageIfSoftware(); if (currentCursorImage.surface) { - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { currentCursorImage.surface->resource()->leave(m); } @@ -239,7 +240,7 @@ void CPointerManager::resetCursorImage(bool apply) { currentCursorImage.scale = 1.F; currentCursorImage.hotspot = {0, 0}; - for (auto& s : monitorStates) { + for (auto const& s : monitorStates) { if (s->monitor.expired() || s->monitor->isMirror() || !s->monitor->m_bEnabled) continue; @@ -249,7 +250,7 @@ void CPointerManager::resetCursorImage(bool apply) { if (!apply) return; - for (auto& ms : monitorStates) { + for (auto const& ms : monitorStates) { if (!ms->monitor || !ms->monitor->m_bEnabled || !ms->monitor->dpmsStatus) { Debug::log(TRACE, "Not updating hw cursors: disabled / dpms off display"); continue; @@ -266,7 +267,9 @@ void CPointerManager::resetCursorImage(bool apply) { void CPointerManager::updateCursorBackend() { static auto PNOHW = CConfigValue("cursor:no_hardware_cursors"); - for (auto& m : g_pCompositor->m_vMonitors) { + const auto CURSORBOX = getCursorBoxGlobal(); + + for (auto const& m : g_pCompositor->m_vMonitors) { auto state = stateFor(m); if (!m->m_bEnabled || !m->dpmsStatus) { @@ -274,6 +277,15 @@ void CPointerManager::updateCursorBackend() { continue; } + auto CROSSES = !m->logicalBox().intersection(CURSORBOX).empty(); + + if (!CROSSES) { + if (state->cursorFrontBuffer) + setHWCursorBuffer(state, nullptr); + + continue; + } + if (state->softwareLocks > 0 || *PNOHW || !attemptHardwareCursor(state)) { Debug::log(TRACE, "Output {} rejected hardware cursors, falling back to sw", m->szName); state->box = getCursorBoxLogicalForMonitor(state->monitor.lock()); @@ -294,17 +306,34 @@ void CPointerManager::onCursorMoved() { if (!hasCursor()) return; - for (auto& m : g_pCompositor->m_vMonitors) { + const auto CURSORBOX = getCursorBoxGlobal(); + bool recalc = false; + + for (auto const& m : g_pCompositor->m_vMonitors) { auto state = stateFor(m); state->box = getCursorBoxLogicalForMonitor(state->monitor.lock()); + auto CROSSES = !m->logicalBox().intersection(CURSORBOX).empty(); + + if (!CROSSES && state->cursorFrontBuffer) { + Debug::log(TRACE, "onCursorMoved for output {}: cursor left the viewport, removing it from the backend", m->szName); + setHWCursorBuffer(state, nullptr); + continue; + } else if (CROSSES && !state->cursorFrontBuffer) { + Debug::log(TRACE, "onCursorMoved for output {}: cursor entered the output, but no front buffer, forcing recalc", m->szName); + recalc = true; + } + if (state->hardwareFailed || !state->entered) continue; const auto CURSORPOS = getCursorPosForMonitor(m); - m->output->moveCursor(CURSORPOS); + m->output->moveCursor(CURSORPOS, m->shouldSkipScheduleFrameOnMouseEvent()); } + + if (recalc) + updateCursorBackend(); } bool CPointerManager::attemptHardwareCursor(SP state) { @@ -314,7 +343,7 @@ bool CPointerManager::attemptHardwareCursor(SPmonitor.lock()); - state->monitor->output->moveCursor(CURSORPOS); + state->monitor->output->moveCursor(CURSORPOS, state->monitor->shouldSkipScheduleFrameOnMouseEvent()); auto texture = getCurrentCursorTexture(); @@ -357,7 +386,8 @@ bool CPointerManager::setHWCursorBuffer(SP state, SPcursorFrontBuffer = buf; - g_pCompositor->scheduleFrameForMonitor(state->monitor.get(), Aquamarine::IOutput::AQ_SCHEDULE_CURSOR_SHAPE); + if (!state->monitor->shouldSkipScheduleFrameOnMouseEvent()) + g_pCompositor->scheduleFrameForMonitor(state->monitor.get(), Aquamarine::IOutput::AQ_SCHEDULE_CURSOR_SHAPE); return true; } @@ -560,7 +590,7 @@ Vector2D CPointerManager::closestValid(const Vector2D& pos) { // static auto INSIDE_LAYOUT = [this](const CBox& box) -> bool { - for (auto& b : currentMonitorLayout.monitorBoxes) { + for (auto const& b : currentMonitorLayout.monitorBoxes) { if (box.inside(b)) return true; } @@ -568,7 +598,7 @@ Vector2D CPointerManager::closestValid(const Vector2D& pos) { }; static auto INSIDE_LAYOUT_COORD = [this](const Vector2D& vec) -> bool { - for (auto& b : currentMonitorLayout.monitorBoxes) { + for (auto const& b : currentMonitorLayout.monitorBoxes) { if (b.containsPoint(vec)) return true; } @@ -579,7 +609,7 @@ Vector2D CPointerManager::closestValid(const Vector2D& pos) { Vector2D leader; float distanceSq = __FLT_MAX__; - for (auto& b : currentMonitorLayout.monitorBoxes) { + for (auto const& b : currentMonitorLayout.monitorBoxes) { auto p = b.closestPoint(vec); auto distSq = p.distanceSq(vec); @@ -629,12 +659,12 @@ Vector2D CPointerManager::closestValid(const Vector2D& pos) { } void CPointerManager::damageIfSoftware() { - auto b = getCursorBoxGlobal(); + auto b = getCursorBoxGlobal().expand(4); static auto PNOHW = CConfigValue("cursor:no_hardware_cursors"); - for (auto& mw : monitorStates) { - if (mw->monitor.expired()) + for (auto const& mw : monitorStates) { + if (mw->monitor.expired() || !mw->monitor->output) continue; if ((mw->softwareLocks > 0 || mw->hardwareFailed || *PNOHW) && b.overlaps({mw->monitor->vecPosition, mw->monitor->vecSize})) { @@ -648,8 +678,11 @@ void CPointerManager::warpTo(const Vector2D& logical) { damageIfSoftware(); pointerPos = closestValid(logical); - recheckEnteredOutputs(); - onCursorMoved(); + + if (!g_pInputManager->isLocked()) { + recheckEnteredOutputs(); + onCursorMoved(); + } damageIfSoftware(); } @@ -708,7 +741,7 @@ void CPointerManager::warpAbsolute(Vector2D abs, SP dev) { if (POINTER->boundOutput == "entire") { // find x and y size of the entire space Vector2D bottomRight = {-9999999, -9999999}, topLeft = {9999999, 9999999}; - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { const auto EXTENT = m->logicalBox().extent(); const auto POS = m->logicalBox().pos(); if (EXTENT.x > bottomRight.x) @@ -747,8 +780,8 @@ void CPointerManager::warpAbsolute(Vector2D abs, SP dev) { void CPointerManager::onMonitorLayoutChange() { currentMonitorLayout.monitorBoxes.clear(); - for (auto& m : g_pCompositor->m_vMonitors) { - if (m->isMirror() || !m->m_bEnabled) + for (auto const& m : g_pCompositor->m_vMonitors) { + if (m->isMirror() || !m->m_bEnabled || !m->output) continue; currentMonitorLayout.monitorBoxes.emplace_back(CBox{m->vecPosition, m->vecSize}); @@ -780,6 +813,9 @@ void CPointerManager::attachPointer(SP pointer) { if (!pointer) return; + static auto PMOUSEDPMS = CConfigValue("misc:mouse_move_enables_dpms"); + + // auto listener = pointerListeners.emplace_back(makeShared()); listener->pointer = pointer; @@ -793,76 +829,119 @@ void CPointerManager::attachPointer(SP pointer) { auto E = std::any_cast(e); g_pInputManager->onMouseMoved(E); + + PROTO::idle->onActivity(); + + if (!g_pCompositor->m_bDPMSStateON && *PMOUSEDPMS) + g_pKeybindManager->dpms("on"); }); listener->motionAbsolute = pointer->pointerEvents.motionAbsolute.registerListener([this] (std::any e) { auto E = std::any_cast(e); g_pInputManager->onMouseWarp(E); + + PROTO::idle->onActivity(); + + if (!g_pCompositor->m_bDPMSStateON && *PMOUSEDPMS) + g_pKeybindManager->dpms("on"); }); listener->button = pointer->pointerEvents.button.registerListener([this] (std::any e) { auto E = std::any_cast(e); g_pInputManager->onMouseButton(E); + + PROTO::idle->onActivity(); }); listener->axis = pointer->pointerEvents.axis.registerListener([this] (std::any e) { auto E = std::any_cast(e); g_pInputManager->onMouseWheel(E); + + PROTO::idle->onActivity(); }); listener->frame = pointer->pointerEvents.frame.registerListener([this] (std::any e) { - g_pSeatManager->sendPointerFrame(); + bool shouldSkip = false; + if (!g_pSeatManager->mouse.expired() && g_pInputManager->isLocked()) { + auto PMONITOR = g_pCompositor->m_pLastMonitor.get(); + shouldSkip = PMONITOR && PMONITOR->shouldSkipScheduleFrameOnMouseEvent(); + } + g_pSeatManager->isPointerFrameSkipped = shouldSkip; + if (!g_pSeatManager->isPointerFrameSkipped) + g_pSeatManager->sendPointerFrame(); }); listener->swipeBegin = pointer->pointerEvents.swipeBegin.registerListener([this] (std::any e) { auto E = std::any_cast(e); g_pInputManager->onSwipeBegin(E); + + PROTO::idle->onActivity(); + + if (!g_pCompositor->m_bDPMSStateON && *PMOUSEDPMS) + g_pKeybindManager->dpms("on"); }); listener->swipeEnd = pointer->pointerEvents.swipeEnd.registerListener([this] (std::any e) { auto E = std::any_cast(e); g_pInputManager->onSwipeEnd(E); + + PROTO::idle->onActivity(); }); listener->swipeUpdate = pointer->pointerEvents.swipeUpdate.registerListener([this] (std::any e) { auto E = std::any_cast(e); g_pInputManager->onSwipeUpdate(E); + + PROTO::idle->onActivity(); }); listener->pinchBegin = pointer->pointerEvents.pinchBegin.registerListener([this] (std::any e) { auto E = std::any_cast(e); PROTO::pointerGestures->pinchBegin(E.timeMs, E.fingers); + + PROTO::idle->onActivity(); + + if (!g_pCompositor->m_bDPMSStateON && *PMOUSEDPMS) + g_pKeybindManager->dpms("on"); }); listener->pinchEnd = pointer->pointerEvents.pinchEnd.registerListener([this] (std::any e) { auto E = std::any_cast(e); PROTO::pointerGestures->pinchEnd(E.timeMs, E.cancelled); + + PROTO::idle->onActivity(); }); listener->pinchUpdate = pointer->pointerEvents.pinchUpdate.registerListener([this] (std::any e) { auto E = std::any_cast(e); PROTO::pointerGestures->pinchUpdate(E.timeMs, E.delta, E.scale, E.rotation); + + PROTO::idle->onActivity(); }); listener->holdBegin = pointer->pointerEvents.holdBegin.registerListener([this] (std::any e) { auto E = std::any_cast(e); PROTO::pointerGestures->holdBegin(E.timeMs, E.fingers); + + PROTO::idle->onActivity(); }); listener->holdEnd = pointer->pointerEvents.holdEnd.registerListener([this] (std::any e) { auto E = std::any_cast(e); PROTO::pointerGestures->holdEnd(E.timeMs, E.cancelled); + + PROTO::idle->onActivity(); }); // clang-format on @@ -873,6 +952,9 @@ void CPointerManager::attachTouch(SP touch) { if (!touch) return; + static auto PMOUSEDPMS = CConfigValue("misc:mouse_move_enables_dpms"); + + // auto listener = touchListeners.emplace_back(makeShared()); listener->touch = touch; @@ -886,18 +968,27 @@ void CPointerManager::attachTouch(SP touch) { auto E = std::any_cast(e); g_pInputManager->onTouchDown(E); + + PROTO::idle->onActivity(); + + if (!g_pCompositor->m_bDPMSStateON && *PMOUSEDPMS) + g_pKeybindManager->dpms("on"); }); listener->up = touch->touchEvents.up.registerListener([this] (std::any e) { auto E = std::any_cast(e); g_pInputManager->onTouchUp(E); + + PROTO::idle->onActivity(); }); listener->motion = touch->touchEvents.motion.registerListener([this] (std::any e) { auto E = std::any_cast(e); g_pInputManager->onTouchMove(E); + + PROTO::idle->onActivity(); }); listener->cancel = touch->touchEvents.cancel.registerListener([this] (std::any e) { @@ -916,6 +1007,9 @@ void CPointerManager::attachTablet(SP tablet) { if (!tablet) return; + static auto PMOUSEDPMS = CConfigValue("misc:mouse_move_enables_dpms"); + + // auto listener = tabletListeners.emplace_back(makeShared()); listener->tablet = tablet; @@ -929,24 +1023,38 @@ void CPointerManager::attachTablet(SP tablet) { auto E = std::any_cast(e); g_pInputManager->onTabletAxis(E); + + PROTO::idle->onActivity(); + + if (!g_pCompositor->m_bDPMSStateON && *PMOUSEDPMS) + g_pKeybindManager->dpms("on"); }); listener->proximity = tablet->tabletEvents.proximity.registerListener([this] (std::any e) { auto E = std::any_cast(e); g_pInputManager->onTabletProximity(E); + + PROTO::idle->onActivity(); }); listener->tip = tablet->tabletEvents.tip.registerListener([this] (std::any e) { auto E = std::any_cast(e); g_pInputManager->onTabletTip(E); + + PROTO::idle->onActivity(); + + if (!g_pCompositor->m_bDPMSStateON && *PMOUSEDPMS) + g_pKeybindManager->dpms("on"); }); listener->button = tablet->tabletEvents.button.registerListener([this] (std::any e) { auto E = std::any_cast(e); g_pInputManager->onTabletButton(E); + + PROTO::idle->onActivity(); }); // clang-format on @@ -966,7 +1074,7 @@ void CPointerManager::detachTablet(SP tablet) { } void CPointerManager::damageCursor(SP pMonitor) { - for (auto& mw : monitorStates) { + for (auto const& mw : monitorStates) { if (mw->monitor != pMonitor) continue; @@ -984,3 +1092,22 @@ void CPointerManager::damageCursor(SP pMonitor) { Vector2D CPointerManager::cursorSizeLogical() { return currentCursorImage.size / currentCursorImage.scale; } + +void CPointerManager::storeMovement(uint64_t time, const Vector2D& delta, const Vector2D& deltaUnaccel) { + storedTime = time; + storedDelta += delta; + storedUnaccel += deltaUnaccel; +} + +void CPointerManager::setStoredMovement(uint64_t time, const Vector2D& delta, const Vector2D& deltaUnaccel) { + storedTime = time; + storedDelta = delta; + storedUnaccel = deltaUnaccel; +} + +void CPointerManager::sendStoredMovement() { + PROTO::relativePointer->sendRelativeMotion((uint64_t)storedTime * 1000, storedDelta, storedUnaccel); + storedTime = 0; + storedDelta = Vector2D{}; + storedUnaccel = Vector2D{}; +} diff --git a/src/managers/PointerManager.hpp b/src/managers/PointerManager.hpp index 4a4c4f61..6b89eb16 100644 --- a/src/managers/PointerManager.hpp +++ b/src/managers/PointerManager.hpp @@ -61,6 +61,9 @@ class CPointerManager { // Vector2D position(); Vector2D cursorSizeLogical(); + void storeMovement(uint64_t time, const Vector2D& delta, const Vector2D& deltaUnaccel); + void setStoredMovement(uint64_t time, const Vector2D& delta, const Vector2D& deltaUnaccel); + void sendStoredMovement(); void recheckEnteredOutputs(); @@ -154,6 +157,10 @@ class CPointerManager { Vector2D pointerPos = {0, 0}; + uint64_t storedTime = 0; + Vector2D storedDelta = {0, 0}; + Vector2D storedUnaccel = {0, 0}; + struct SMonitorPointerState { SMonitorPointerState(SP m) : monitor(m) {} ~SMonitorPointerState() {} diff --git a/src/managers/ProtocolManager.cpp b/src/managers/ProtocolManager.cpp index 635e6223..25a34657 100644 --- a/src/managers/ProtocolManager.cpp +++ b/src/managers/ProtocolManager.cpp @@ -43,6 +43,10 @@ #include "../protocols/ToplevelExport.hpp" #include "../protocols/TextInputV1.hpp" #include "../protocols/GlobalShortcuts.hpp" +#include "../protocols/XDGDialog.hpp" +#include "../protocols/SinglePixel.hpp" +#include "../protocols/SecurityContext.hpp" +#include "../protocols/CTMControl.hpp" #include "../protocols/core/Seat.hpp" #include "../protocols/core/DataDevice.hpp" @@ -76,7 +80,7 @@ void CProtocolManager::onMonitorModeChange(CMonitor* pMonitor) { CProtocolManager::CProtocolManager() { - static const auto PENABLEEXPLICIT = CConfigValue("experimental:explicit_sync"); + static const auto PENABLEEXPLICIT = CConfigValue("render:explicit_sync"); // Outputs are a bit dumb, we have to agree. static auto P = g_pHookSystem->hookDynamic("monitorAdded", [this](void* self, SCallbackInfo& info, std::any param) { @@ -84,7 +88,8 @@ CProtocolManager::CProtocolManager() { // ignore mirrored outputs. I don't think this will ever be hit as mirrors are applied after // this event is emitted iirc. - if (M->isMirror()) + // also ignore the fallback + if (M->isMirror() || M == g_pCompositor->m_pUnsafeOutput) return; if (PROTO::outputs.contains(M->szName)) @@ -150,8 +155,12 @@ CProtocolManager::CProtocolManager() { PROTO::screencopy = std::make_unique(&zwlr_screencopy_manager_v1_interface, 3, "Screencopy"); PROTO::toplevelExport = std::make_unique(&hyprland_toplevel_export_manager_v1_interface, 2, "ToplevelExport"); PROTO::globalShortcuts = std::make_unique(&hyprland_global_shortcuts_manager_v1_interface, 1, "GlobalShortcuts"); + PROTO::xdgDialog = std::make_unique(&xdg_dialog_v1_interface, 1, "XDGDialog"); + PROTO::singlePixel = std::make_unique(&wp_single_pixel_buffer_manager_v1_interface, 1, "SinglePixel"); + PROTO::securityContext = std::make_unique(&wp_security_context_manager_v1_interface, 1, "SecurityContext"); + PROTO::ctm = std::make_unique(&hyprland_ctm_control_manager_v1_interface, 1, "CTMControl"); - for (auto& b : g_pCompositor->m_pAqBackend->getImplementations()) { + for (auto const& b : g_pCompositor->m_pAqBackend->getImplementations()) { if (b->type() != Aquamarine::AQ_BACKEND_DRM) continue; @@ -219,9 +228,64 @@ CProtocolManager::~CProtocolManager() { PROTO::screencopy.reset(); PROTO::toplevelExport.reset(); PROTO::globalShortcuts.reset(); + PROTO::xdgDialog.reset(); + PROTO::singlePixel.reset(); + PROTO::securityContext.reset(); + PROTO::ctm.reset(); PROTO::lease.reset(); PROTO::sync.reset(); PROTO::mesaDRM.reset(); PROTO::linuxDma.reset(); } + +bool CProtocolManager::isGlobalPrivileged(const wl_global* global) { + if (!global) + return false; + + for (auto& [k, v] : PROTO::outputs) { + if (global == v->getGlobal()) + return false; + } + + // this is a static whitelist of allowed protocols, + // outputs are dynamic so we checked them above + // clang-format off + static const std::vector ALLOWED_WHITELIST = { + PROTO::seat->getGlobal(), + PROTO::data->getGlobal(), + PROTO::compositor->getGlobal(), + PROTO::subcompositor->getGlobal(), + PROTO::shm->getGlobal(), + PROTO::viewport->getGlobal(), + PROTO::tearing->getGlobal(), + PROTO::fractional->getGlobal(), + PROTO::cursorShape->getGlobal(), + PROTO::idleInhibit->getGlobal(), + PROTO::relativePointer->getGlobal(), + PROTO::xdgDecoration->getGlobal(), + PROTO::alphaModifier->getGlobal(), + PROTO::pointerGestures->getGlobal(), + PROTO::shortcutsInhibit->getGlobal(), + PROTO::textInputV1->getGlobal(), + PROTO::textInputV3->getGlobal(), + PROTO::constraints->getGlobal(), + PROTO::activation->getGlobal(), + PROTO::idle->getGlobal(), + PROTO::ime->getGlobal(), + PROTO::virtualKeyboard->getGlobal(), + PROTO::virtualPointer->getGlobal(), + PROTO::serverDecorationKDE->getGlobal(), + PROTO::tablet->getGlobal(), + PROTO::presentation->getGlobal(), + PROTO::xdgShell->getGlobal(), + PROTO::xdgDialog->getGlobal(), + PROTO::singlePixel->getGlobal(), + PROTO::sync ? PROTO::sync->getGlobal() : nullptr, + PROTO::mesaDRM ? PROTO::mesaDRM->getGlobal() : nullptr, + PROTO::linuxDma ? PROTO::linuxDma->getGlobal() : nullptr, + }; + // clang-format on + + return std::find(ALLOWED_WHITELIST.begin(), ALLOWED_WHITELIST.end(), global) == ALLOWED_WHITELIST.end(); +} diff --git a/src/managers/ProtocolManager.hpp b/src/managers/ProtocolManager.hpp index 1b6d31b6..d5629e9e 100644 --- a/src/managers/ProtocolManager.hpp +++ b/src/managers/ProtocolManager.hpp @@ -11,6 +11,8 @@ class CProtocolManager { CProtocolManager(); ~CProtocolManager(); + bool isGlobalPrivileged(const wl_global* global); + private: std::unordered_map m_mModeChangeListeners; diff --git a/src/managers/SeatManager.cpp b/src/managers/SeatManager.cpp index 801ae55a..b40d6cad 100644 --- a/src/managers/SeatManager.cpp +++ b/src/managers/SeatManager.cpp @@ -6,6 +6,7 @@ #include "../protocols/core/Compositor.hpp" #include "../Compositor.hpp" #include "../devices/IKeyboard.hpp" +#include "wlr-layer-shell-unstable-v1.hpp" #include #include @@ -24,7 +25,7 @@ void CSeatManager::onNewSeatResource(SP resource) { } SP CSeatManager::containerForResource(SP seatResource) { - for (auto& c : seatResources) { + for (auto const& c : seatResources) { if (c->resource == seatResource) return c; } @@ -112,11 +113,11 @@ void CSeatManager::setKeyboardFocus(SP surf) { if (state.keyboardFocusResource) { auto client = state.keyboardFocusResource->client(); - for (auto& s : seatResources) { + for (auto const& s : seatResources) { if (s->resource->client() != client) continue; - for (auto& k : s->resource->keyboards) { + for (auto const& k : s->resource->keyboards) { if (!k) continue; @@ -134,12 +135,12 @@ void CSeatManager::setKeyboardFocus(SP surf) { } auto client = surf->client(); - for (auto& r : seatResources | std::views::reverse) { + for (auto const& r : seatResources | std::views::reverse) { if (r->resource->client() != client) continue; state.keyboardFocusResource = r->resource; - for (auto& k : r->resource->keyboards) { + for (auto const& k : r->resource->keyboards) { if (!k) continue; @@ -157,11 +158,11 @@ void CSeatManager::sendKeyboardKey(uint32_t timeMs, uint32_t key, wl_keyboard_ke if (!state.keyboardFocusResource) return; - for (auto& s : seatResources) { + for (auto const& s : seatResources) { if (s->resource->client() != state.keyboardFocusResource->client()) continue; - for (auto& k : s->resource->keyboards) { + for (auto const& k : s->resource->keyboards) { if (!k) continue; @@ -174,11 +175,11 @@ void CSeatManager::sendKeyboardMods(uint32_t depressed, uint32_t latched, uint32 if (!state.keyboardFocusResource) return; - for (auto& s : seatResources) { + for (auto const& s : seatResources) { if (s->resource->client() != state.keyboardFocusResource->client()) continue; - for (auto& k : s->resource->keyboards) { + for (auto const& k : s->resource->keyboards) { if (!k) continue; @@ -205,11 +206,11 @@ void CSeatManager::setPointerFocus(SP surf, const Vector2D& if (state.pointerFocusResource) { auto client = state.pointerFocusResource->client(); - for (auto& s : seatResources) { + for (auto const& s : seatResources) { if (s->resource->client() != client) continue; - for (auto& p : s->resource->pointers) { + for (auto const& p : s->resource->pointers) { if (!p) continue; @@ -230,12 +231,12 @@ void CSeatManager::setPointerFocus(SP surf, const Vector2D& } auto client = surf->client(); - for (auto& r : seatResources | std::views::reverse) { + for (auto const& r : seatResources | std::views::reverse) { if (r->resource->client() != client) continue; state.pointerFocusResource = r->resource; - for (auto& p : r->resource->pointers) { + for (auto const& p : r->resource->pointers) { if (!p) continue; @@ -257,11 +258,11 @@ void CSeatManager::sendPointerMotion(uint32_t timeMs, const Vector2D& local) { if (!state.pointerFocusResource) return; - for (auto& s : seatResources) { + for (auto const& s : seatResources) { if (s->resource->client() != state.pointerFocusResource->client()) continue; - for (auto& p : s->resource->pointers) { + for (auto const& p : s->resource->pointers) { if (!p) continue; @@ -276,11 +277,11 @@ void CSeatManager::sendPointerButton(uint32_t timeMs, uint32_t key, wl_pointer_b if (!state.pointerFocusResource) return; - for (auto& s : seatResources) { + for (auto const& s : seatResources) { if (s->resource->client() != state.pointerFocusResource->client()) continue; - for (auto& p : s->resource->pointers) { + for (auto const& p : s->resource->pointers) { if (!p) continue; @@ -300,11 +301,11 @@ void CSeatManager::sendPointerFrame(WP pResource) { if (!pResource) return; - for (auto& s : seatResources) { + for (auto const& s : seatResources) { if (s->resource->client() != pResource->client()) continue; - for (auto& p : s->resource->pointers) { + for (auto const& p : s->resource->pointers) { if (!p) continue; @@ -318,11 +319,11 @@ void CSeatManager::sendPointerAxis(uint32_t timeMs, wl_pointer_axis axis, double if (!state.pointerFocusResource) return; - for (auto& s : seatResources) { + for (auto const& s : seatResources) { if (s->resource->client() != state.pointerFocusResource->client()) continue; - for (auto& p : s->resource->pointers) { + for (auto const& p : s->resource->pointers) { if (!p) continue; @@ -346,12 +347,12 @@ void CSeatManager::sendTouchDown(SP surf, uint32_t timeMs, i state.touchFocus = surf; auto client = surf->client(); - for (auto& r : seatResources | std::views::reverse) { + for (auto const& r : seatResources | std::views::reverse) { if (r->resource->client() != client) continue; state.touchFocusResource = r->resource; - for (auto& t : r->resource->touches) { + for (auto const& t : r->resource->touches) { if (!t) continue; @@ -372,12 +373,12 @@ void CSeatManager::sendTouchUp(uint32_t timeMs, int32_t id) { return; auto client = state.touchFocusResource->client(); - for (auto& r : seatResources | std::views::reverse) { + for (auto const& r : seatResources | std::views::reverse) { if (r->resource->client() != client) continue; state.touchFocusResource = r->resource; - for (auto& t : r->resource->touches) { + for (auto const& t : r->resource->touches) { if (!t) continue; @@ -395,11 +396,11 @@ void CSeatManager::sendTouchMotion(uint32_t timeMs, int32_t id, const Vector2D& if (!state.touchFocusResource) return; - for (auto& s : seatResources) { + for (auto const& s : seatResources) { if (s->resource->client() != state.touchFocusResource->client()) continue; - for (auto& t : s->resource->touches) { + for (auto const& t : s->resource->touches) { if (!t) continue; @@ -412,11 +413,11 @@ void CSeatManager::sendTouchFrame() { if (!state.touchFocusResource) return; - for (auto& s : seatResources) { + for (auto const& s : seatResources) { if (s->resource->client() != state.touchFocusResource->client()) continue; - for (auto& t : s->resource->touches) { + for (auto const& t : s->resource->touches) { if (!t) continue; @@ -429,11 +430,11 @@ void CSeatManager::sendTouchCancel() { if (!state.touchFocusResource) return; - for (auto& s : seatResources) { + for (auto const& s : seatResources) { if (s->resource->client() != state.touchFocusResource->client()) continue; - for (auto& t : s->resource->touches) { + for (auto const& t : s->resource->touches) { if (!t) continue; @@ -446,11 +447,11 @@ void CSeatManager::sendTouchShape(int32_t id, const Vector2D& shape) { if (!state.touchFocusResource) return; - for (auto& s : seatResources) { + for (auto const& s : seatResources) { if (s->resource->client() != state.touchFocusResource->client()) continue; - for (auto& t : s->resource->touches) { + for (auto const& t : s->resource->touches) { if (!t) continue; @@ -463,11 +464,11 @@ void CSeatManager::sendTouchOrientation(int32_t id, double angle) { if (!state.touchFocusResource) return; - for (auto& s : seatResources) { + for (auto const& s : seatResources) { if (s->resource->client() != state.touchFocusResource->client()) continue; - for (auto& t : s->resource->touches) { + for (auto const& t : s->resource->touches) { if (!t) continue; @@ -483,7 +484,7 @@ void CSeatManager::refocusGrab() { if (seatGrab->surfs.size() > 0) { // try to find a surf in focus first const auto MOUSE = g_pInputManager->getMouseCoordsInternal(); - for (auto& s : seatGrab->surfs) { + for (auto const& s : seatGrab->surfs) { auto hlSurf = CWLSurface::fromResource(s.lock()); if (!hlSurf) continue; @@ -584,6 +585,36 @@ void CSeatManager::setGrab(SP grab) { auto oldGrab = seatGrab; seatGrab.reset(); g_pInputManager->refocus(); + + auto currentFocus = state.keyboardFocus.lock(); + auto refocus = !currentFocus; + + SP surf; + PHLLS layer; + + if (!refocus) { + surf = CWLSurface::fromResource(currentFocus); + layer = surf->getLayer(); + } + + if (!refocus && !layer) { + auto popup = surf->getPopup(); + if (popup) { + auto parent = popup->getT1Owner(); + layer = parent->getLayer(); + } + } + + if (!refocus && layer) + refocus = layer->interactivity == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE; + + if (refocus) { + auto candidate = g_pCompositor->m_pLastWindow.lock(); + + if (candidate) + g_pCompositor->focusWindow(candidate); + } + if (oldGrab->onEnd) oldGrab->onEnd(); } diff --git a/src/managers/SeatManager.hpp b/src/managers/SeatManager.hpp index 43ebe8b5..e50f123f 100644 --- a/src/managers/SeatManager.hpp +++ b/src/managers/SeatManager.hpp @@ -2,7 +2,6 @@ #include #include -#include "../helpers/WLListener.hpp" #include "../macros.hpp" #include "../helpers/signal/Signal.hpp" #include "../helpers/math/Math.hpp" @@ -129,6 +128,9 @@ class CSeatManager { void setGrab(SP grab); // nullptr removes SP seatGrab; + bool isPointerFrameSkipped = false; + bool isPointerFrameCommit = false; + private: struct SSeatResourceContainer { SSeatResourceContainer(SP); diff --git a/src/managers/SessionLockManager.cpp b/src/managers/SessionLockManager.cpp index a82432a8..260a3992 100644 --- a/src/managers/SessionLockManager.cpp +++ b/src/managers/SessionLockManager.cpp @@ -4,6 +4,7 @@ #include "../protocols/FractionalScale.hpp" #include "../protocols/SessionLock.hpp" #include +#include SSessionLockSurface::SSessionLockSurface(SP surface_) : surface(surface_) { pWlrSurface = surface->surface(); @@ -29,6 +30,9 @@ SSessionLockSurface::SSessionLockSurface(SP surface_) : sur listeners.commit = surface_->events.commit.registerListener([this](std::any data) { const auto PMONITOR = g_pCompositor->getMonitorFromID(iMonitorID); + if (mapped && pWlrSurface != g_pCompositor->m_pLastFocus) + g_pInputManager->simulateMouseMovement(); + if (PMONITOR) g_pHyprRenderer->damageMonitor(PMONITOR); }); @@ -67,7 +71,7 @@ void CSessionLockManager::onNewSessionLock(SP pLock) { m_pSessionLock.reset(); g_pInputManager->refocus(); - for (auto& m : g_pCompositor->m_vMonitors) + for (auto const& m : g_pCompositor->m_vMonitors) g_pHyprRenderer->damageMonitor(m.get()); }); @@ -75,7 +79,7 @@ void CSessionLockManager::onNewSessionLock(SP pLock) { m_pSessionLock.reset(); g_pCompositor->focusSurface(nullptr); - for (auto& m : g_pCompositor->m_vMonitors) + for (auto const& m : g_pCompositor->m_vMonitors) g_pHyprRenderer->damageMonitor(m.get()); }); @@ -90,7 +94,7 @@ SSessionLockSurface* CSessionLockManager::getSessionLockSurfaceForMonitor(uint64 if (!m_pSessionLock) return nullptr; - for (auto& sls : m_pSessionLock->vSessionLockSurfaces) { + for (auto const& sls : m_pSessionLock->vSessionLockSurfaces) { if (sls->iMonitorID == id) { if (sls->mapped) return sls.get(); @@ -137,7 +141,7 @@ bool CSessionLockManager::isSurfaceSessionLock(SP pSurface) if (!m_pSessionLock) return false; - for (auto& sls : m_pSessionLock->vSessionLockSurfaces) { + for (auto const& sls : m_pSessionLock->vSessionLockSurfaces) { if (sls->surface->surface() == pSurface) return true; } @@ -154,7 +158,7 @@ void CSessionLockManager::removeSessionLockSurface(SSessionLockSurface* pSLS) { if (g_pCompositor->m_pLastFocus) return; - for (auto& sls : m_pSessionLock->vSessionLockSurfaces) { + for (auto const& sls : m_pSessionLock->vSessionLockSurfaces) { if (!sls->mapped) continue; @@ -166,3 +170,7 @@ void CSessionLockManager::removeSessionLockSurface(SSessionLockSurface* pSLS) { bool CSessionLockManager::isSessionLockPresent() { return m_pSessionLock && !m_pSessionLock->vSessionLockSurfaces.empty(); } + +bool CSessionLockManager::anySessionLockSurfacesPresent() { + return m_pSessionLock && std::ranges::any_of(m_pSessionLock->vSessionLockSurfaces, [](const auto& surf) { return surf->mapped; }); +} diff --git a/src/managers/SessionLockManager.hpp b/src/managers/SessionLockManager.hpp index b01ee288..9b3b882c 100644 --- a/src/managers/SessionLockManager.hpp +++ b/src/managers/SessionLockManager.hpp @@ -55,6 +55,7 @@ class CSessionLockManager { bool isSessionLocked(); bool isSessionLockPresent(); bool isSurfaceSessionLock(SP); + bool anySessionLockSurfacesPresent(); void removeSessionLockSurface(SSessionLockSurface*); diff --git a/src/managers/XCursorManager.cpp b/src/managers/XCursorManager.cpp index b3f33086..93ee7965 100644 --- a/src/managers/XCursorManager.cpp +++ b/src/managers/XCursorManager.cpp @@ -1,7 +1,12 @@ +#include #include #include #include +#include +#include +#include "config/ConfigValue.hpp" #include "helpers/CursorShapes.hpp" +#include "../managers/CursorManager.hpp" #include "debug/Log.hpp" #include "XCursorManager.hpp" @@ -96,12 +101,13 @@ CXCursorManager::CXCursorManager() { defaultCursor = hyprCursor; } -void CXCursorManager::loadTheme(std::string const& name, int size) { - if (lastLoadSize == size && themeName == name) +void CXCursorManager::loadTheme(std::string const& name, int size, float scale) { + if (lastLoadSize == (size * std::ceil(scale)) && themeName == name && lastLoadScale == scale) return; - lastLoadSize = size; - themeName = name.empty() ? "default" : name; + lastLoadSize = size * std::ceil(scale); + lastLoadScale = scale; + themeName = name.empty() ? "default" : name; defaultCursor.reset(); cursors.clear(); @@ -111,9 +117,11 @@ void CXCursorManager::loadTheme(std::string const& name, int size) { cursors = loadStandardCursors(themeName, lastLoadSize); } else { for (auto const& p : paths) { - auto dirCursors = loadAllFromDir(p, lastLoadSize); - std::copy_if(dirCursors.begin(), dirCursors.end(), std::back_inserter(cursors), - [this](auto const& p) { return std::none_of(cursors.begin(), cursors.end(), [&p](auto const& dp) { return dp->shape == p->shape; }); }); + try { + auto dirCursors = loadAllFromDir(p, lastLoadSize); + std::copy_if(dirCursors.begin(), dirCursors.end(), std::back_inserter(cursors), + [this](auto const& p) { return std::none_of(cursors.begin(), cursors.end(), [&p](auto const& dp) { return dp->shape == p->shape; }); }); + } catch (std::exception& e) { Debug::log(ERR, "XCursor path {} can't be loaded: threw error {}", p, e.what()); } } } @@ -135,18 +143,27 @@ void CXCursorManager::loadTheme(std::string const& name, int size) { continue; } + if (std::any_of(cursors.begin(), cursors.end(), [&shape](auto const& dp) { return dp->shape == shape; })) { + Debug::log(LOG, "XCursor already has a shape {} loaded, skipping", shape); + continue; + } + auto cursor = makeShared(); cursor->images = it->get()->images; cursor->shape = shape; cursors.emplace_back(cursor); } + + static auto SYNCGSETTINGS = CConfigValue("cursor:sync_gsettings_theme"); + if (*SYNCGSETTINGS) + syncGsettings(); } -SP CXCursorManager::getShape(std::string const& shape, int size) { +SP CXCursorManager::getShape(std::string const& shape, int size, float scale) { // monitor scaling changed etc, so reload theme with new size. - if (size != lastLoadSize) - loadTheme(themeName, size); + if ((size * std::ceil(scale)) != lastLoadSize || scale != lastLoadScale) + loadTheme(themeName, size, scale); // try to get an icon we know if we have one for (auto const& c : cursors) { @@ -180,11 +197,10 @@ SP CXCursorManager::createCursor(std::string const& shape, XcursorIma return xcursor; } -std::vector CXCursorManager::themePaths(std::string const& theme) { - auto const* path = XcursorLibraryPath(); - std::vector paths; +std::unordered_set CXCursorManager::themePaths(std::string const& theme) { + auto const* path = XcursorLibraryPath(); - auto expandTilde = [](std::string const& path) { + auto expandTilde = [](std::string const& path) { if (!path.empty() && path[0] == '~') { const char* home = std::getenv("HOME"); if (home) @@ -193,15 +209,109 @@ std::vector CXCursorManager::themePaths(std::string const& theme) { return path; }; - if (path) { + auto getInheritThemes = [](std::string const& indexTheme) { + std::ifstream infile(indexTheme); + std::string line; + std::vector themes; + + Debug::log(LOG, "XCursor parsing index.theme {}", indexTheme); + + while (std::getline(infile, line)) { + if (line.empty()) + continue; + + // Trim leading and trailing whitespace + auto pos = line.find_first_not_of(" \t\n\r"); + if (pos != std::string::npos) + line.erase(0, pos); + + pos = line.find_last_not_of(" \t\n\r"); + if (pos != std::string::npos && pos < line.length()) { + line.erase(pos + 1); + } + + if (line.rfind("Inherits", 8) != std::string::npos) { // Check if line starts with "Inherits" + std::string inheritThemes = line.substr(8); // Extract the part after "Inherits" + if (inheritThemes.empty()) + continue; + + // Remove leading whitespace from inheritThemes and = + pos = inheritThemes.find_first_not_of(" \t\n\r"); + if (pos != std::string::npos) + inheritThemes.erase(0, pos); + + if (inheritThemes.empty()) + continue; + + if (inheritThemes.at(0) == '=') + inheritThemes.erase(0, 1); + else + continue; // not correct formatted index.theme + + pos = inheritThemes.find_first_not_of(" \t\n\r"); + if (pos != std::string::npos) + inheritThemes.erase(0, pos); + + std::stringstream inheritStream(inheritThemes); + std::string inheritTheme; + while (std::getline(inheritStream, inheritTheme, ',')) { + if (inheritTheme.empty()) + continue; + + // Trim leading and trailing whitespace from each theme + pos = inheritTheme.find_first_not_of(" \t\n\r"); + if (pos != std::string::npos) + inheritTheme.erase(0, pos); + + pos = inheritTheme.find_last_not_of(" \t\n\r"); + if (pos != std::string::npos && pos < inheritTheme.length()) + inheritTheme.erase(inheritTheme.find_last_not_of(" \t\n\r") + 1); + + themes.push_back(inheritTheme); + } + } + } + infile.close(); + + return themes; + }; + + std::unordered_set paths; + std::unordered_set inherits; + + auto scanTheme = [&path, &paths, &expandTilde, &inherits, &getInheritThemes](auto const& t) { std::stringstream ss(path); - std::string item; + std::string line; - while (std::getline(ss, item, ':')) { - auto p = expandTilde(item + "/" + theme + "/cursors"); + Debug::log(LOG, "XCursor scanning theme {}", t); - if (std::filesystem::exists(p) && std::filesystem::is_directory(p)) - paths.push_back(p); + while (std::getline(ss, line, ':')) { + auto p = expandTilde(line + "/" + t + "/cursors"); + if (std::filesystem::exists(p) && std::filesystem::is_directory(p)) { + Debug::log(LOG, "XCursor using theme path {}", p); + paths.insert(p); + } + + auto inherit = expandTilde(line + "/" + t + "/index.theme"); + if (std::filesystem::exists(inherit) && std::filesystem::is_regular_file(inherit)) { + auto inheritThemes = getInheritThemes(inherit); + for (auto const& i : inheritThemes) { + Debug::log(LOG, "XCursor theme {} inherits {}", t, i); + inherits.insert(i); + } + } + } + }; + + if (path) { + scanTheme(theme); + while (!inherits.empty()) { + auto oldInherits = inherits; + for (auto const& i : oldInherits) + scanTheme(i); + + if (oldInherits.size() == inherits.size()) + break; } } @@ -402,14 +512,16 @@ std::vector> CXCursorManager::loadStandardCursors(std::string cons std::vector> CXCursorManager::loadAllFromDir(std::string const& path, int size) { std::vector> newCursors; - std::string full; if (std::filesystem::exists(path) && std::filesystem::is_directory(path)) { for (const auto& entry : std::filesystem::directory_iterator(path)) { - if (!entry.is_regular_file() && !entry.is_symlink()) + std::error_code e1, e2; + if ((!entry.is_regular_file(e1) && !entry.is_symlink(e2)) || e1 || e2) { + Debug::log(WARN, "XCursor failed to load shape {}: {}", entry.path().stem().string(), e1 ? e1.message() : e2.message()); continue; + } - std::string full = entry.path().string(); + auto const& full = entry.path().string(); using PcloseType = int (*)(FILE*); const std::unique_ptr f(fopen(full.c_str(), "r"), static_cast(fclose)); @@ -428,7 +540,7 @@ std::vector> CXCursorManager::loadAllFromDir(std::string const& pa } } - std::string shape = entry.path().filename().string(); + auto const& shape = entry.path().filename().string(); auto cursor = createCursor(shape, xImages); newCursors.emplace_back(cursor); @@ -445,3 +557,56 @@ std::vector> CXCursorManager::loadAllFromDir(std::string const& pa return newCursors; } + +void CXCursorManager::syncGsettings() { + auto checkParamExists = [](std::string const& paramName, std::string const& category) { + auto* gSettingsSchemaSource = g_settings_schema_source_get_default(); + + if (!gSettingsSchemaSource) { + Debug::log(WARN, "GSettings default schema source does not exist, cant sync GSettings"); + return false; + } + + auto* gSettingsSchema = g_settings_schema_source_lookup(gSettingsSchemaSource, category.c_str(), true); + bool hasParam = false; + + if (gSettingsSchema != NULL) { + hasParam = gSettingsSchema && g_settings_schema_has_key(gSettingsSchema, paramName.c_str()); + g_settings_schema_unref(gSettingsSchema); + } + + return hasParam; + }; + + using SettingValue = std::variant; + auto setValue = [&checkParamExists](std::string const& paramName, const SettingValue& paramValue, std::string const& category) { + if (!checkParamExists(paramName, category)) { + Debug::log(WARN, "GSettings parameter doesnt exist {} in {}", paramName, category); + return; + } + + auto* gsettings = g_settings_new(category.c_str()); + + if (!gsettings) { + Debug::log(WARN, "GSettings failed to allocate new settings with category {}", category); + return; + } + + std::visit( + [&](auto&& value) { + using T = std::decay_t; + if constexpr (std::is_same_v) + g_settings_set_string(gsettings, paramName.c_str(), value.c_str()); + else if constexpr (std::is_same_v) + g_settings_set_int(gsettings, paramName.c_str(), value); + }, + paramValue); + + g_settings_sync(); + g_object_unref(gsettings); + }; + + int unscaledSize = lastLoadSize / std::ceil(lastLoadScale); + setValue("cursor-theme", themeName, "org.gnome.desktop.interface"); + setValue("cursor-size", unscaledSize, "org.gnome.desktop.interface"); +} diff --git a/src/managers/XCursorManager.hpp b/src/managers/XCursorManager.hpp index 9ced076f..1f3c24db 100644 --- a/src/managers/XCursorManager.hpp +++ b/src/managers/XCursorManager.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include #include #include @@ -28,19 +29,21 @@ class CXCursorManager { CXCursorManager(); ~CXCursorManager() = default; - void loadTheme(const std::string& name, int size); - SP getShape(std::string const& shape, int size); + void loadTheme(const std::string& name, int size, float scale); + SP getShape(std::string const& shape, int size, float scale); + void syncGsettings(); private: - SP createCursor(std::string const& shape, XcursorImages* xImages); - std::vector themePaths(std::string const& theme); - std::string getLegacyShapeName(std::string const& shape); - std::vector> loadStandardCursors(std::string const& name, int size); - std::vector> loadAllFromDir(std::string const& path, int size); + SP createCursor(std::string const& shape, XcursorImages* xImages); + std::unordered_set themePaths(std::string const& theme); + std::string getLegacyShapeName(std::string const& shape); + std::vector> loadStandardCursors(std::string const& name, int size); + std::vector> loadAllFromDir(std::string const& path, int size); - int lastLoadSize = 0; - std::string themeName = ""; - SP defaultCursor; - SP hyprCursor; - std::vector> cursors; -}; \ No newline at end of file + int lastLoadSize = 0; + float lastLoadScale = 0; + std::string themeName = ""; + SP defaultCursor; + SP hyprCursor; + std::vector> cursors; +}; diff --git a/src/managers/XWaylandManager.cpp b/src/managers/XWaylandManager.cpp index f329dbe1..8b63d37e 100644 --- a/src/managers/XWaylandManager.cpp +++ b/src/managers/XWaylandManager.cpp @@ -5,14 +5,13 @@ #include "../protocols/XDGShell.hpp" #include "../protocols/core/Compositor.hpp" #include "../xwayland/XWayland.hpp" +#include #define OUTPUT_MANAGER_VERSION 3 #define OUTPUT_DONE_DEPRECATED_SINCE_VERSION 3 #define OUTPUT_DESCRIPTION_MUTABLE_SINCE_VERSION 3 -CHyprXWaylandManager::CHyprXWaylandManager() { - ; -} +CHyprXWaylandManager::CHyprXWaylandManager() = default; CHyprXWaylandManager::~CHyprXWaylandManager() { #ifndef NO_XWAYLAND @@ -21,44 +20,50 @@ CHyprXWaylandManager::~CHyprXWaylandManager() { } SP CHyprXWaylandManager::getWindowSurface(PHLWINDOW pWindow) { - return pWindow->m_pWLSurface->resource(); + return pWindow ? pWindow->m_pWLSurface->resource() : nullptr; } void CHyprXWaylandManager::activateSurface(SP pSurface, bool activate) { if (!pSurface) return; - // TODO: - // this cannot be nicely done until we rewrite wlr_surface - for (auto& w : g_pCompositor->m_vWindows) { - if (!w->m_bIsMapped) - continue; - - if (w->m_pWLSurface->resource() != pSurface) - continue; - - if (w->m_bIsX11) { - if (activate) { - w->m_pXWaylandSurface->setMinimized(false); - w->m_pXWaylandSurface->restackToTop(); - } - w->m_pXWaylandSurface->activate(activate); - } else - w->m_pXDGSurface->toplevel->setActive(activate); + auto HLSurface = CWLSurface::fromResource(pSurface); + if (!HLSurface) { + Debug::log(TRACE, "CHyprXWaylandManager::activateSurface on non-desktop surface, ignoring"); + return; } + + const auto PWINDOW = HLSurface->getWindow(); + if (!PWINDOW) { + Debug::log(TRACE, "CHyprXWaylandManager::activateSurface on non-window surface, ignoring"); + return; + } + + if (PWINDOW->m_bIsX11) { + if (PWINDOW->m_pXWaylandSurface) { + if (activate) { + PWINDOW->m_pXWaylandSurface->setMinimized(false); + PWINDOW->m_pXWaylandSurface->restackToTop(); + } + PWINDOW->m_pXWaylandSurface->activate(activate); + } + } else if (PWINDOW->m_pXDGSurface) + PWINDOW->m_pXDGSurface->toplevel->setActive(activate); } void CHyprXWaylandManager::activateWindow(PHLWINDOW pWindow, bool activate) { if (pWindow->m_bIsX11) { - setWindowSize(pWindow, pWindow->m_vRealSize.value()); // update xwayland output pos if (activate) { + setWindowSize(pWindow, pWindow->m_vRealSize.value()); // update xwayland output pos pWindow->m_pXWaylandSurface->setMinimized(false); - if (pWindow->m_iX11Type != 2) + + if (!pWindow->isX11OverrideRedirect()) pWindow->m_pXWaylandSurface->restackToTop(); } pWindow->m_pXWaylandSurface->activate(activate); + } else if (pWindow->m_pXDGSurface && pWindow->m_pXDGSurface->toplevel) pWindow->m_pXDGSurface->toplevel->setActive(activate); @@ -72,16 +77,22 @@ void CHyprXWaylandManager::activateWindow(PHLWINDOW pWindow, bool activate) { } void CHyprXWaylandManager::getGeometryForWindow(PHLWINDOW pWindow, CBox* pbox) { + if (!pWindow) + return; + if (pWindow->m_bIsX11) { const auto SIZEHINTS = pWindow->m_pXWaylandSurface->sizeHints.get(); - if (SIZEHINTS && pWindow->m_iX11Type != 2) { + if (SIZEHINTS && !pWindow->isX11OverrideRedirect()) { // WM_SIZE_HINTS' x,y,w,h is deprecated it seems. // Source: https://x.org/releases/X11R7.6/doc/xorg-docs/specs/ICCCM/icccm.html#wm_normal_hints_property pbox->x = pWindow->m_pXWaylandSurface->geometry.x; pbox->y = pWindow->m_pXWaylandSurface->geometry.y; - if ((SIZEHINTS->flags & 0x2 /* ICCCM USSize */) || (SIZEHINTS->flags & 0x8 /* ICCCM PSize */)) { + constexpr int ICCCM_USSize = 0x2; + constexpr int ICCCM_PSize = 0x8; + + if ((SIZEHINTS->flags & ICCCM_USSize) || (SIZEHINTS->flags & ICCCM_PSize)) { pbox->w = SIZEHINTS->base_width; pbox->h = SIZEHINTS->base_height; } else { @@ -115,34 +126,34 @@ void CHyprXWaylandManager::setWindowSize(PHLWINDOW pWindow, Vector2D size, bool Vector2D windowPos = pWindow->m_vRealPosition.value(); if (pWindow->m_bIsX11 && PMONITOR) { - windowPos = windowPos - PMONITOR->vecPosition; // normalize to monitor + windowPos -= PMONITOR->vecPosition; // normalize to monitor if (*PXWLFORCESCALEZERO) - windowPos = windowPos * PMONITOR->scale; // scale if applicable - windowPos = windowPos + PMONITOR->vecXWaylandPosition; // move to correct position for xwayland + windowPos *= PMONITOR->scale; // scale if applicable + windowPos += PMONITOR->vecXWaylandPosition; // move to correct position for xwayland } - if (!force && ((pWindow->m_vPendingReportedSize == size && windowPos == pWindow->m_vReportedPosition) || (pWindow->m_vPendingReportedSize == size && !pWindow->m_bIsX11))) + if (!force && pWindow->m_vPendingReportedSize == size && (windowPos == pWindow->m_vReportedPosition || !pWindow->m_bIsX11)) return; pWindow->m_vReportedPosition = windowPos; pWindow->m_vPendingReportedSize = size; - pWindow->m_fX11SurfaceScaledBy = 1.f; + pWindow->m_fX11SurfaceScaledBy = 1.0f; if (*PXWLFORCESCALEZERO && pWindow->m_bIsX11 && PMONITOR) { - size = size * PMONITOR->scale; + size *= PMONITOR->scale; pWindow->m_fX11SurfaceScaledBy = PMONITOR->scale; } if (pWindow->m_bIsX11) pWindow->m_pXWaylandSurface->configure({windowPos, size}); else if (pWindow->m_pXDGSurface->toplevel) - pWindow->m_vPendingSizeAcks.push_back(std::make_pair<>(pWindow->m_pXDGSurface->toplevel->setSize(size), size.floor())); + pWindow->m_vPendingSizeAcks.emplace_back(pWindow->m_pXDGSurface->toplevel->setSize(size), size.floor()); } bool CHyprXWaylandManager::shouldBeFloated(PHLWINDOW pWindow, bool pending) { if (pWindow->m_bIsX11) { - for (auto& a : pWindow->m_pXWaylandSurface->atoms) + for (const auto& a : pWindow->m_pXWaylandSurface->atoms) if (a == HYPRATOMS["_NET_WM_WINDOW_TYPE_DIALOG"] || a == HYPRATOMS["_NET_WM_WINDOW_TYPE_SPLASH"] || a == HYPRATOMS["_NET_WM_WINDOW_TYPE_TOOLBAR"] || a == HYPRATOMS["_NET_WM_WINDOW_TYPE_UTILITY"] || a == HYPRATOMS["_NET_WM_WINDOW_TYPE_TOOLTIP"] || a == HYPRATOMS["_NET_WM_WINDOW_TYPE_POPUP_MENU"] || a == HYPRATOMS["_NET_WM_WINDOW_TYPE_DOCK"] || a == HYPRATOMS["_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"] || a == HYPRATOMS["_NET_WM_WINDOW_TYPE_MENU"] || @@ -155,18 +166,8 @@ bool CHyprXWaylandManager::shouldBeFloated(PHLWINDOW pWindow, bool pending) { return true; } - if (pWindow->m_pXWaylandSurface->modal) { - pWindow->m_bIsModal = true; - return true; - } - - if (pWindow->m_pXWaylandSurface->transient) - return true; - - if (pWindow->m_pXWaylandSurface->role.contains("task_dialog") || pWindow->m_pXWaylandSurface->role.contains("pop-up")) - return true; - - if (pWindow->m_pXWaylandSurface->overrideRedirect) + if (pWindow->isModal() || pWindow->m_pXWaylandSurface->transient || + (pWindow->m_pXWaylandSurface->role.contains("task_dialog") || pWindow->m_pXWaylandSurface->role.contains("pop-up")) || pWindow->m_pXWaylandSurface->overrideRedirect) return true; const auto SIZEHINTS = pWindow->m_pXWaylandSurface->sizeHints.get(); @@ -188,7 +189,7 @@ void CHyprXWaylandManager::checkBorders(PHLWINDOW pWindow) { if (!pWindow->m_bIsX11) return; - for (auto& a : pWindow->m_pXWaylandSurface->atoms) { + for (auto const& a : pWindow->m_pXWaylandSurface->atoms) { if (a == HYPRATOMS["_NET_WM_WINDOW_TYPE_POPUP_MENU"] || a == HYPRATOMS["_NET_WM_WINDOW_TYPE_NOTIFICATION"] || a == HYPRATOMS["_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"] || a == HYPRATOMS["_NET_WM_WINDOW_TYPE_COMBO"] || a == HYPRATOMS["_NET_WM_WINDOW_TYPE_MENU"] || a == HYPRATOMS["_NET_WM_WINDOW_TYPE_SPLASH"] || a == HYPRATOMS["_NET_WM_WINDOW_TYPE_TOOLTIP"]) { @@ -198,12 +199,14 @@ void CHyprXWaylandManager::checkBorders(PHLWINDOW pWindow) { } } - if (pWindow->m_iX11Type == 2) { + if (pWindow->isX11OverrideRedirect()) pWindow->m_bX11DoesntWantBorders = true; - } } void CHyprXWaylandManager::setWindowFullscreen(PHLWINDOW pWindow, bool fullscreen) { + if (!pWindow) + return; + if (pWindow->m_bIsX11) pWindow->m_pXWaylandSurface->setFullscreen(fullscreen); else if (pWindow->m_pXDGSurface && pWindow->m_pXDGSurface->toplevel) @@ -211,37 +214,33 @@ void CHyprXWaylandManager::setWindowFullscreen(PHLWINDOW pWindow, bool fullscree } Vector2D CHyprXWaylandManager::getMaxSizeForWindow(PHLWINDOW pWindow) { - if (!validMapped(pWindow)) - return Vector2D(99999, 99999); + constexpr int NO_MAX_SIZE_LIMIT = 99999; + if (!validMapped(pWindow) || + ((pWindow->m_bIsX11 && !pWindow->m_pXWaylandSurface->sizeHints) || (!pWindow->m_bIsX11 && !pWindow->m_pXDGSurface->toplevel) || + pWindow->m_sWindowData.noMaxSize.valueOrDefault())) + return Vector2D(NO_MAX_SIZE_LIMIT, NO_MAX_SIZE_LIMIT); - if ((pWindow->m_bIsX11 && !pWindow->m_pXWaylandSurface->sizeHints) || (!pWindow->m_bIsX11 && !pWindow->m_pXDGSurface->toplevel) || - pWindow->m_sWindowData.noMaxSize.valueOrDefault()) - return Vector2D(99999, 99999); + Vector2D maxSize = pWindow->m_bIsX11 ? Vector2D(pWindow->m_pXWaylandSurface->sizeHints->max_width, pWindow->m_pXWaylandSurface->sizeHints->max_height) : + pWindow->m_pXDGSurface->toplevel->current.maxSize; - auto MAXSIZE = pWindow->m_bIsX11 ? Vector2D(pWindow->m_pXWaylandSurface->sizeHints->max_width, pWindow->m_pXWaylandSurface->sizeHints->max_height) : - pWindow->m_pXDGSurface->toplevel->current.maxSize; + if (maxSize.x < 5) + maxSize.x = NO_MAX_SIZE_LIMIT; + if (maxSize.y < 5) + maxSize.y = NO_MAX_SIZE_LIMIT; - if (MAXSIZE.x < 5) - MAXSIZE.x = 99999; - if (MAXSIZE.y < 5) - MAXSIZE.y = 99999; - - return MAXSIZE; + return maxSize; } Vector2D CHyprXWaylandManager::getMinSizeForWindow(PHLWINDOW pWindow) { - if (!validMapped(pWindow)) + if (!validMapped(pWindow) || ((pWindow->m_bIsX11 && !pWindow->m_pXWaylandSurface->sizeHints) || (!pWindow->m_bIsX11 && !pWindow->m_pXDGSurface->toplevel))) return Vector2D(0, 0); - if ((pWindow->m_bIsX11 && !pWindow->m_pXWaylandSurface->sizeHints) || (!pWindow->m_bIsX11 && !pWindow->m_pXDGSurface->toplevel)) - return Vector2D(0, 0); + Vector2D minSize = pWindow->m_bIsX11 ? Vector2D(pWindow->m_pXWaylandSurface->sizeHints->min_width, pWindow->m_pXWaylandSurface->sizeHints->min_height) : + pWindow->m_pXDGSurface->toplevel->current.minSize; - auto MINSIZE = pWindow->m_bIsX11 ? Vector2D(pWindow->m_pXWaylandSurface->sizeHints->min_width, pWindow->m_pXWaylandSurface->sizeHints->min_height) : - pWindow->m_pXDGSurface->toplevel->current.minSize; + minSize = minSize.clamp({1, 1}); - MINSIZE = MINSIZE.clamp({1, 1}); - - return MINSIZE; + return minSize; } Vector2D CHyprXWaylandManager::xwaylandToWaylandCoords(const Vector2D& coord) { @@ -250,7 +249,7 @@ Vector2D CHyprXWaylandManager::xwaylandToWaylandCoords(const Vector2D& coord) { CMonitor* pMonitor = nullptr; double bestDistance = __FLT_MAX__; - for (auto& m : g_pCompositor->m_vMonitors) { + for (const auto& m : g_pCompositor->m_vMonitors) { const auto SIZ = *PXWLFORCESCALEZERO ? m->vecTransformedSize : m->vecSize; double distance = @@ -269,9 +268,9 @@ Vector2D CHyprXWaylandManager::xwaylandToWaylandCoords(const Vector2D& coord) { Vector2D result = coord - pMonitor->vecXWaylandPosition; // if scaled, unscale if (*PXWLFORCESCALEZERO) - result = result / pMonitor->scale; + result /= pMonitor->scale; // add pos - result = result + pMonitor->vecPosition; + result += pMonitor->vecPosition; return result; -} +} \ No newline at end of file diff --git a/src/managers/eventLoop/EventLoopManager.cpp b/src/managers/eventLoop/EventLoopManager.cpp index c2c088f8..081268c3 100644 --- a/src/managers/eventLoop/EventLoopManager.cpp +++ b/src/managers/eventLoop/EventLoopManager.cpp @@ -19,7 +19,7 @@ CEventLoopManager::CEventLoopManager(wl_display* display, wl_event_loop* wlEvent } CEventLoopManager::~CEventLoopManager() { - for (auto& eventSource : m_sWayland.aqEventSources) { + for (auto const& eventSource : m_sWayland.aqEventSources) { wl_event_source_remove(eventSource); } @@ -46,18 +46,21 @@ void CEventLoopManager::enterLoop() { m_sWayland.eventSource = wl_event_loop_add_fd(m_sWayland.loop, m_sTimers.timerfd, WL_EVENT_READABLE, timerWrite, nullptr); aqPollFDs = g_pCompositor->m_pAqBackend->getPollFDs(); - for (auto& fd : aqPollFDs) { + for (auto const& fd : aqPollFDs) { m_sWayland.aqEventSources.emplace_back(wl_event_loop_add_fd(m_sWayland.loop, fd->fd, WL_EVENT_READABLE, aquamarineFDWrite, fd.get())); - fd->onSignal(); // dispatch outstanding } + // if we have a session, dispatch it to get the pending input devices + if (g_pCompositor->m_pAqBackend->hasSession()) + g_pCompositor->m_pAqBackend->session->dispatchPendingEventsAsync(); + wl_display_run(m_sWayland.display); Debug::log(LOG, "Kicked off the event loop! :("); } void CEventLoopManager::onTimerFire() { - for (auto& t : m_sTimers.timers) { + for (auto const& t : m_sTimers.timers) { if (t.strongRef() > 1 /* if it's 1, it was lost. Don't call it. */ && t->passed() && !t->cancelled()) t->call(t); } @@ -76,8 +79,8 @@ void CEventLoopManager::removeTimer(SP timer) { } static void timespecAddNs(timespec* pTimespec, int64_t delta) { - int delta_ns_low = delta % TIMESPEC_NSEC_PER_SEC; - int delta_s_high = delta / TIMESPEC_NSEC_PER_SEC; + auto delta_ns_low = delta % TIMESPEC_NSEC_PER_SEC; + auto delta_s_high = delta / TIMESPEC_NSEC_PER_SEC; pTimespec->tv_sec += delta_s_high; @@ -94,7 +97,7 @@ void CEventLoopManager::nudgeTimers() { long nextTimerUs = 10 * 1000 * 1000; // 10s - for (auto& t : m_sTimers.timers) { + for (auto const& t : m_sTimers.timers) { if (const auto µs = t->leftUs(); µs < nextTimerUs) nextTimerUs = µs; } @@ -123,7 +126,7 @@ void CEventLoopManager::doLater(const std::function& fn) { auto cpy = IDLE->fns; IDLE->fns.clear(); IDLE->eventSource = nullptr; - for (auto& c : cpy) { + for (auto const& c : cpy) { if (c) c(); } diff --git a/src/managers/eventLoop/EventLoopTimer.cpp b/src/managers/eventLoop/EventLoopTimer.cpp index d3cfdf8d..b8943b7c 100644 --- a/src/managers/eventLoop/EventLoopTimer.cpp +++ b/src/managers/eventLoop/EventLoopTimer.cpp @@ -49,3 +49,7 @@ float CEventLoopTimer::leftUs() { return std::chrono::duration_cast(*expires - std::chrono::steady_clock::now()).count(); } + +bool CEventLoopTimer::armed() { + return expires.has_value(); +} diff --git a/src/managers/eventLoop/EventLoopTimer.hpp b/src/managers/eventLoop/EventLoopTimer.hpp index ad7d3986..e5a8c5e3 100644 --- a/src/managers/eventLoop/EventLoopTimer.hpp +++ b/src/managers/eventLoop/EventLoopTimer.hpp @@ -16,6 +16,7 @@ class CEventLoopTimer { void cancel(); bool passed(); + bool armed(); float leftUs(); diff --git a/src/managers/input/IdleInhibitor.cpp b/src/managers/input/IdleInhibitor.cpp index 2c335a7e..a6f25142 100644 --- a/src/managers/input/IdleInhibitor.cpp +++ b/src/managers/input/IdleInhibitor.cpp @@ -31,7 +31,7 @@ void CInputManager::newIdleInhibitor(std::any inhibitor) { void CInputManager::recheckIdleInhibitorStatus() { - for (auto& ii : m_vIdleInhibitors) { + for (auto const& ii : m_vIdleInhibitors) { if (ii->nonDesktop) { PROTO::idle->setInhibit(true); return; @@ -49,7 +49,7 @@ void CInputManager::recheckIdleInhibitorStatus() { } // check manual user-set inhibitors - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (w->m_eIdleInhibitMode == IDLEINHIBIT_NONE) continue; diff --git a/src/managers/input/InputManager.cpp b/src/managers/input/InputManager.cpp index 33708165..c93614fa 100644 --- a/src/managers/input/InputManager.cpp +++ b/src/managers/input/InputManager.cpp @@ -28,6 +28,7 @@ #include "../../managers/PointerManager.hpp" #include "../../managers/SeatManager.hpp" +#include "../../managers/KeybindManager.hpp" #include @@ -82,18 +83,18 @@ CInputManager::~CInputManager() { } void CInputManager::onMouseMoved(IPointer::SMotionEvent e) { - static auto PSENS = CConfigValue("general:sensitivity"); - static auto PNOACCEL = CConfigValue("input:force_no_accel"); - static auto PSENSTORAW = CConfigValue("general:apply_sens_to_raw"); + static auto PNOACCEL = CConfigValue("input:force_no_accel"); const auto DELTA = *PNOACCEL == 1 ? e.unaccel : e.delta; - if (*PSENSTORAW == 1) - PROTO::relativePointer->sendRelativeMotion((uint64_t)e.timeMs * 1000, DELTA * *PSENS, e.unaccel * *PSENS); + if (g_pSeatManager->isPointerFrameSkipped) + g_pPointerManager->storeMovement((uint64_t)e.timeMs, DELTA, e.unaccel); else - PROTO::relativePointer->sendRelativeMotion((uint64_t)e.timeMs * 1000, DELTA, e.unaccel); + g_pPointerManager->setStoredMovement((uint64_t)e.timeMs, DELTA, e.unaccel); - g_pPointerManager->move(DELTA * *PSENS); + PROTO::relativePointer->sendRelativeMotion((uint64_t)e.timeMs * 1000, DELTA, e.unaccel); + + g_pPointerManager->move(DELTA); mouseMoveUnified(e.timeMs); @@ -140,7 +141,6 @@ void CInputManager::sendMotionEventsToFocused() { void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { static auto PFOLLOWMOUSE = CConfigValue("input:follow_mouse"); static auto PMOUSEREFOCUS = CConfigValue("input:mouse_refocus"); - static auto PMOUSEDPMS = CConfigValue("misc:mouse_move_enables_dpms"); static auto PFOLLOWONDND = CConfigValue("misc:always_follow_on_dnd"); static auto PFLOATBEHAVIOR = CConfigValue("input:float_switch_override_focus"); static auto PMOUSEFOCUSMON = CConfigValue("misc:mouse_move_focuses_monitor"); @@ -158,16 +158,10 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { Vector2D surfacePos = Vector2D(-1337, -1337); PHLWINDOW pFoundWindow; PHLLS pFoundLayerSurface; - SSessionLockSurface* pSessionLock = nullptr; if (!g_pCompositor->m_bReadyToProcess || g_pCompositor->m_bIsShuttingDown || g_pCompositor->m_bUnsafeState) return; - if (!g_pCompositor->m_bDPMSStateON && *PMOUSEDPMS) { - // enable dpms - g_pKeybindManager->dpms("on"); - } - Vector2D mouseCoords = getMouseCoordsInternal(); const auto MOUSECOORDSFLOORED = mouseCoords.floor(); @@ -176,12 +170,9 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { EMIT_HOOK_EVENT_CANCELLABLE("mouseMove", MOUSECOORDSFLOORED); - if (time) - PROTO::idle->onActivity(); - m_vLastCursorPosFloored = MOUSECOORDSFLOORED; - const auto PMONITOR = g_pCompositor->getMonitorFromCursor(); + const auto PMONITOR = isLocked() && g_pCompositor->m_pLastMonitor ? g_pCompositor->m_pLastMonitor.get() : g_pCompositor->getMonitorFromCursor(); // this can happen if there are no displays hooked up to Hyprland if (PMONITOR == nullptr) @@ -195,23 +186,12 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { if (!PMONITOR->solitaryClient.lock() && g_pHyprRenderer->shouldRenderCursor() && g_pPointerManager->softwareLockedFor(PMONITOR->self.lock()) && !skipFrameSchedule) g_pCompositor->scheduleFrameForMonitor(PMONITOR, Aquamarine::IOutput::AQ_SCHEDULE_CURSOR_MOVE); - PHLWINDOW forcedFocus = m_pForcedFocus.lock(); - - if (!forcedFocus) - forcedFocus = g_pCompositor->getForceFocus(); - - if (forcedFocus) { - pFoundWindow = forcedFocus; - surfacePos = pFoundWindow->m_vRealPosition.value(); - foundSurface = pFoundWindow->m_pWLSurface->resource(); - } - // constraints if (!g_pSeatManager->mouse.expired() && isConstrained()) { const auto SURF = CWLSurface::fromResource(g_pCompositor->m_pLastFocus.lock()); - const auto CONSTRAINT = SURF->constraint(); + const auto CONSTRAINT = SURF ? SURF->constraint() : nullptr; - if (SURF && CONSTRAINT) { + if (CONSTRAINT) { if (CONSTRAINT->isLocked()) { const auto HINT = CONSTRAINT->logicPositionHint(); g_pCompositor->warpCursorTo(HINT, true); @@ -232,6 +212,33 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { Debug::log(ERR, "BUG THIS: Null SURF/CONSTRAINT in mouse refocus. Ignoring constraints. {:x} {:x}", (uintptr_t)SURF.get(), (uintptr_t)CONSTRAINT.get()); } + if (PMONITOR != g_pCompositor->m_pLastMonitor.get() && (*PMOUSEFOCUSMON || refocus) && m_pForcedFocus.expired()) + g_pCompositor->setActiveMonitor(PMONITOR); + + if (g_pSessionLockManager->isSessionLocked()) { + const auto PSESSIONLOCKSURFACE = g_pSessionLockManager->getSessionLockSurfaceForMonitor(PMONITOR->ID); + surfacePos = PMONITOR->vecPosition; + + foundSurface = PSESSIONLOCKSURFACE ? PSESSIONLOCKSURFACE->surface->surface() : nullptr; + g_pCompositor->focusSurface(foundSurface); + + const auto SURFACELOCAL = mouseCoords - surfacePos; + g_pSeatManager->setPointerFocus(foundSurface, SURFACELOCAL); + g_pSeatManager->sendPointerMotion(time, SURFACELOCAL); + return; + } + + PHLWINDOW forcedFocus = m_pForcedFocus.lock(); + + if (!forcedFocus) + forcedFocus = g_pCompositor->getForceFocus(); + + if (forcedFocus) { + pFoundWindow = forcedFocus; + surfacePos = pFoundWindow->m_vRealPosition.value(); + foundSurface = pFoundWindow->m_pWLSurface->resource(); + } + // if we are holding a pointer button, // and we're not dnd-ing, don't refocus. Keep focus on last surface. if (!PROTO::data->dndActive() && !m_lCurrentlyHeldButtons.empty() && g_pCompositor->m_pLastFocus && g_pSeatManager->state.pointerFocus && !m_bHardInput) { @@ -263,19 +270,6 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { g_pLayoutManager->getCurrentLayout()->onMouseMove(getMouseCoordsInternal()); - if (PMONITOR && PMONITOR != g_pCompositor->m_pLastMonitor.get() && (*PMOUSEFOCUSMON || refocus) && m_pForcedFocus.expired()) - g_pCompositor->setActiveMonitor(PMONITOR); - - if (g_pSessionLockManager->isSessionLocked()) { - pSessionLock = PMONITOR ? g_pSessionLockManager->getSessionLockSurfaceForMonitor(PMONITOR->ID) : nullptr; - - if (!pSessionLock) - return; - - foundSurface = pSessionLock->surface->surface(); - surfacePos = PMONITOR->vecPosition; - } - if (!foundSurface) foundSurface = g_pCompositor->vectorToLayerPopupSurface(mouseCoords, PMONITOR, &surfaceCoords, &pFoundLayerSurface); @@ -465,9 +459,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { restoreCursorIconToApp(); } - if (pSessionLock != nullptr) - g_pCompositor->focusSurface(foundSurface); - else if (pFoundWindow) { + if (pFoundWindow) { // change cursor icon if hovering over border if (*PRESIZEONBORDER && *PRESIZECURSORICON) { if (!pFoundWindow->isFullscreen() && !pFoundWindow->hasPopupAt(mouseCoords)) { @@ -513,6 +505,9 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { } } + if (g_pSeatManager->state.keyboardFocus == nullptr) + g_pCompositor->focusWindow(pFoundWindow, foundSurface); + m_bLastFocusOnLS = false; } else { if (*PRESIZEONBORDER && *PRESIZECURSORICON && m_eBorderIconDirection != BORDERICON_NONE) { @@ -536,8 +531,6 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { void CInputManager::onMouseButton(IPointer::SButtonEvent e) { EMIT_HOOK_EVENT_CANCELLABLE("mouseButton", e); - PROTO::idle->onActivity(); - m_tmrLastCursorMovement.reset(); if (e.state == WL_POINTER_BUTTON_STATE_PRESSED) { @@ -680,7 +673,7 @@ void CInputManager::processMouseDownNormal(const IPointer::SButtonEvent& e) { // clicking on border triggers resize // TODO detect click on LS properly - if (*PRESIZEONBORDER && !m_bLastFocusOnLS && e.state == WL_POINTER_BUTTON_STATE_PRESSED && (!w || w->m_iX11Type != 2)) { + if (*PRESIZEONBORDER && !m_bLastFocusOnLS && e.state == WL_POINTER_BUTTON_STATE_PRESSED && (!w || !w->isX11OverrideRedirect())) { if (w && !w->isFullscreen()) { const CBox real = {w->m_vRealPosition.value().x, w->m_vRealPosition.value().y, w->m_vRealSize.value().x, w->m_vRealSize.value().y}; const CBox grab = {real.x - BORDER_GRAB_AREA, real.y - BORDER_GRAB_AREA, real.width + 2 * BORDER_GRAB_AREA, real.height + 2 * BORDER_GRAB_AREA}; @@ -772,8 +765,6 @@ void CInputManager::onMouseWheel(IPointer::SAxisEvent e) { bool passEvent = g_pKeybindManager->onAxisEvent(e); - PROTO::idle->onActivity(); - if (!passEvent) return; @@ -863,6 +854,8 @@ void CInputManager::newVirtualKeyboard(SP keyboard) } void CInputManager::setupKeyboard(SP keeb) { + static auto PDPMS = CConfigValue("misc:key_press_enables_dpms"); + m_vHIDs.push_back(keeb); try { @@ -888,6 +881,12 @@ void CInputManager::setupKeyboard(SP keeb) { auto PKEEB = ((IKeyboard*)owner)->self.lock(); onKeyboardKey(data, PKEEB); + + if (PKEEB->enabled) + PROTO::idle->onActivity(); + + if (PKEEB->enabled && *PDPMS && !g_pCompositor->m_bDPMSStateON) + g_pKeybindManager->dpms("on"); }, keeb.get()); @@ -896,6 +895,12 @@ void CInputManager::setupKeyboard(SP keeb) { auto PKEEB = ((IKeyboard*)owner)->self.lock(); onKeyboardMod(PKEEB); + + if (PKEEB->enabled) + PROTO::idle->onActivity(); + + if (PKEEB->enabled && *PDPMS && !g_pCompositor->m_bDPMSStateON) + g_pKeybindManager->dpms("on"); }, keeb.get()); @@ -920,10 +925,12 @@ void CInputManager::setupKeyboard(SP keeb) { applyConfigToKeyboard(keeb); g_pSeatManager->setKeyboard(keeb); + + keeb->updateLEDs(); } void CInputManager::setKeyboardLayout() { - for (auto& k : m_vKeyboards) + for (auto const& k : m_vKeyboards) applyConfigToKeyboard(k); g_pKeybindManager->updateXKBTranslationState(); @@ -1038,7 +1045,7 @@ void CInputManager::setupMouse(SP mauz) { } void CInputManager::setPointerConfigs() { - for (auto& m : m_vPointers) { + for (auto const& m : m_vPointers) { auto devname = m->hlName; const auto HASCONFIG = g_pConfigManager->deviceConfigExists(devname); @@ -1112,8 +1119,9 @@ void CInputManager::setPointerConfigs() { libinput_device_config_tap_set_drag_lock_enabled(LIBINPUTDEV, LIBINPUT_CONFIG_DRAG_LOCK_ENABLED); if (libinput_device_config_tap_get_finger_count(LIBINPUTDEV)) // this is for tapping (like on a laptop) - if (g_pConfigManager->getDeviceInt(devname, "tap-to-click", "input:touchpad:tap-to-click") == 1) - libinput_device_config_tap_set_enabled(LIBINPUTDEV, LIBINPUT_CONFIG_TAP_ENABLED); + libinput_device_config_tap_set_enabled(LIBINPUTDEV, + g_pConfigManager->getDeviceInt(devname, "tap-to-click", "input:touchpad:tap-to-click") == 1 ? LIBINPUT_CONFIG_TAP_ENABLED : + LIBINPUT_CONFIG_TAP_DISABLED); if (libinput_device_config_scroll_has_natural_scroll(LIBINPUTDEV)) { @@ -1201,7 +1209,7 @@ void CInputManager::destroyKeyboard(SP pKeyboard) { if (m_vKeyboards.size() > 0) { bool found = false; - for (auto& k : m_vKeyboards | std::views::reverse) { + for (auto const& k : m_vKeyboards | std::views::reverse) { if (!k) continue; @@ -1272,7 +1280,7 @@ void CInputManager::updateKeyboardsLeds(SP pKeyboard) { if (!leds.has_value()) return; - for (auto& k : m_vKeyboards) { + for (auto const& k : m_vKeyboards) { k->updateLEDs(leds.value()); } } @@ -1286,18 +1294,10 @@ void CInputManager::onKeyboardKey(std::any event, SP pKeyboard) { const auto EMAP = std::unordered_map{{"keyboard", pKeyboard}, {"event", event}}; EMIT_HOOK_EVENT_CANCELLABLE("keyPress", EMAP); - static auto PDPMS = CConfigValue("misc:key_press_enables_dpms"); - if (*PDPMS && !g_pCompositor->m_bDPMSStateON) { - // enable dpms - g_pKeybindManager->dpms("on"); - } - bool passEvent = DISALLOWACTION || g_pKeybindManager->onKeyEvent(event, pKeyboard); auto e = std::any_cast(event); - PROTO::idle->onActivity(); - if (passEvent) { const auto IME = m_sIMERelay.m_pIME.lock(); @@ -1407,7 +1407,7 @@ void CInputManager::unconstrainMouse() { if (g_pSeatManager->mouse.expired()) return; - for (auto& c : m_vConstraints) { + for (auto const& c : m_vConstraints) { const auto C = c.lock(); if (!C) @@ -1421,7 +1421,7 @@ void CInputManager::unconstrainMouse() { } bool CInputManager::isConstrained() { - for (auto& c : m_vConstraints) { + for (auto const& c : m_vConstraints) { const auto C = c.lock(); if (!C) @@ -1436,10 +1436,20 @@ bool CInputManager::isConstrained() { return false; } +bool CInputManager::isLocked() { + if (!isConstrained()) + return false; + + const auto SURF = CWLSurface::fromResource(g_pCompositor->m_pLastFocus.lock()); + const auto CONSTRAINT = SURF ? SURF->constraint() : nullptr; + + return CONSTRAINT && CONSTRAINT->isLocked(); +} + void CInputManager::updateCapabilities() { uint32_t caps = 0; - for (auto& h : m_vHIDs) { + for (auto const& h : m_vHIDs) { if (h.expired()) continue; @@ -1454,7 +1464,7 @@ uint32_t CInputManager::accumulateModsFromAllKBs() { uint32_t finalMask = 0; - for (auto& kb : m_vKeyboards) { + for (auto const& kb : m_vKeyboards) { if (kb->isVirtual() && shouldIgnoreVirtualKeyboard(kb)) continue; @@ -1469,7 +1479,7 @@ uint32_t CInputManager::accumulateModsFromAllKBs() { void CInputManager::disableAllKeyboards(bool virt) { - for (auto& k : m_vKeyboards) { + for (auto const& k : m_vKeyboards) { if (k->isVirtual() != virt) continue; @@ -1545,13 +1555,13 @@ void CInputManager::setTouchDeviceConfigs(SP dev) { return; } - for (auto& m : m_vTouches) { + for (auto const& m : m_vTouches) { setConfig(m); } } void CInputManager::setTabletConfigs() { - for (auto& t : m_vTablets) { + for (auto const& t : m_vTablets) { if (t->aq()->getLibinputHandle()) { const auto NAME = t->hlName; const auto LIBINPUTDEV = t->aq()->getLibinputHandle(); @@ -1678,7 +1688,7 @@ void CInputManager::releaseAllMouseButtons() { if (PROTO::data->dndActive()) return; - for (auto& mb : buttonsCopy) { + for (auto const& mb : buttonsCopy) { g_pSeatManager->sendPointerButton(0, mb, WL_POINTER_BUTTON_STATE_RELEASED); } @@ -1693,7 +1703,7 @@ void CInputManager::setCursorIconOnBorder(PHLWINDOW w) { } // ignore X11 OR windows, they shouldn't be touched - if (w->m_bIsX11 && w->m_iX11Type == 2) + if (w->m_bIsX11 && w->isX11OverrideRedirect()) return; static auto PEXTENDBORDERGRAB = CConfigValue("general:extend_border_grab_area"); @@ -1716,7 +1726,7 @@ void CInputManager::setCursorIconOnBorder(PHLWINDOW w) { bool onDeco = false; - for (auto& wd : w->m_dWindowDecorations) { + for (auto const& wd : w->m_dWindowDecorations) { if (!(wd->getDecorationFlags() & DECORATION_ALLOWS_MOUSE_INPUT)) continue; diff --git a/src/managers/input/InputManager.hpp b/src/managers/input/InputManager.hpp index ebf00b2d..d5634796 100644 --- a/src/managers/input/InputManager.hpp +++ b/src/managers/input/InputManager.hpp @@ -108,6 +108,7 @@ class CInputManager { void unconstrainMouse(); bool isConstrained(); + bool isLocked(); Vector2D getMouseCoordsInternal(); void refocus(); diff --git a/src/managers/input/InputMethodPopup.cpp b/src/managers/input/InputMethodPopup.cpp index a8757030..cf48f2a5 100644 --- a/src/managers/input/InputMethodPopup.cpp +++ b/src/managers/input/InputMethodPopup.cpp @@ -100,14 +100,14 @@ void CInputPopup::updateBox() { cursorBoxParent = {0, 0, (int)parentBox.w, (int)parentBox.h}; } - Vector2D currentPopupSize = surface->getViewporterCorrectedSize(); + Vector2D currentPopupSize = surface->getViewporterCorrectedSize() / surface->resource()->current.scale; CMonitor* pMonitor = g_pCompositor->getMonitorFromVector(parentBox.middle()); Vector2D popupOffset(0, 0); if (parentBox.y + cursorBoxParent.y + cursorBoxParent.height + currentPopupSize.y > pMonitor->vecPosition.y + pMonitor->vecSize.y) - popupOffset.y = -currentPopupSize.y; + popupOffset.y -= currentPopupSize.y; else popupOffset.y = cursorBoxParent.height; diff --git a/src/managers/input/InputMethodPopup.hpp b/src/managers/input/InputMethodPopup.hpp index f6e5c8be..53c11cff 100644 --- a/src/managers/input/InputMethodPopup.hpp +++ b/src/managers/input/InputMethodPopup.hpp @@ -1,6 +1,5 @@ #pragma once -#include "../../helpers/WLListener.hpp" #include "../../desktop/WLSurface.hpp" #include "../../macros.hpp" #include "../../helpers/math/Math.hpp" @@ -33,7 +32,7 @@ class CInputPopup { WP popup; SP surface; CBox lastBoxLocal; - uint64_t lastMonitor = -1; + MONITORID lastMonitor = MONITOR_INVALID; struct { CHyprSignalListener map; diff --git a/src/managers/input/InputMethodRelay.cpp b/src/managers/input/InputMethodRelay.cpp index 7cfd54b4..5ef7f331 100644 --- a/src/managers/input/InputMethodRelay.cpp +++ b/src/managers/input/InputMethodRelay.cpp @@ -57,7 +57,7 @@ void CInputMethodRelay::onNewIME(SP pIME) { if (!g_pCompositor->m_pLastFocus) return; - for (auto& ti : m_vTextInputs) { + for (auto const& ti : m_vTextInputs) { if (ti->client() != g_pCompositor->m_pLastFocus->client()) continue; @@ -80,7 +80,7 @@ CTextInput* CInputMethodRelay::getFocusedTextInput() { if (!g_pCompositor->m_pLastFocus) return nullptr; - for (auto& ti : m_vTextInputs) { + for (auto const& ti : m_vTextInputs) { if (ti->focusedSurface() == g_pCompositor->m_pLastFocus) return ti.get(); } @@ -101,25 +101,27 @@ void CInputMethodRelay::removeTextInput(CTextInput* pInput) { } void CInputMethodRelay::updateAllPopups() { - for (auto& p : m_vIMEPopups) { + for (auto const& p : m_vIMEPopups) { p->onCommit(); } } -void CInputMethodRelay::activateIME(CTextInput* pInput) { +void CInputMethodRelay::activateIME(CTextInput* pInput, bool shouldCommit) { if (m_pIME.expired()) return; m_pIME->activate(); - commitIMEState(pInput); + if (shouldCommit) + commitIMEState(pInput); } -void CInputMethodRelay::deactivateIME(CTextInput* pInput) { +void CInputMethodRelay::deactivateIME(CTextInput* pInput, bool shouldCommit) { if (m_pIME.expired()) return; m_pIME->deactivate(); - commitIMEState(pInput); + if (shouldCommit) + commitIMEState(pInput); } void CInputMethodRelay::commitIMEState(CTextInput* pInput) { @@ -138,7 +140,7 @@ void CInputMethodRelay::onKeyboardFocus(SP pSurface) { m_pLastKbFocus = pSurface; - for (auto& ti : m_vTextInputs) { + for (auto const& ti : m_vTextInputs) { if (!ti->focusedSurface()) continue; @@ -148,7 +150,7 @@ void CInputMethodRelay::onKeyboardFocus(SP pSurface) { if (!pSurface) return; - for (auto& ti : m_vTextInputs) { + for (auto const& ti : m_vTextInputs) { if (!ti->isV3()) continue; @@ -160,7 +162,7 @@ void CInputMethodRelay::onKeyboardFocus(SP pSurface) { } CInputPopup* CInputMethodRelay::popupFromCoords(const Vector2D& point) { - for (auto& p : m_vIMEPopups) { + for (auto const& p : m_vIMEPopups) { if (p->isVecInPopup(point)) return p.get(); } @@ -169,7 +171,7 @@ CInputPopup* CInputMethodRelay::popupFromCoords(const Vector2D& point) { } CInputPopup* CInputMethodRelay::popupFromSurface(const SP surface) { - for (auto& p : m_vIMEPopups) { + for (auto const& p : m_vIMEPopups) { if (p->getSurface() == surface) return p.get(); } diff --git a/src/managers/input/InputMethodRelay.hpp b/src/managers/input/InputMethodRelay.hpp index 5ecc11a2..3d706563 100644 --- a/src/managers/input/InputMethodRelay.hpp +++ b/src/managers/input/InputMethodRelay.hpp @@ -21,8 +21,8 @@ class CInputMethodRelay { void onNewTextInput(WP tiv3); void onNewTextInput(WP pTIV1); - void activateIME(CTextInput* pInput); - void deactivateIME(CTextInput* pInput); + void activateIME(CTextInput* pInput, bool shouldCommit = true); + void deactivateIME(CTextInput* pInput, bool shouldCommit = true); void commitIMEState(CTextInput* pInput); void removeTextInput(CTextInput* pInput); diff --git a/src/managers/input/Swipe.cpp b/src/managers/input/Swipe.cpp index c0e6c4f0..aa1d3274 100644 --- a/src/managers/input/Swipe.cpp +++ b/src/managers/input/Swipe.cpp @@ -14,7 +14,7 @@ void CInputManager::onSwipeBegin(IPointer::SSwipeBeginEvent e) { return; int onMonitor = 0; - for (auto& w : g_pCompositor->m_vWorkspaces) { + for (auto const& w : g_pCompositor->m_vWorkspaces) { if (w->m_iMonitorID == g_pCompositor->m_pLastMonitor->ID && !g_pCompositor->isWorkspaceSpecial(w->m_iID)) { onMonitor++; } @@ -38,7 +38,7 @@ void CInputManager::beginWorkspaceSwipe() { m_sActiveSwipe.speedPoints = 0; if (PWORKSPACE->m_bHasFullscreenWindow) { - for (auto& ls : g_pCompositor->m_pLastMonitor->m_aLayerSurfaceLayers[2]) { + for (auto const& ls : g_pCompositor->m_pLastMonitor->m_aLayerSurfaceLayers[2]) { ls->alpha = 1.f; } } @@ -77,7 +77,7 @@ void CInputManager::endWorkspaceSwipe() { // left of where we started. Instead, it's one more than the greatest // workspace ID that currently exists. if (workspaceIDRight <= m_sActiveSwipe.pWorkspaceBegin->m_iID && *PSWIPENEW) { - int maxWorkspace = 0; + WORKSPACEID maxWorkspace = 0; for (const auto& ws : g_pCompositor->m_vWorkspaces) { maxWorkspace = std::max(maxWorkspace, ws->m_iID); } @@ -193,7 +193,7 @@ void CInputManager::endWorkspaceSwipe() { g_pInputManager->refocus(); // apply alpha - for (auto& ls : g_pCompositor->m_pLastMonitor->m_aLayerSurfaceLayers[2]) { + for (auto const& ls : g_pCompositor->m_pLastMonitor->m_aLayerSurfaceLayers[2]) { ls->alpha = pSwitchedTo->m_bHasFullscreenWindow && pSwitchedTo->m_efFullscreenMode == FSMODE_FULLSCREEN ? 0.f : 1.f; } } diff --git a/src/managers/input/Tablets.cpp b/src/managers/input/Tablets.cpp index 5e50e851..9f80726e 100644 --- a/src/managers/input/Tablets.cpp +++ b/src/managers/input/Tablets.cpp @@ -1,6 +1,5 @@ #include "InputManager.hpp" #include "../../Compositor.hpp" -#include "../../protocols/IdleNotify.hpp" #include "../../protocols/Tablet.hpp" #include "../../devices/Tablet.hpp" #include "../../managers/PointerManager.hpp" @@ -14,7 +13,7 @@ static void unfocusTool(SP tool) { tool->setSurface(nullptr); if (tool->isDown) PROTO::tablet->up(tool); - for (auto& b : tool->buttonsDown) { + for (auto const& b : tool->buttonsDown) { PROTO::tablet->buttonTool(tool, b, false); } PROTO::tablet->proximityOut(tool); @@ -31,7 +30,7 @@ static void focusTool(SP tool, SP tablet, SPproximityIn(tool, tablet, surf); if (tool->isDown) PROTO::tablet->down(tool); - for (auto& b : tool->buttonsDown) { + for (auto const& b : tool->buttonsDown) { PROTO::tablet->buttonTool(tool, b, true); } } @@ -155,8 +154,6 @@ void CInputManager::onTabletAxis(CTablet::SAxisEvent e) { if (e.updatedAxes & (CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_TILT_X | CTablet::eTabletToolAxes::HID_TABLET_TOOL_AXIS_TILT_Y)) PROTO::tablet->tilt(PTOOL, PTOOL->tilt); - - PROTO::idle->onActivity(); } void CInputManager::onTabletTip(CTablet::STipEvent e) { @@ -171,8 +168,6 @@ void CInputManager::onTabletTip(CTablet::STipEvent e) { PROTO::tablet->up(PTOOL); PTOOL->isDown = e.in; - - PROTO::idle->onActivity(); } void CInputManager::onTabletButton(CTablet::SButtonEvent e) { @@ -184,8 +179,6 @@ void CInputManager::onTabletButton(CTablet::SButtonEvent e) { PTOOL->buttonsDown.push_back(e.button); else std::erase(PTOOL->buttonsDown, e.button); - - PROTO::idle->onActivity(); } void CInputManager::onTabletProximity(CTablet::SProximityEvent e) { @@ -201,8 +194,6 @@ void CInputManager::onTabletProximity(CTablet::SProximityEvent e) { simulateMouseMovement(); refocusTablet(PTAB, PTOOL); } - - PROTO::idle->onActivity(); } void CInputManager::newTablet(SP pDevice) { @@ -229,7 +220,7 @@ void CInputManager::newTablet(SP pDevice) { SP CInputManager::ensureTabletToolPresent(SP pTool) { - for (auto& t : m_vTabletTools) { + for (auto const& t : m_vTabletTools) { if (t->aq() == pTool) return t; } diff --git a/src/managers/input/TextInput.cpp b/src/managers/input/TextInput.cpp index 5bff9fed..f7a6a350 100644 --- a/src/managers/input/TextInput.cpp +++ b/src/managers/input/TextInput.cpp @@ -22,12 +22,17 @@ void CTextInput::initCallbacks() { listeners.enable = INPUT->events.enable.registerListener([this](std::any p) { onEnabled(); }); listeners.disable = INPUT->events.disable.registerListener([this](std::any p) { onDisabled(); }); listeners.commit = INPUT->events.onCommit.registerListener([this](std::any p) { onCommit(); }); + listeners.reset = INPUT->events.reset.registerListener([this](std::any p) { onReset(); }); listeners.destroy = INPUT->events.destroy.registerListener([this](std::any p) { - const auto INPUT = pV3Input.lock(); - if (INPUT && INPUT->current.enabled && focusedSurface()) - g_pInputManager->m_sIMERelay.deactivateIME(this); + listeners.surfaceUnmap.reset(); + listeners.surfaceDestroy.reset(); g_pInputManager->m_sIMERelay.removeTextInput(this); + if (!g_pInputManager->m_sIMERelay.getFocusedTextInput()) + g_pInputManager->m_sIMERelay.deactivateIME(this); }); + + if (!g_pCompositor->m_pLastFocus.expired() && g_pCompositor->m_pLastFocus->client() == INPUT->client()) + enter(g_pCompositor->m_pLastFocus.lock()); } else { const auto INPUT = pV1Input.lock(); @@ -37,10 +42,13 @@ void CTextInput::initCallbacks() { }); listeners.disable = INPUT->events.disable.registerListener([this](std::any p) { onDisabled(); }); listeners.commit = INPUT->events.onCommit.registerListener([this](std::any p) { onCommit(); }); + listeners.reset = INPUT->events.reset.registerListener([this](std::any p) { onReset(); }); listeners.destroy = INPUT->events.destroy.registerListener([this](std::any p) { listeners.surfaceUnmap.reset(); listeners.surfaceDestroy.reset(); g_pInputManager->m_sIMERelay.removeTextInput(this); + if (!g_pInputManager->m_sIMERelay.getFocusedTextInput()) + g_pInputManager->m_sIMERelay.deactivateIME(this); }); } } @@ -71,25 +79,44 @@ void CTextInput::onDisabled() { return; } - if (!focusedSurface()) - return; - if (!isV3()) leave(); listeners.surfaceUnmap.reset(); listeners.surfaceDestroy.reset(); + if (!focusedSurface()) + return; + + const auto PFOCUSEDTI = g_pInputManager->m_sIMERelay.getFocusedTextInput(); + if (!PFOCUSEDTI || PFOCUSEDTI != this) + return; + g_pInputManager->m_sIMERelay.deactivateIME(this); } +void CTextInput::onReset() { + if (g_pInputManager->m_sIMERelay.m_pIME.expired()) + return; + + if (!focusedSurface()) + return; + + const auto PFOCUSEDTI = g_pInputManager->m_sIMERelay.getFocusedTextInput(); + if (!PFOCUSEDTI || PFOCUSEDTI != this) + return; + + g_pInputManager->m_sIMERelay.deactivateIME(this, false); + g_pInputManager->m_sIMERelay.activateIME(this); +} + void CTextInput::onCommit() { if (g_pInputManager->m_sIMERelay.m_pIME.expired()) { // Debug::log(WARN, "Committing TextInput on no IME!"); return; } - if (!(isV3() ? pV3Input->current.enabled : pV1Input->active)) { + if (!(isV3() ? pV3Input->current.enabled.value : pV1Input->active)) { Debug::log(WARN, "Disabled TextInput commit?"); return; } @@ -103,12 +130,12 @@ void CTextInput::setFocusedSurface(SP pSurface) { pFocusedSurface = pSurface; - listeners.surfaceUnmap.reset(); - listeners.surfaceDestroy.reset(); - if (!pSurface) return; + listeners.surfaceUnmap.reset(); + listeners.surfaceDestroy.reset(); + listeners.surfaceUnmap = pSurface->events.unmap.registerListener([this](std::any d) { Debug::log(LOG, "Unmap TI owner1"); @@ -117,6 +144,16 @@ void CTextInput::setFocusedSurface(SP pSurface) { pFocusedSurface.reset(); listeners.surfaceUnmap.reset(); listeners.surfaceDestroy.reset(); + + if (isV3() && !pV3Input.expired() && pV3Input->current.enabled.value) { + pV3Input->pending.enabled.value = false; + pV3Input->pending.enabled.isDisablePending = false; + pV3Input->pending.enabled.isEnablePending = false; + pV3Input->current.enabled.value = false; + } + + if (!g_pInputManager->m_sIMERelay.getFocusedTextInput()) + g_pInputManager->m_sIMERelay.deactivateIME(this); }); listeners.surfaceDestroy = pSurface->events.destroy.registerListener([this](std::any d) { @@ -127,6 +164,16 @@ void CTextInput::setFocusedSurface(SP pSurface) { pFocusedSurface.reset(); listeners.surfaceUnmap.reset(); listeners.surfaceDestroy.reset(); + + if (isV3() && !pV3Input.expired() && pV3Input->current.enabled.value) { + pV3Input->pending.enabled.value = false; + pV3Input->pending.enabled.isDisablePending = false; + pV3Input->pending.enabled.isEnablePending = false; + pV3Input->current.enabled.value = false; + } + + if (!g_pInputManager->m_sIMERelay.getFocusedTextInput()) + g_pInputManager->m_sIMERelay.deactivateIME(this); }); } @@ -141,10 +188,8 @@ void CTextInput::enter(SP pSurface) { if (pSurface == focusedSurface()) return; - if (focusedSurface()) { + if (focusedSurface()) leave(); - setFocusedSurface(nullptr); - } enterLocks++; if (enterLocks != 1) { @@ -172,11 +217,10 @@ void CTextInput::leave() { enterLocks = 0; } - if (isV3() && focusedSurface()) + if (isV3()) pV3Input->leave(focusedSurface()); - else if (focusedSurface() && pV1Input) { + else pV1Input->leave(); - } setFocusedSurface(nullptr); @@ -192,7 +236,7 @@ wl_client* CTextInput::client() { } void CTextInput::commitStateToIME(SP ime) { - if (isV3()) { + if (isV3() && !pV3Input.expired()) { const auto INPUT = pV3Input.lock(); if (INPUT->current.surrounding.updated) @@ -202,7 +246,7 @@ void CTextInput::commitStateToIME(SP ime) { if (INPUT->current.contentType.updated) ime->textContentType(INPUT->current.contentType.hint, INPUT->current.contentType.purpose); - } else { + } else if (!pV1Input.expired()) { const auto INPUT = pV1Input.lock(); if (INPUT->pendingSurrounding.isPending) @@ -241,7 +285,7 @@ void CTextInput::updateIMEState(SP ime) { INPUT->preeditStyling(0, std::string(ime->current.preeditString.string).length(), ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_HIGHLIGHT); INPUT->preeditString(pV1Input->serial, ime->current.preeditString.string.c_str(), ""); } else { - INPUT->preeditCursor(ime->current.preeditString.begin); + INPUT->preeditCursor(0); INPUT->preeditStyling(0, 0, ZWP_TEXT_INPUT_V1_PREEDIT_STYLE_HIGHLIGHT); INPUT->preeditString(pV1Input->serial, "", ""); } diff --git a/src/managers/input/TextInput.hpp b/src/managers/input/TextInput.hpp index 0f69866e..3cf07006 100644 --- a/src/managers/input/TextInput.hpp +++ b/src/managers/input/TextInput.hpp @@ -1,6 +1,5 @@ #pragma once -#include "../../helpers/WLListener.hpp" #include "../../macros.hpp" #include "../../helpers/math/Math.hpp" #include "../../helpers/signal/Signal.hpp" @@ -29,6 +28,7 @@ class CTextInput { void onEnabled(SP surfV1 = nullptr); void onDisabled(); void onCommit(); + void onReset(); bool hasCursorRectangle(); CBox cursorBox(); @@ -47,6 +47,7 @@ class CTextInput { struct { CHyprSignalListener enable; CHyprSignalListener disable; + CHyprSignalListener reset; CHyprSignalListener commit; CHyprSignalListener destroy; CHyprSignalListener surfaceUnmap; diff --git a/src/managers/input/Touch.cpp b/src/managers/input/Touch.cpp index 40af4b1e..0333648a 100644 --- a/src/managers/input/Touch.cpp +++ b/src/managers/input/Touch.cpp @@ -1,7 +1,6 @@ #include "InputManager.hpp" #include "../../Compositor.hpp" #include "../../config/ConfigValue.hpp" -#include "../../protocols/IdleNotify.hpp" #include "../../devices/ITouch.hpp" #include "../SeatManager.hpp" @@ -40,7 +39,6 @@ void CInputManager::onTouchDown(ITouch::SDownEvent e) { const auto PWORKSPACE = PMONITOR->activeWorkspace; const bool VERTANIMS = PWORKSPACE->m_vRenderOffset.getConfig()->pValues->internalStyle == "slidevert" || PWORKSPACE->m_vRenderOffset.getConfig()->pValues->internalStyle.starts_with("slidefadevert"); - // TODO: support no_gaps_when_only? const double TARGETLEFT = ((VERTANIMS ? gapsOut.top : gapsOut.left) + *PBORDERSIZE) / (VERTANIMS ? PMONITOR->vecSize.y : PMONITOR->vecSize.x); const double TARGETRIGHT = 1 - (((VERTANIMS ? gapsOut.bottom : gapsOut.right) + *PBORDERSIZE) / (VERTANIMS ? PMONITOR->vecSize.y : PMONITOR->vecSize.x)); const double POSITION = (VERTANIMS ? e.pos.y : e.pos.x); @@ -78,8 +76,6 @@ void CInputManager::onTouchDown(ITouch::SDownEvent e) { return; // oops, nothing found. g_pSeatManager->sendTouchDown(m_sTouchData.touchFocusSurface.lock(), e.timeMs, e.touchID, local); - - PROTO::idle->onActivity(); } void CInputManager::onTouchUp(ITouch::SUpEvent e) { diff --git a/src/meson.build b/src/meson.build index 098d8298..2dbe2f44 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,12 +1,14 @@ globber = run_command('sh', '-c', 'find . -name "*.cpp" | sort', check: true) src = globber.stdout().strip().split('\n') -executable('Hyprland', src, +executable( + 'Hyprland', + src, link_args: '-rdynamic', cpp_pch: 'pch/pch.hpp', dependencies: [ server_protos, - dependency('aquamarine'), + aquamarine, dependency('gbm'), dependency('xcursor'), dependency('wayland-server'), @@ -14,7 +16,7 @@ executable('Hyprland', src, dependency('cairo'), dependency('hyprcursor', version: '>=0.1.7'), dependency('hyprlang', version: '>= 0.3.2'), - dependency('hyprutils', version: '>= 0.2.1'), + dependency('hyprutils', version: '>= 0.2.3'), dependency('libdrm'), dependency('egl'), dependency('xkbcommon'), @@ -28,7 +30,13 @@ executable('Hyprland', src, xcb_xfixes_dep, backtrace_dep, epoll_dep, - udis86, + gio_dep, + tracy, + + # Try to find canihavesomecoffee's udis86 using pkgconfig + # vmt/udis86 does not provide a .pc file and won't be detected this way + # Falls back to using the subproject through udis86.wrap + dependency('udis86'), dependency('pixman-1'), dependency('gl', 'opengl'), @@ -37,5 +45,5 @@ executable('Hyprland', src, dependency('pangocairo'), dependency('uuid'), ], - install : true + install: true, ) diff --git a/src/plugins/HookSystem.cpp b/src/plugins/HookSystem.cpp index 9118456b..745b2593 100644 --- a/src/plugins/HookSystem.cpp +++ b/src/plugins/HookSystem.cpp @@ -81,7 +81,7 @@ CFunctionHook::SAssembly CFunctionHook::fixInstructionProbeRIPCalls(const SInstr std::vector finalBytes; finalBytes.resize(probe.len); - for (auto& len : probe.insSizes) { + for (auto const& len : probe.insSizes) { // copy original bytes to our finalBytes for (size_t i = 0; i < len; ++i) { diff --git a/src/plugins/PluginAPI.cpp b/src/plugins/PluginAPI.cpp index 098e3f12..ef3ae06a 100644 --- a/src/plugins/PluginAPI.cpp +++ b/src/plugins/PluginAPI.cpp @@ -124,8 +124,8 @@ APICALL bool HyprlandAPI::removeWindowDecoration(HANDLE handle, IHyprWindowDecor if (!PLUGIN) return false; - for (auto& w : g_pCompositor->m_vWindows) { - for (auto& d : w->m_dWindowDecorations) { + for (auto const& w : g_pCompositor->m_vWindows) { + for (auto const& d : w->m_dWindowDecorations) { if (d.get() == pDecoration) { w->removeWindowDeco(pDecoration); return true; @@ -194,7 +194,10 @@ APICALL bool HyprlandAPI::addDispatcher(HANDLE handle, const std::string& name, PLUGIN->registeredDispatchers.push_back(name); - g_pKeybindManager->m_mDispatchers[name] = handler; + g_pKeybindManager->m_mDispatchers[name] = [handler](std::string arg1) -> SDispatchResult { + handler(arg1); + return {}; + }; return true; } @@ -378,4 +381,4 @@ APICALL bool HyprlandAPI::unregisterHyprCtlCommand(HANDLE handle, SPunregisterCommand(cmd); return true; -} \ No newline at end of file +} diff --git a/src/plugins/PluginSystem.cpp b/src/plugins/PluginSystem.cpp index 122faafd..0f1f97a2 100644 --- a/src/plugins/PluginSystem.cpp +++ b/src/plugins/PluginSystem.cpp @@ -98,27 +98,27 @@ void CPluginSystem::unloadPlugin(const CPlugin* plugin, bool eject) { exitFunc(); } - for (auto& [k, v] : plugin->registeredCallbacks) { + for (auto const& [k, v] : plugin->registeredCallbacks) { if (const auto SHP = v.lock()) g_pHookSystem->unhook(SHP); } const auto ls = plugin->registeredLayouts; - for (auto& l : ls) + for (auto const& l : ls) g_pLayoutManager->removeLayout(l); g_pFunctionHookSystem->removeAllHooksFrom(plugin->m_pHandle); const auto rd = plugin->registeredDecorations; - for (auto& d : rd) + for (auto const& d : rd) HyprlandAPI::removeWindowDecoration(plugin->m_pHandle, d); const auto rdi = plugin->registeredDispatchers; - for (auto& d : rdi) + for (auto const& d : rdi) HyprlandAPI::removeDispatcher(plugin->m_pHandle, d); const auto rhc = plugin->registeredHyprctlCommands; - for (auto& c : rhc) + for (auto const& c : rhc) HyprlandAPI::unregisterHyprCtlCommand(plugin->m_pHandle, c); g_pConfigManager->removePluginConfig(plugin->m_pHandle); @@ -139,7 +139,7 @@ void CPluginSystem::unloadPlugin(const CPlugin* plugin, bool eject) { } void CPluginSystem::unloadAllPlugins() { - for (auto& p : m_vLoadedPlugins | std::views::reverse) + for (auto const& p : m_vLoadedPlugins | std::views::reverse) unloadPlugin(p.get(), false); // Unload remaining plugins gracefully } @@ -147,7 +147,7 @@ std::vector CPluginSystem::updateConfigPlugins(const std::vector failures; // unload all plugins that are no longer present - for (auto& p : m_vLoadedPlugins | std::views::reverse) { + for (auto const& p : m_vLoadedPlugins | std::views::reverse) { if (p->m_bLoadedWithConfig && std::find(plugins.begin(), plugins.end(), p->path) == plugins.end()) { Debug::log(LOG, "Unloading plugin {} which is no longer present in config", p->path); unloadPlugin(p.get(), false); @@ -156,7 +156,7 @@ std::vector CPluginSystem::updateConfigPlugins(const std::vectorpath == path; }) == m_vLoadedPlugins.end()) { Debug::log(LOG, "Loading plugin {} which is now present in config", path); const auto plugin = loadPlugin(path); @@ -173,7 +173,7 @@ std::vector CPluginSystem::updateConfigPlugins(const std::vectorpath == path) return p.get(); } @@ -182,7 +182,7 @@ CPlugin* CPluginSystem::getPluginByPath(const std::string& path) { } CPlugin* CPluginSystem::getPluginByHandle(HANDLE handle) { - for (auto& p : m_vLoadedPlugins) { + for (auto const& p : m_vLoadedPlugins) { if (p->m_pHandle == handle) return p.get(); } diff --git a/src/protocols/AlphaModifier.cpp b/src/protocols/AlphaModifier.cpp index 38b8c800..13597fa9 100644 --- a/src/protocols/AlphaModifier.cpp +++ b/src/protocols/AlphaModifier.cpp @@ -4,8 +4,6 @@ #include "../render/Renderer.hpp" #include "core/Compositor.hpp" -#define LOGM PROTO::alphaModifier->protoLog - CAlphaModifier::CAlphaModifier(SP resource_, SP surface_) : resource(resource_), pSurface(surface_) { if (!resource->resource()) return; diff --git a/src/protocols/CTMControl.cpp b/src/protocols/CTMControl.cpp new file mode 100644 index 00000000..f2a54c6f --- /dev/null +++ b/src/protocols/CTMControl.cpp @@ -0,0 +1,86 @@ +#include "CTMControl.hpp" +#include "../Compositor.hpp" +#include "core/Output.hpp" + +CHyprlandCTMControlResource::CHyprlandCTMControlResource(SP resource_) : resource(resource_) { + if (!good()) + return; + + resource->setDestroy([this](CHyprlandCtmControlManagerV1* pMgr) { PROTO::ctm->destroyResource(this); }); + resource->setOnDestroy([this](CHyprlandCtmControlManagerV1* pMgr) { PROTO::ctm->destroyResource(this); }); + + resource->setSetCtmForOutput([this](CHyprlandCtmControlManagerV1* r, wl_resource* output, wl_fixed_t mat0, wl_fixed_t mat1, wl_fixed_t mat2, wl_fixed_t mat3, wl_fixed_t mat4, + wl_fixed_t mat5, wl_fixed_t mat6, wl_fixed_t mat7, wl_fixed_t mat8) { + const auto OUTPUTRESOURCE = CWLOutputResource::fromResource(output); + + if (!OUTPUTRESOURCE) + return; // ?! + + const auto PMONITOR = OUTPUTRESOURCE->monitor.lock(); + + if (!PMONITOR) + return; // ?!?! + + const std::array MAT = {wl_fixed_to_double(mat0), wl_fixed_to_double(mat1), wl_fixed_to_double(mat2), wl_fixed_to_double(mat3), wl_fixed_to_double(mat4), + wl_fixed_to_double(mat5), wl_fixed_to_double(mat6), wl_fixed_to_double(mat7), wl_fixed_to_double(mat8)}; + + for (auto& el : MAT) { + if (el < 0.F) { + resource->error(HYPRLAND_CTM_CONTROL_MANAGER_V1_ERROR_INVALID_MATRIX, "a matrix component was < 0"); + return; + } + } + + ctms[PMONITOR->szName] = MAT; + + LOGM(LOG, "CTM set for output {}: {}", PMONITOR->szName, ctms.at(PMONITOR->szName).toString()); + }); + + resource->setCommit([this](CHyprlandCtmControlManagerV1* r) { + LOGM(LOG, "Committing ctms to outputs"); + + for (auto& m : g_pCompositor->m_vMonitors) { + if (!ctms.contains(m->szName)) { + PROTO::ctm->setCTM(m, Mat3x3::identity()); + continue; + } + + PROTO::ctm->setCTM(m, ctms.at(m->szName)); + } + }); +} + +CHyprlandCTMControlResource::~CHyprlandCTMControlResource() { + for (auto& m : g_pCompositor->m_vMonitors) { + PROTO::ctm->setCTM(m, Mat3x3::identity()); + } +} + +bool CHyprlandCTMControlResource::good() { + return resource->resource(); +} + +CHyprlandCTMControlProtocol::CHyprlandCTMControlProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { + ; +} + +void CHyprlandCTMControlProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + + const auto RESOURCE = m_vManagers.emplace_back(makeShared(makeShared(client, ver, id))); + + if (!RESOURCE->good()) { + wl_client_post_no_memory(client); + m_vManagers.pop_back(); + return; + } + + LOGM(LOG, "New CTM Manager at 0x{:x}", (uintptr_t)RESOURCE.get()); +} + +void CHyprlandCTMControlProtocol::destroyResource(CHyprlandCTMControlResource* res) { + std::erase_if(m_vManagers, [&](const auto& other) { return other.get() == res; }); +} + +void CHyprlandCTMControlProtocol::setCTM(SP monitor, const Mat3x3& ctm) { + monitor->setCTM(ctm); +} diff --git a/src/protocols/CTMControl.hpp b/src/protocols/CTMControl.hpp new file mode 100644 index 00000000..08f1b0e8 --- /dev/null +++ b/src/protocols/CTMControl.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include +#include "WaylandProtocol.hpp" +#include "hyprland-ctm-control-v1.hpp" +#include + +class CMonitor; + +class CHyprlandCTMControlResource { + public: + CHyprlandCTMControlResource(SP resource_); + ~CHyprlandCTMControlResource(); + + bool good(); + + private: + SP resource; + + std::unordered_map ctms; +}; + +class CHyprlandCTMControlProtocol : public IWaylandProtocol { + public: + CHyprlandCTMControlProtocol(const wl_interface* iface, const int& ver, const std::string& name); + + virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); + + private: + void destroyResource(CHyprlandCTMControlResource* resource); + + void setCTM(SP monitor, const Mat3x3& ctm); + + // + std::vector> m_vManagers; + + friend class CHyprlandCTMControlResource; +}; + +namespace PROTO { + inline UP ctm; +}; \ No newline at end of file diff --git a/src/protocols/CursorShape.cpp b/src/protocols/CursorShape.cpp index 812afe53..233a5df9 100644 --- a/src/protocols/CursorShape.cpp +++ b/src/protocols/CursorShape.cpp @@ -2,8 +2,6 @@ #include #include "../helpers/CursorShapes.hpp" -#define LOGM PROTO::cursorShape->protoLog - CCursorShapeProtocol::CCursorShapeProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { ; } diff --git a/src/protocols/DRMLease.cpp b/src/protocols/DRMLease.cpp index 9f5b6312..d0114ce7 100644 --- a/src/protocols/DRMLease.cpp +++ b/src/protocols/DRMLease.cpp @@ -1,10 +1,9 @@ #include "DRMLease.hpp" #include "../Compositor.hpp" +#include "managers/eventLoop/EventLoopManager.hpp" #include #include -#define LOGM PROTO::lease->protoLog - CDRMLeaseResource::CDRMLeaseResource(SP resource_, SP request) : resource(resource_) { if (!good()) return; @@ -15,7 +14,7 @@ CDRMLeaseResource::CDRMLeaseResource(SP resource_, SPparent; requested = request->requested; - for (auto& m : requested) { + for (auto const& m : requested) { if (!m->monitor || m->monitor->isBeingLeased) { LOGM(ERR, "Rejecting lease: no monitor or monitor is being leased for {}", (m->monitor ? m->monitor->szName : "null")); resource->sendFinished(); @@ -27,14 +26,14 @@ CDRMLeaseResource::CDRMLeaseResource(SP resource_, SPmonitor->szName); } return roll; }()); std::vector> outputs; - for (auto& m : requested) { + for (auto const& m : requested) { outputs.emplace_back(m->monitor->output); } @@ -45,25 +44,26 @@ CDRMLeaseResource::CDRMLeaseResource(SP resource_, SPleaseFD); - - resource->sendLeaseFd(aqlease->leaseFD); - lease = aqlease; - for (auto& m : requested) { + for (auto const& m : requested) { m->monitor->isBeingLeased = true; } listeners.destroyLease = lease->events.destroy.registerListener([this](std::any d) { - for (auto& m : requested) { + for (auto const& m : requested) { if (m && m->monitor) m->monitor->isBeingLeased = false; } resource->sendFinished(); + LOGM(LOG, "Revoking lease for fd {}", lease->leaseFD); }); + LOGM(LOG, "Granting lease, sending fd {}", lease->leaseFD); + + resource->sendLeaseFd(lease->leaseFD); + close(lease->leaseFD); } @@ -71,6 +71,12 @@ bool CDRMLeaseResource::good() { return resource->resource(); } +CDRMLeaseResource::~CDRMLeaseResource() { + // destroy in this order to ensure listener gets called + lease.reset(); + listeners.destroyLease.reset(); +} + CDRMLeaseRequestResource::CDRMLeaseRequestResource(SP resource_) : resource(resource_) { if (!good()) return; @@ -101,7 +107,7 @@ CDRMLeaseRequestResource::CDRMLeaseRequestResource(SP reso return; } - auto RESOURCE = makeShared(makeShared(resource->client(), resource->version(), -1), self.lock()); + auto RESOURCE = makeShared(makeShared(resource->client(), resource->version(), id), self.lock()); if (!RESOURCE) { resource->noMemory(); return; @@ -185,8 +191,9 @@ CDRMLeaseDeviceResource::CDRMLeaseDeviceResource(SP resourc resource->sendDrmFd(fd); close(fd); - for (auto& m : PROTO::lease->primaryDevice->offeredOutputs) { - sendConnector(m.lock()); + for (auto const& m : PROTO::lease->primaryDevice->offeredOutputs) { + if (m) + sendConnector(m.lock()); } resource->sendDone(); @@ -197,10 +204,10 @@ bool CDRMLeaseDeviceResource::good() { } void CDRMLeaseDeviceResource::sendConnector(SP monitor) { - if (std::find_if(connectorsSent.begin(), connectorsSent.end(), [monitor](const auto& e) { return e->monitor == monitor; }) != connectorsSent.end()) + if (std::find_if(connectorsSent.begin(), connectorsSent.end(), [monitor](const auto& e) { return e && !e->dead && e->monitor == monitor; }) != connectorsSent.end()) return; - auto RESOURCE = makeShared(makeShared(resource->client(), resource->version(), -1), monitor); + auto RESOURCE = makeShared(makeShared(resource->client(), resource->version(), 0), monitor); if (!RESOURCE) { resource->noMemory(); return; @@ -235,7 +242,7 @@ CDRMLeaseDevice::CDRMLeaseDevice(SP drmBackend) : backe } CDRMLeaseProtocol::CDRMLeaseProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { - for (auto& b : g_pCompositor->m_pAqBackend->getImplementations()) { + for (auto const& b : g_pCompositor->m_pAqBackend->getImplementations()) { if (b->type() != Aquamarine::AQ_BACKEND_DRM) continue; @@ -247,10 +254,8 @@ CDRMLeaseProtocol::CDRMLeaseProtocol(const wl_interface* iface, const int& ver, break; } - if (!primaryDevice || primaryDevice->success) { - PROTO::lease.reset(); - return; - } + if (!primaryDevice || !primaryDevice->success) + g_pEventLoopManager->doLater([]() { PROTO::lease.reset(); }); } void CDRMLeaseProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { @@ -270,6 +275,9 @@ void CDRMLeaseProtocol::destroyResource(CDRMLeaseDeviceResource* resource) { } void CDRMLeaseProtocol::destroyResource(CDRMLeaseConnectorResource* resource) { + for (const auto& m : m_vManagers) { + std::erase_if(m->connectorsSent, [resource](const auto& e) { return e.expired() || e->dead || e.get() == resource; }); + } std::erase_if(m_vConnectors, [resource](const auto& e) { return e.get() == resource; }); } @@ -282,6 +290,7 @@ void CDRMLeaseProtocol::destroyResource(CDRMLeaseResource* resource) { } void CDRMLeaseProtocol::offer(SP monitor) { + std::erase_if(primaryDevice->offeredOutputs, [](const auto& e) { return e.expired(); }); if (std::find(primaryDevice->offeredOutputs.begin(), primaryDevice->offeredOutputs.end(), monitor) != primaryDevice->offeredOutputs.end()) return; @@ -295,7 +304,7 @@ void CDRMLeaseProtocol::offer(SP monitor) { primaryDevice->offeredOutputs.emplace_back(monitor); - for (auto& m : m_vManagers) { + for (auto const& m : m_vManagers) { m->sendConnector(monitor); m->resource->sendDone(); } diff --git a/src/protocols/DRMLease.hpp b/src/protocols/DRMLease.hpp index 56eaa3db..3671cfce 100644 --- a/src/protocols/DRMLease.hpp +++ b/src/protocols/DRMLease.hpp @@ -22,12 +22,13 @@ class CDRMLeaseRequestResource; class CDRMLeaseResource { public: CDRMLeaseResource(SP resource_, SP request); + ~CDRMLeaseResource(); bool good(); WP parent; std::vector> requested; - WP lease; + SP lease; int leaseFD = -1; diff --git a/src/protocols/DRMSyncobj.cpp b/src/protocols/DRMSyncobj.cpp index 33339554..1da4baaf 100644 --- a/src/protocols/DRMSyncobj.cpp +++ b/src/protocols/DRMSyncobj.cpp @@ -7,8 +7,6 @@ #include -#define LOGM PROTO::sync->protoLog - CDRMSyncobjSurfaceResource::CDRMSyncobjSurfaceResource(SP resource_, SP surface_) : surface(surface_), resource(resource_) { if (!good()) return; @@ -24,9 +22,9 @@ CDRMSyncobjSurfaceResource::CDRMSyncobjSurfaceResource(SPsetSetReleasePoint([this](CWpLinuxDrmSyncobjSurfaceV1* r, wl_resource* timeline_, uint32_t hi, uint32_t lo) { @@ -35,29 +33,41 @@ CDRMSyncobjSurfaceResource::CDRMSyncobjSurfaceResource(SPevents.precommit.registerListener([this](std::any d) { - if (!!acquireTimeline != !!releaseTimeline) { - resource->error(acquireTimeline ? WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_RELEASE_POINT : WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_ACQUIRE_POINT, "Missing timeline"); - surface->pending.rejected = true; - return; - } - - if ((acquireTimeline || releaseTimeline) && !surface->pending.texture) { + if ((pending.acquireTimeline || pending.releaseTimeline) && !surface->pending.texture) { resource->error(WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_BUFFER, "Missing buffer"); surface->pending.rejected = true; return; } - if (!acquireTimeline) + if (!surface->pending.newBuffer) + return; // this commit does not change the state here + + if (!!pending.acquireTimeline != !!pending.releaseTimeline) { + resource->error(pending.acquireTimeline ? WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_RELEASE_POINT : WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_NO_ACQUIRE_POINT, + "Missing timeline"); + surface->pending.rejected = true; + return; + } + + if (!pending.acquireTimeline) return; + if (pending.acquireTimeline && pending.releaseTimeline && pending.acquireTimeline == pending.releaseTimeline) { + if (pending.acquirePoint >= pending.releasePoint) { + resource->error(WP_LINUX_DRM_SYNCOBJ_SURFACE_V1_ERROR_CONFLICTING_POINTS, "Acquire and release points are on the same timeline, and acquire >= release"); + surface->pending.rejected = true; + return; + } + } + // wait for the acquire timeline to materialize - auto materialized = acquireTimeline->timeline->check(acquirePoint, DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE); + auto materialized = pending.acquireTimeline->timeline->check(pending.acquirePoint, DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE); if (!materialized.has_value()) { LOGM(ERR, "Failed to check the acquire timeline"); resource->noMemory(); @@ -68,7 +78,24 @@ CDRMSyncobjSurfaceResource::CDRMSyncobjSurfaceResource(SPlockPendingState(); - acquireTimeline->timeline->addWaiter([this]() { surface->unlockPendingState(); }, acquirePoint, DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE); + pending.acquireTimeline->timeline->addWaiter([this]() { surface->unlockPendingState(); }, pending.acquirePoint, DRM_SYNCOBJ_WAIT_FLAGS_WAIT_AVAILABLE); + }); + + listeners.surfaceCommit = surface->events.roleCommit.registerListener([this](std::any d) { + // apply timelines if new ones have been attached, otherwise don't touch + // the current ones + if (pending.releaseTimeline) { + current.releaseTimeline = pending.releaseTimeline; + current.releasePoint = pending.releasePoint; + } + + if (pending.acquireTimeline) { + current.acquireTimeline = pending.acquireTimeline; + current.acquirePoint = pending.acquirePoint; + } + + pending.releaseTimeline.reset(); + pending.acquireTimeline.reset(); }); } @@ -93,6 +120,11 @@ CDRMSyncobjTimelineResource::CDRMSyncobjTimelineResource(SP= 0) + close(fd); +} + SP CDRMSyncobjTimelineResource::fromResource(wl_resource* res) { auto data = (CDRMSyncobjTimelineResource*)(((CWpLinuxDrmSyncobjTimelineV1*)wl_resource_get_user_data(res))->data()); return data ? data->self.lock() : nullptr; diff --git a/src/protocols/DRMSyncobj.hpp b/src/protocols/DRMSyncobj.hpp index c1c884ff..bc89a3d3 100644 --- a/src/protocols/DRMSyncobj.hpp +++ b/src/protocols/DRMSyncobj.hpp @@ -14,23 +14,27 @@ class CDRMSyncobjSurfaceResource { public: CDRMSyncobjSurfaceResource(SP resource_, SP surface_); - bool good(); + bool good(); - WP surface; - WP acquireTimeline, releaseTimeline; - uint64_t acquirePoint = 0, releasePoint = 0; + WP surface; + struct { + WP acquireTimeline, releaseTimeline; + uint64_t acquirePoint = 0, releasePoint = 0; + } current, pending; private: SP resource; struct { CHyprSignalListener surfacePrecommit; + CHyprSignalListener surfaceCommit; } listeners; }; class CDRMSyncobjTimelineResource { public: CDRMSyncobjTimelineResource(SP resource_, int fd_); + ~CDRMSyncobjTimelineResource(); static SP fromResource(wl_resource*); bool good(); diff --git a/src/protocols/DataDeviceWlr.cpp b/src/protocols/DataDeviceWlr.cpp index c039d3b4..69e28772 100644 --- a/src/protocols/DataDeviceWlr.cpp +++ b/src/protocols/DataDeviceWlr.cpp @@ -3,8 +3,6 @@ #include "../managers/SeatManager.hpp" #include "core/Seat.hpp" -#define LOGM PROTO::dataWlr->protoLog - CWLRDataOffer::CWLRDataOffer(SP resource_, SP source_) : source(source_), resource(resource_) { if (!good()) return; @@ -39,7 +37,7 @@ void CWLRDataOffer::sendData() { if (!source) return; - for (auto& m : source->mimes()) { + for (auto const& m : source->mimes()) { resource->sendOffer(m.c_str()); } } @@ -135,7 +133,7 @@ CWLRDataDevice::CWLRDataDevice(SP resource_) : resourc auto source = sourceR ? CWLRDataSource::fromResource(sourceR) : CSharedPointer{}; if (!source) { LOGM(LOG, "wlr reset primary selection received"); - g_pSeatManager->setCurrentSelection(nullptr); + g_pSeatManager->setCurrentPrimarySelection(nullptr); return; } @@ -193,7 +191,7 @@ CWLRDataControlManagerResource::CWLRDataControlManagerResource(SPself = RESOURCE; device = RESOURCE; - for (auto& s : sources) { + for (auto const& s : sources) { if (!s) continue; s->device = RESOURCE; @@ -293,7 +291,7 @@ void CDataDeviceWLRProtocol::sendSelectionToDevice(SP dev, SP source, bool primary) { - for (auto& o : m_vOffers) { + for (auto const& o : m_vOffers) { if (o->source && o->source->hasDnd()) continue; if (o->primary != primary) @@ -304,7 +302,7 @@ void CDataDeviceWLRProtocol::setSelection(SP source, bool primary) if (!source) { LOGM(LOG, "resetting {}selection", primary ? "primary " : " "); - for (auto& d : m_vDevices) { + for (auto const& d : m_vDevices) { sendSelectionToDevice(d, nullptr, primary); } @@ -313,7 +311,7 @@ void CDataDeviceWLRProtocol::setSelection(SP source, bool primary) LOGM(LOG, "New {}selection for data source {:x}", primary ? "primary" : "", (uintptr_t)source.get()); - for (auto& d : m_vDevices) { + for (auto const& d : m_vDevices) { sendSelectionToDevice(d, source, primary); } } diff --git a/src/protocols/FocusGrab.cpp b/src/protocols/FocusGrab.cpp index 40f9af44..aff232e4 100644 --- a/src/protocols/FocusGrab.cpp +++ b/src/protocols/FocusGrab.cpp @@ -8,8 +8,6 @@ #include #include -#define LOGM PROTO::focusGrab->protoLog - CFocusGrabSurfaceState::CFocusGrabSurfaceState(CFocusGrab* grab, SP surface) { listeners.destroy = surface->events.destroy.registerListener([=](std::any d) { grab->eraseSurface(surface); }); } @@ -105,7 +103,7 @@ void CFocusGrab::refocusKeyboard() { return; SP surface = nullptr; - for (auto& [surf, state] : m_mSurfaces) { + for (auto const& [surf, state] : m_mSurfaces) { if (state->state == CFocusGrabSurfaceState::Comitted) { surface = surf.lock(); break; diff --git a/src/protocols/FocusGrab.hpp b/src/protocols/FocusGrab.hpp index a2d545c5..6fe8780f 100644 --- a/src/protocols/FocusGrab.hpp +++ b/src/protocols/FocusGrab.hpp @@ -53,9 +53,6 @@ class CFocusGrab { bool m_bGrabActive = false; - DYNLISTENER(pointerGrabStarted); - DYNLISTENER(keyboardGrabStarted); - DYNLISTENER(touchGrabStarted); friend class CFocusGrabSurfaceState; }; diff --git a/src/protocols/ForeignToplevel.cpp b/src/protocols/ForeignToplevel.cpp index f7b3886f..1f8cbcbe 100644 --- a/src/protocols/ForeignToplevel.cpp +++ b/src/protocols/ForeignToplevel.cpp @@ -1,8 +1,6 @@ #include "ForeignToplevel.hpp" #include "../Compositor.hpp" -#define LOGM PROTO::foreignToplevel->protoLog - CForeignToplevelHandle::CForeignToplevelHandle(SP resource_, PHLWINDOW pWindow_) : resource(resource_), pWindow(pWindow_) { if (!resource_->resource()) return; @@ -32,7 +30,7 @@ CForeignToplevelList::CForeignToplevelList(SP resourc LOGM(LOG, "CForeignToplevelList: finished"); }); - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (!w->m_bIsMapped || w->m_bFadingOut) continue; @@ -114,19 +112,19 @@ bool CForeignToplevelList::good() { CForeignToplevelProtocol::CForeignToplevelProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { static auto P = g_pHookSystem->hookDynamic("openWindow", [this](void* self, SCallbackInfo& info, std::any data) { - for (auto& m : m_vManagers) { + for (auto const& m : m_vManagers) { m->onMap(std::any_cast(data)); } }); static auto P1 = g_pHookSystem->hookDynamic("closeWindow", [this](void* self, SCallbackInfo& info, std::any data) { - for (auto& m : m_vManagers) { + for (auto const& m : m_vManagers) { m->onUnmap(std::any_cast(data)); } }); static auto P2 = g_pHookSystem->hookDynamic("windowTitle", [this](void* self, SCallbackInfo& info, std::any data) { - for (auto& m : m_vManagers) { + for (auto const& m : m_vManagers) { m->onTitle(std::any_cast(data)); } }); diff --git a/src/protocols/ForeignToplevelWlr.cpp b/src/protocols/ForeignToplevelWlr.cpp index 295834ea..291969db 100644 --- a/src/protocols/ForeignToplevelWlr.cpp +++ b/src/protocols/ForeignToplevelWlr.cpp @@ -4,8 +4,6 @@ #include "protocols/core/Output.hpp" #include "render/Renderer.hpp" -#define LOGM PROTO::foreignToplevelWlr->protoLog - CForeignToplevelHandleWlr::CForeignToplevelHandleWlr(SP resource_, PHLWINDOW pWindow_) : resource(resource_), pWindow(pWindow_) { if (!resource_->resource()) return; @@ -119,7 +117,7 @@ wl_resource* CForeignToplevelHandleWlr::res() { } void CForeignToplevelHandleWlr::sendMonitor(CMonitor* pMonitor) { - if (lastMonitorID == (int64_t)pMonitor->ID) + if (lastMonitorID == pMonitor->ID) return; const auto CLIENT = resource->client(); @@ -181,7 +179,7 @@ CForeignToplevelWlrManager::CForeignToplevelWlrManager(SPonManagerResourceDestroy(this); }); - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (!w->m_bIsMapped || w->m_bFadingOut) continue; @@ -315,42 +313,42 @@ bool CForeignToplevelWlrManager::good() { CForeignToplevelWlrProtocol::CForeignToplevelWlrProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { static auto P = g_pHookSystem->hookDynamic("openWindow", [this](void* self, SCallbackInfo& info, std::any data) { const auto PWINDOW = std::any_cast(data); - for (auto& m : m_vManagers) { + for (auto const& m : m_vManagers) { m->onMap(PWINDOW); } }); static auto P1 = g_pHookSystem->hookDynamic("closeWindow", [this](void* self, SCallbackInfo& info, std::any data) { const auto PWINDOW = std::any_cast(data); - for (auto& m : m_vManagers) { + for (auto const& m : m_vManagers) { m->onUnmap(PWINDOW); } }); static auto P2 = g_pHookSystem->hookDynamic("windowTitle", [this](void* self, SCallbackInfo& info, std::any data) { const auto PWINDOW = std::any_cast(data); - for (auto& m : m_vManagers) { + for (auto const& m : m_vManagers) { m->onTitle(PWINDOW); } }); static auto P3 = g_pHookSystem->hookDynamic("activeWindow", [this](void* self, SCallbackInfo& info, std::any data) { const auto PWINDOW = std::any_cast(data); - for (auto& m : m_vManagers) { + for (auto const& m : m_vManagers) { m->onNewFocus(PWINDOW); } }); static auto P4 = g_pHookSystem->hookDynamic("moveWindow", [this](void* self, SCallbackInfo& info, std::any data) { const auto PWINDOW = std::any_cast(std::any_cast>(data).at(0)); - for (auto& m : m_vManagers) { + for (auto const& m : m_vManagers) { m->onMoveMonitor(PWINDOW); } }); static auto P5 = g_pHookSystem->hookDynamic("fullscreen", [this](void* self, SCallbackInfo& info, std::any data) { const auto PWINDOW = std::any_cast(data); - for (auto& m : m_vManagers) { + for (auto const& m : m_vManagers) { m->onFullscreen(PWINDOW); } }); @@ -376,7 +374,7 @@ void CForeignToplevelWlrProtocol::destroyHandle(CForeignToplevelHandleWlr* handl } PHLWINDOW CForeignToplevelWlrProtocol::windowFromHandleResource(wl_resource* res) { - for (auto& h : m_vHandles) { + for (auto const& h : m_vHandles) { if (h->res() != res) continue; diff --git a/src/protocols/ForeignToplevelWlr.hpp b/src/protocols/ForeignToplevelWlr.hpp index e3b6f3f3..99f63b47 100644 --- a/src/protocols/ForeignToplevelWlr.hpp +++ b/src/protocols/ForeignToplevelWlr.hpp @@ -20,7 +20,7 @@ class CForeignToplevelHandleWlr { SP resource; PHLWINDOWREF pWindow; bool closed = false; - int64_t lastMonitorID = -1; + MONITORID lastMonitorID = MONITOR_INVALID; void sendMonitor(CMonitor* pMonitor); void sendState(); diff --git a/src/protocols/FractionalScale.cpp b/src/protocols/FractionalScale.cpp index 5bf56c5a..d8010eac 100644 --- a/src/protocols/FractionalScale.cpp +++ b/src/protocols/FractionalScale.cpp @@ -2,8 +2,6 @@ #include #include "core/Compositor.hpp" -#define LOGM PROTO::fractional->protoLog - CFractionalScaleProtocol::CFractionalScaleProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { ; } @@ -26,7 +24,7 @@ void CFractionalScaleProtocol::onManagerResourceDestroy(wl_resource* res) { } void CFractionalScaleProtocol::onGetFractionalScale(CWpFractionalScaleManagerV1* pMgr, uint32_t id, SP surface) { - for (auto& [k, v] : m_mAddons) { + for (auto const& [k, v] : m_mAddons) { if (k == surface) { LOGM(ERR, "Surface {:x} already has a fractionalScale addon", (uintptr_t)surface.get()); pMgr->error(WP_FRACTIONAL_SCALE_MANAGER_V1_ERROR_FRACTIONAL_SCALE_EXISTS, "Fractional scale already exists"); diff --git a/src/protocols/GammaControl.cpp b/src/protocols/GammaControl.cpp index 494d9862..e794e543 100644 --- a/src/protocols/GammaControl.cpp +++ b/src/protocols/GammaControl.cpp @@ -5,8 +5,6 @@ #include "../Compositor.hpp" #include "../protocols/core/Output.hpp" -#define LOGM PROTO::gamma->protoLog - CGammaControl::CGammaControl(SP resource_, wl_resource* output) : resource(resource_) { if (!resource_->resource()) return; @@ -19,15 +17,15 @@ CGammaControl::CGammaControl(SP resource_, wl_resource* out return; } - pMonitor = OUTPUTRES->monitor.get(); + pMonitor = OUTPUTRES->monitor; - if (!pMonitor) { + if (!pMonitor || !pMonitor->output) { LOGM(ERR, "No CMonitor"); resource->sendFailed(); return; } - for (auto& g : PROTO::gamma->m_vGammaControllers) { + for (auto const& g : PROTO::gamma->m_vGammaControllers) { if (g->pMonitor == pMonitor) { resource->sendFailed(); return; @@ -48,6 +46,13 @@ CGammaControl::CGammaControl(SP resource_, wl_resource* out resource->setOnDestroy([this](CZwlrGammaControlV1* gamma) { PROTO::gamma->destroyGammaControl(this); }); resource->setSetGamma([this](CZwlrGammaControlV1* gamma, int32_t fd) { + if (!pMonitor) { + LOGM(ERR, "setGamma for a dead monitor"); + resource->sendFailed(); + close(fd); + return; + } + LOGM(LOG, "setGamma for {}", pMonitor->szName); int fdFlags = fcntl(fd, F_GETFL, 0); @@ -105,11 +110,11 @@ CGammaControl::CGammaControl(SP resource_, wl_resource* out resource->sendGammaSize(gammaSize); listeners.monitorDestroy = pMonitor->events.destroy.registerListener([this](std::any) { this->onMonitorDestroy(); }); - listeners.monitorDisconnect = pMonitor->events.destroy.registerListener([this](std::any) { this->onMonitorDestroy(); }); + listeners.monitorDisconnect = pMonitor->events.disconnect.registerListener([this](std::any) { this->onMonitorDestroy(); }); } CGammaControl::~CGammaControl() { - if (!gammaTableSet || !pMonitor) + if (!gammaTableSet || !pMonitor || !pMonitor->output) return; // reset the LUT if the client dies for whatever reason and doesn't unset the gamma @@ -121,7 +126,7 @@ bool CGammaControl::good() { } void CGammaControl::applyToMonitor() { - if (!pMonitor) + if (!pMonitor || !pMonitor->output) return; // ?? LOGM(LOG, "setting to monitor {}", pMonitor->szName); @@ -138,16 +143,16 @@ void CGammaControl::applyToMonitor() { pMonitor->output->state->setGammaLut({}); } - g_pHyprRenderer->damageMonitor(pMonitor); + g_pHyprRenderer->damageMonitor(pMonitor.get()); } CMonitor* CGammaControl::getMonitor() { - return pMonitor; + return pMonitor ? pMonitor.get() : nullptr; } void CGammaControl::onMonitorDestroy() { + LOGM(LOG, "Destroying gamma control for {}", pMonitor->szName); resource->sendFailed(); - pMonitor = nullptr; } CGammaControlProtocol::CGammaControlProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { @@ -182,11 +187,11 @@ void CGammaControlProtocol::onGetGammaControl(CZwlrGammaControlManagerV1* pMgr, } void CGammaControlProtocol::applyGammaToState(CMonitor* pMonitor) { - for (auto& g : m_vGammaControllers) { + for (auto const& g : m_vGammaControllers) { if (g->getMonitor() != pMonitor) continue; g->applyToMonitor(); break; } -} \ No newline at end of file +} diff --git a/src/protocols/GammaControl.hpp b/src/protocols/GammaControl.hpp index 963eea5c..bbdc0139 100644 --- a/src/protocols/GammaControl.hpp +++ b/src/protocols/GammaControl.hpp @@ -20,7 +20,7 @@ class CGammaControl { private: SP resource; - CMonitor* pMonitor = nullptr; + WP pMonitor; size_t gammaSize = 0; bool gammaTableSet = false; std::vector gammaTable; // [r,g,b]+ diff --git a/src/protocols/GlobalShortcuts.cpp b/src/protocols/GlobalShortcuts.cpp index 860004c9..b02126ef 100644 --- a/src/protocols/GlobalShortcuts.cpp +++ b/src/protocols/GlobalShortcuts.cpp @@ -1,8 +1,6 @@ #include "GlobalShortcuts.hpp" #include "../Compositor.hpp" -#define LOGM PROTO::globalShortcuts->protoLog - CShortcutClient::CShortcutClient(SP resource_) : resource(resource_) { if (!good()) return; @@ -56,8 +54,8 @@ void CGlobalShortcutsProtocol::destroyResource(CShortcutClient* client) { } bool CGlobalShortcutsProtocol::isTaken(std::string appid, std::string trigger) { - for (auto& c : m_vClients) { - for (auto& sh : c->shortcuts) { + for (auto const& c : m_vClients) { + for (auto const& sh : c->shortcuts) { if (sh->appid == appid && sh->id == trigger) { return true; } @@ -68,8 +66,8 @@ bool CGlobalShortcutsProtocol::isTaken(std::string appid, std::string trigger) { } void CGlobalShortcutsProtocol::sendGlobalShortcutEvent(std::string appid, std::string trigger, bool pressed) { - for (auto& c : m_vClients) { - for (auto& sh : c->shortcuts) { + for (auto const& c : m_vClients) { + for (auto const& sh : c->shortcuts) { if (sh->appid == appid && sh->id == trigger) { timespec now; clock_gettime(CLOCK_MONOTONIC, &now); @@ -86,8 +84,8 @@ void CGlobalShortcutsProtocol::sendGlobalShortcutEvent(std::string appid, std::s std::vector CGlobalShortcutsProtocol::getAllShortcuts() { std::vector copy; - for (auto& c : m_vClients) { - for (auto& sh : c->shortcuts) { + for (auto const& c : m_vClients) { + for (auto const& sh : c->shortcuts) { copy.push_back(*sh); } } diff --git a/src/protocols/IdleNotify.cpp b/src/protocols/IdleNotify.cpp index 2ec7d2a1..3517a62d 100644 --- a/src/protocols/IdleNotify.cpp +++ b/src/protocols/IdleNotify.cpp @@ -1,8 +1,6 @@ #include "IdleNotify.hpp" #include "../managers/eventLoop/EventLoopManager.hpp" -#define LOGM PROTO::idle->protoLog - static int onTimer(SP self, void* data) { const auto NOTIF = (CExtIdleNotification*)data; @@ -88,14 +86,14 @@ void CIdleNotifyProtocol::onGetNotification(CExtIdleNotifierV1* pMgr, uint32_t i } void CIdleNotifyProtocol::onActivity() { - for (auto& n : m_vNotifications) { + for (auto const& n : m_vNotifications) { n->onActivity(); } } void CIdleNotifyProtocol::setInhibit(bool inhibited) { isInhibited = inhibited; - for (auto& n : m_vNotifications) { + for (auto const& n : m_vNotifications) { n->onActivity(); } } \ No newline at end of file diff --git a/src/protocols/InputMethodV2.cpp b/src/protocols/InputMethodV2.cpp index fd306f09..5fd1e893 100644 --- a/src/protocols/InputMethodV2.cpp +++ b/src/protocols/InputMethodV2.cpp @@ -6,8 +6,6 @@ #include "core/Compositor.hpp" #include -#define LOGM PROTO::ime->protoLog - CInputMethodKeyboardGrabV2::CInputMethodKeyboardGrabV2(SP resource_, SP owner_) : resource(resource_), owner(owner_) { if (!resource->resource()) return; @@ -270,7 +268,7 @@ wl_client* CInputMethodV2::grabClient() { if (grabs.empty()) return nullptr; - for (auto& gw : grabs) { + for (auto const& gw : grabs) { auto g = gw.lock(); if (!g) @@ -284,7 +282,7 @@ wl_client* CInputMethodV2::grabClient() { void CInputMethodV2::sendInputRectangle(const CBox& box) { inputRectangle = box; - for (auto& wp : popups) { + for (auto const& wp : popups) { auto p = wp.lock(); if (!p) @@ -295,7 +293,7 @@ void CInputMethodV2::sendInputRectangle(const CBox& box) { } void CInputMethodV2::sendKey(uint32_t time, uint32_t key, wl_keyboard_key_state state) { - for (auto& gw : grabs) { + for (auto const& gw : grabs) { auto g = gw.lock(); if (!g) @@ -306,7 +304,7 @@ void CInputMethodV2::sendKey(uint32_t time, uint32_t key, wl_keyboard_key_state } void CInputMethodV2::sendMods(uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group) { - for (auto& gw : grabs) { + for (auto const& gw : grabs) { auto g = gw.lock(); if (!g) @@ -317,7 +315,7 @@ void CInputMethodV2::sendMods(uint32_t depressed, uint32_t latched, uint32_t loc } void CInputMethodV2::setKeyboard(SP keyboard) { - for (auto& gw : grabs) { + for (auto const& gw : grabs) { auto g = gw.lock(); if (!g) diff --git a/src/protocols/LayerShell.cpp b/src/protocols/LayerShell.cpp index 17d3b22a..c02d23f3 100644 --- a/src/protocols/LayerShell.cpp +++ b/src/protocols/LayerShell.cpp @@ -4,8 +4,6 @@ #include "core/Compositor.hpp" #include "core/Output.hpp" -#define LOGM PROTO::layerShell->protoLog - void CLayerShellResource::SState::reset() { anchor = 0; exclusive = 0; diff --git a/src/protocols/LinuxDMABUF.cpp b/src/protocols/LinuxDMABUF.cpp index 0fbf832e..d9dd1d01 100644 --- a/src/protocols/LinuxDMABUF.cpp +++ b/src/protocols/LinuxDMABUF.cpp @@ -14,8 +14,6 @@ #include "../render/OpenGL.hpp" #include "../Compositor.hpp" -#define LOGM PROTO::linuxDma->protoLog - static std::optional devIDFromFD(int fd) { struct stat stat; if (fstat(fd, &stat) != 0) @@ -33,8 +31,8 @@ CDMABUFFormatTable::CDMABUFFormatTable(SDMABUFTranche _rendererTranche, std::vec size_t i = 0; rendererTranche.indicies.clear(); - for (auto& fmt : rendererTranche.formats) { - for (auto& mod : fmt.modifiers) { + for (auto const& fmt : rendererTranche.formats) { + for (auto const& mod : fmt.modifiers) { auto format = std::make_pair<>(fmt.drmFormat, mod); auto [_, inserted] = formats.insert(format); if (inserted) { @@ -55,8 +53,8 @@ CDMABUFFormatTable::CDMABUFFormatTable(SDMABUFTranche _rendererTranche, std::vec for (auto& [monitor, tranche] : monitorTranches) { tranche.indicies.clear(); - for (auto& fmt : tranche.formats) { - for (auto& mod : fmt.modifiers) { + for (auto const& fmt : tranche.formats) { + for (auto const& mod : fmt.modifiers) { // apparently these can implode on planes, so dont use them if (mod == DRM_FORMAT_MOD_INVALID || mod == DRM_FORMAT_MOD_LINEAR) continue; @@ -272,7 +270,7 @@ bool CLinuxDMABBUFParamsResource::verify() { } bool empty = false; - for (auto& plane : attrs->fds) { + for (auto const& plane : attrs->fds) { if (empty && plane != -1) { resource->error(ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_FORMAT, "Gap in planes"); return false; @@ -404,8 +402,8 @@ bool CLinuxDMABUFResource::good() { } void CLinuxDMABUFResource::sendMods() { - for (auto& fmt : PROTO::linuxDma->formatTable->rendererTranche.formats) { - for (auto& mod : fmt.modifiers) { + for (auto const& fmt : PROTO::linuxDma->formatTable->rendererTranche.formats) { + for (auto const& mod : fmt.modifiers) { if (resource->version() < 3) { if (mod == DRM_FORMAT_MOD_INVALID || mod == DRM_FORMAT_MOD_LINEAR) resource->sendFormat(fmt.drmFormat); @@ -425,7 +423,7 @@ CLinuxDMABufV1Protocol::CLinuxDMABufV1Protocol(const wl_interface* iface, const auto dev = devIDFromFD(rendererFD); if (!dev.has_value()) { - protoLog(ERR, "failed to get drm dev, disabling linux dmabuf"); + LOGM(ERR, "failed to get drm dev, disabling linux dmabuf"); removeGlobal(); return; } @@ -444,7 +442,7 @@ CLinuxDMABufV1Protocol::CLinuxDMABufV1Protocol(const wl_interface* iface, const // this assumes there's only 1 device used for both scanout and rendering // also that each monitor never changes its primary plane - for (auto& mon : g_pCompositor->m_vMonitors) { + for (auto const& mon : g_pCompositor->m_vMonitors) { auto tranche = SDMABUFTranche{ .device = mainDevice, .flags = ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_SCANOUT, @@ -477,7 +475,7 @@ CLinuxDMABufV1Protocol::CLinuxDMABufV1Protocol(const wl_interface* iface, const drmDevice* device = nullptr; if (drmGetDeviceFromDevId(mainDevice, 0, &device) != 0) { - protoLog(ERR, "failed to get drm dev, disabling linux dmabuf"); + LOGM(ERR, "failed to get drm dev, disabling linux dmabuf"); removeGlobal(); return; } @@ -487,14 +485,13 @@ CLinuxDMABufV1Protocol::CLinuxDMABufV1Protocol(const wl_interface* iface, const mainDeviceFD = open(name, O_RDWR | O_CLOEXEC); drmFreeDevice(&device); if (mainDeviceFD < 0) { - protoLog(ERR, "failed to open drm dev, disabling linux dmabuf"); + LOGM(ERR, "failed to open drm dev, disabling linux dmabuf"); removeGlobal(); return; } } else { - protoLog(ERR, "DRM device {} has no render node, disabling linux dmabuf", device->nodes[DRM_NODE_PRIMARY] ? device->nodes[DRM_NODE_PRIMARY] : "null"); + LOGM(ERR, "DRM device {} has no render node, disabling linux dmabuf checks", device->nodes[DRM_NODE_PRIMARY] ? device->nodes[DRM_NODE_PRIMARY] : "null"); drmFreeDevice(&device); - removeGlobal(); } }); } @@ -508,7 +505,7 @@ void CLinuxDMABufV1Protocol::resetFormatTable() { // this might be a big copy auto newFormatTable = std::make_unique(formatTable->rendererTranche, formatTable->monitorTranches); - for (auto& feedback : m_vFeedbacks) { + for (auto const& feedback : m_vFeedbacks) { feedback->resource->sendFormatTable(newFormatTable->tableFD, newFormatTable->tableSize); if (feedback->lastFeedbackWasScanout) { SP mon; @@ -565,7 +562,7 @@ void CLinuxDMABufV1Protocol::destroyResource(CLinuxDMABuffer* resource) { void CLinuxDMABufV1Protocol::updateScanoutTranche(SP surface, SP pMonitor) { SP feedbackResource; - for (auto& f : m_vFeedbacks) { + for (auto const& f : m_vFeedbacks) { if (f->surface != surface) continue; diff --git a/src/protocols/MesaDRM.cpp b/src/protocols/MesaDRM.cpp index ed412555..b4efd2c8 100644 --- a/src/protocols/MesaDRM.cpp +++ b/src/protocols/MesaDRM.cpp @@ -4,8 +4,6 @@ #include "../Compositor.hpp" #include "types/WLBuffer.hpp" -#define LOGM PROTO::mesaDRM->protoLog - CMesaDRMBufferResource::CMesaDRMBufferResource(uint32_t id, wl_client* client, Aquamarine::SDMABUFAttrs attrs_) { LOGM(LOG, "Creating a Mesa dmabuf, with id {}: size {}, fmt {}, planes {}", id, attrs_.size, attrs_.format, attrs_.planes); for (int i = 0; i < attrs_.planes; ++i) { @@ -62,11 +60,11 @@ CMesaDRMResource::CMesaDRMResource(SP resource_) : resource(resource_) { uint64_t mod = DRM_FORMAT_MOD_INVALID; auto fmts = g_pHyprOpenGL->getDRMFormats(); - for (auto& f : fmts) { + for (auto const& f : fmts) { if (f.drmFormat != fmt) continue; - for (auto& m : f.modifiers) { + for (auto const& m : f.modifiers) { if (m == DRM_FORMAT_MOD_LINEAR) continue; @@ -102,7 +100,7 @@ CMesaDRMResource::CMesaDRMResource(SP resource_) : resource(resource_) { resource->sendCapabilities(WL_DRM_CAPABILITY_PRIME); auto fmts = g_pHyprOpenGL->getDRMFormats(); - for (auto& fmt : fmts) { + for (auto const& fmt : fmts) { resource->sendFormat(fmt.drmFormat); } } @@ -115,7 +113,7 @@ CMesaDRMProtocol::CMesaDRMProtocol(const wl_interface* iface, const int& ver, co drmDevice* dev = nullptr; int drmFD = g_pCompositor->m_iDRMFD; if (drmGetDevice2(drmFD, 0, &dev) != 0) { - protoLog(ERR, "Failed to get device, disabling MesaDRM"); + LOGM(ERR, "Failed to get device, disabling MesaDRM"); removeGlobal(); return; } @@ -126,13 +124,13 @@ CMesaDRMProtocol::CMesaDRMProtocol(const wl_interface* iface, const int& ver, co ASSERT(dev->available_nodes & (1 << DRM_NODE_PRIMARY)); if (!dev->nodes[DRM_NODE_PRIMARY]) { - protoLog(ERR, "No DRM render node available, both render and primary are null, disabling MesaDRM"); + LOGM(ERR, "No DRM render node available, both render and primary are null, disabling MesaDRM"); drmFreeDevice(&dev); removeGlobal(); return; } - protoLog(WARN, "No DRM render node, falling back to primary {}", dev->nodes[DRM_NODE_PRIMARY]); + LOGM(WARN, "No DRM render node, falling back to primary {}", dev->nodes[DRM_NODE_PRIMARY]); nodeName = dev->nodes[DRM_NODE_PRIMARY]; } drmFreeDevice(&dev); diff --git a/src/protocols/OutputManagement.cpp b/src/protocols/OutputManagement.cpp index 66f4c5f0..3fd0cf6c 100644 --- a/src/protocols/OutputManagement.cpp +++ b/src/protocols/OutputManagement.cpp @@ -4,8 +4,6 @@ using namespace Aquamarine; -#define LOGM PROTO::outputManagement->protoLog - COutputManager::COutputManager(SP resource_) : resource(resource_) { if (!good()) return; @@ -30,7 +28,7 @@ COutputManager::COutputManager(SP resource_) : resource(re }); // send all heads at start - for (auto& m : g_pCompositor->m_vRealMonitors) { + for (auto const& m : g_pCompositor->m_vRealMonitors) { if (m.get() == g_pCompositor->m_pUnsafeOutput) continue; @@ -69,7 +67,7 @@ void COutputManager::ensureMonitorSent(CMonitor* pMonitor) { if (pMonitor == g_pCompositor->m_pUnsafeOutput) return; - for (auto& hw : heads) { + for (auto const& hw : heads) { auto h = hw.lock(); if (!h) @@ -98,7 +96,7 @@ COutputHead::COutputHead(SP resource_, CMonitor* pMonitor_) : listeners.monitorDestroy = pMonitor->events.destroy.registerListener([this](std::any d) { resource->sendFinished(); - for (auto& mw : modes) { + for (auto const& mw : modes) { auto m = mw.lock(); if (!m) @@ -108,7 +106,7 @@ COutputHead::COutputHead(SP resource_, CMonitor* pMonitor_) : } pMonitor = nullptr; - for (auto& m : PROTO::outputManagement->m_vManagers) { + for (auto const& m : PROTO::outputManagement->m_vManagers) { m->sendDone(); } }); @@ -149,7 +147,7 @@ void COutputHead::sendAllData() { if (modes.empty()) { if (!pMonitor->output->modes.empty()) { - for (auto& m : pMonitor->output->modes) { + for (auto const& m : pMonitor->output->modes) { makeAndSendNewMode(m); } } else if (pMonitor->output->state->state().customMode) { @@ -160,7 +158,7 @@ void COutputHead::sendAllData() { // send current mode if (pMonitor->m_bEnabled) { - for (auto& mw : modes) { + for (auto const& mw : modes) { auto m = mw.lock(); if (!m) @@ -191,7 +189,7 @@ void COutputHead::updateMode() { resource->sendAdaptiveSync(pMonitor->vrrActive ? ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_ENABLED : ZWLR_OUTPUT_HEAD_V1_ADAPTIVE_SYNC_STATE_DISABLED); if (pMonitor->m_bEnabled) { - for (auto& mw : modes) { + for (auto const& mw : modes) { auto m = mw.lock(); if (!m) @@ -309,10 +307,15 @@ COutputConfiguration::COutputConfiguration(SP resour LOGM(LOG, "disableHead on {}", PMONITOR->szName); - PMONITOR->activeMonitorRule.disabled = true; - if (!g_pConfigManager->replaceMonitorRule(PMONITOR->activeMonitorRule)) - g_pConfigManager->appendMonitorRule(PMONITOR->activeMonitorRule); - g_pHyprRenderer->applyMonitorRule(PMONITOR, &PMONITOR->activeMonitorRule, false); + SWlrManagerSavedOutputState newState; + if (owner->monitorStates.contains(PMONITOR->szName)) + newState = owner->monitorStates.at(PMONITOR->szName); + + newState.enabled = false; + + g_pConfigManager->m_bWantsMonitorReload = true; + + owner->monitorStates[PMONITOR->szName] = newState; }); resource->setTest([this](CZwlrOutputConfigurationV1* r) { @@ -348,7 +351,12 @@ bool COutputConfiguration::applyTestConfiguration(bool test) { LOGM(LOG, "Applying configuration"); - for (auto& headw : heads) { + if (!owner) { + LOGM(ERR, "applyTestConfiguration: no owner?!"); + return false; + } + + for (auto const& headw : heads) { auto head = headw.lock(); if (!head) @@ -359,41 +367,59 @@ bool COutputConfiguration::applyTestConfiguration(bool test) { if (!PMONITOR) continue; - LOGM(LOG, "Applying config for monitor {}", PMONITOR->szName); + LOGM(LOG, "Saving config for monitor {}", PMONITOR->szName); - SMonitorRule newRule = PMONITOR->activeMonitorRule; - newRule.name = PMONITOR->szName; - newRule.disabled = false; + SWlrManagerSavedOutputState newState; + if (owner->monitorStates.contains(PMONITOR->szName)) + newState = owner->monitorStates.at(PMONITOR->szName); - if (head->committedProperties & COutputConfigurationHead::eCommittedProperties::OUTPUT_HEAD_COMMITTED_MODE) { - newRule.resolution = head->state.mode->getMode()->pixelSize; - newRule.refreshRate = head->state.mode->getMode()->refreshRate / 1000.F; - } else if (head->committedProperties & COutputConfigurationHead::eCommittedProperties::OUTPUT_HEAD_COMMITTED_CUSTOM_MODE) { - newRule.resolution = head->state.customMode.size; - newRule.refreshRate = head->state.customMode.refresh / 1000.F; + newState.enabled = true; + + if (head->state.committedProperties & eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_MODE) { + newState.resolution = head->state.mode->getMode()->pixelSize; + newState.refresh = head->state.mode->getMode()->refreshRate; + newState.committedProperties |= eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_MODE; + LOGM(LOG, " > Mode: {:.0f}x{:.0f}@{}mHz", newState.resolution.x, newState.resolution.y, newState.refresh); + } else if (head->state.committedProperties & eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_CUSTOM_MODE) { + newState.resolution = head->state.customMode.size; + newState.refresh = head->state.customMode.refresh; + newState.committedProperties |= eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_CUSTOM_MODE; + LOGM(LOG, " > Custom mode: {:.0f}x{:.0f}@{}mHz", newState.resolution.x, newState.resolution.y, newState.refresh); } - if (head->committedProperties & COutputConfigurationHead::eCommittedProperties::OUTPUT_HEAD_COMMITTED_POSITION) - newRule.offset = head->state.position; + if (head->state.committedProperties & eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_POSITION) { + newState.position = head->state.position; + newState.committedProperties |= eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_POSITION; + LOGM(LOG, " > Position: {:.0f}, {:.0f}", head->state.position.x, head->state.position.y); + } - if (head->committedProperties & COutputConfigurationHead::eCommittedProperties::OUTPUT_HEAD_COMMITTED_ADAPTIVE_SYNC) - newRule.vrr = head->state.adaptiveSync; + if (head->state.committedProperties & eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_ADAPTIVE_SYNC) { + newState.adaptiveSync = head->state.adaptiveSync; + newState.committedProperties |= eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_ADAPTIVE_SYNC; + LOGM(LOG, " > vrr: {}", newState.adaptiveSync); + } - if (head->committedProperties & COutputConfigurationHead::eCommittedProperties::OUTPUT_HEAD_COMMITTED_SCALE) - newRule.scale = head->state.scale; + if (head->state.committedProperties & eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_SCALE) { + newState.scale = head->state.scale; + newState.committedProperties |= eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_SCALE; + LOGM(LOG, " > scale: {:.2f}", newState.scale); + } - if (head->committedProperties & COutputConfigurationHead::eCommittedProperties::OUTPUT_HEAD_COMMITTED_TRANSFORM) - newRule.transform = head->state.transform; + if (head->state.committedProperties & eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_TRANSFORM) { + newState.transform = head->state.transform; + newState.committedProperties |= eWlrOutputCommittedProperties::OUTPUT_HEAD_COMMITTED_TRANSFORM; + LOGM(LOG, " > transform: {}", (uint8_t)newState.transform); + } // reset properties for next set. - head->committedProperties = 0; + head->state.committedProperties = 0; - if (!g_pConfigManager->replaceMonitorRule(newRule)) - g_pConfigManager->appendMonitorRule(newRule); g_pConfigManager->m_bWantsMonitorReload = true; + + owner->monitorStates[PMONITOR->szName] = newState; } - LOGM(LOG, "Applied configuration"); + LOGM(LOG, "Saved configuration"); return true; } @@ -419,12 +445,12 @@ COutputConfigurationHead::COutputConfigurationHead(SPerror(ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ALREADY_SET, "Property already set"); return; } - committedProperties |= OUTPUT_HEAD_COMMITTED_MODE; + state.committedProperties |= OUTPUT_HEAD_COMMITTED_MODE; state.mode = MODE; LOGM(LOG, " | configHead for {}: set mode to {}x{}@{}", pMonitor->szName, MODE->getMode()->pixelSize.x, MODE->getMode()->pixelSize.y, MODE->getMode()->refreshRate); @@ -436,17 +462,22 @@ COutputConfigurationHead::COutputConfigurationHead(SPerror(ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ALREADY_SET, "Property already set"); return; } - if (w <= 0 || h <= 0 || refresh <= 100) { + if (w <= 0 || h <= 0 || refresh < 0) { resource->error(ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_INVALID_CUSTOM_MODE, "Invalid mode"); return; } - committedProperties |= OUTPUT_HEAD_COMMITTED_CUSTOM_MODE; + if (refresh == 0) { + LOGM(LOG, " | configHead for {}: refreshRate 0, using old refresh rate of {:.2f}Hz", pMonitor->szName, pMonitor->refreshRate); + refresh = std::round(pMonitor->refreshRate * 1000.F); + } + + state.committedProperties |= OUTPUT_HEAD_COMMITTED_CUSTOM_MODE; state.customMode = {{w, h}, (uint32_t)refresh}; LOGM(LOG, " | configHead for {}: set custom mode to {}x{}@{}", pMonitor->szName, w, h, refresh); @@ -458,12 +489,12 @@ COutputConfigurationHead::COutputConfigurationHead(SPerror(ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ALREADY_SET, "Property already set"); return; } - committedProperties |= OUTPUT_HEAD_COMMITTED_POSITION; + state.committedProperties |= OUTPUT_HEAD_COMMITTED_POSITION; state.position = {x, y}; LOGM(LOG, " | configHead for {}: set pos to {}, {}", pMonitor->szName, x, y); @@ -475,7 +506,7 @@ COutputConfigurationHead::COutputConfigurationHead(SPerror(ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ALREADY_SET, "Property already set"); return; } @@ -485,7 +516,7 @@ COutputConfigurationHead::COutputConfigurationHead(SPszName, transform); @@ -497,7 +528,7 @@ COutputConfigurationHead::COutputConfigurationHead(SPerror(ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ALREADY_SET, "Property already set"); return; } @@ -509,7 +540,7 @@ COutputConfigurationHead::COutputConfigurationHead(SPszName, scale); @@ -521,7 +552,7 @@ COutputConfigurationHead::COutputConfigurationHead(SPerror(ZWLR_OUTPUT_CONFIGURATION_HEAD_V1_ERROR_ALREADY_SET, "Property already set"); return; } @@ -531,7 +562,7 @@ COutputConfigurationHead::COutputConfigurationHead(SPszName, as); @@ -579,15 +610,15 @@ void COutputManagementProtocol::destroyResource(COutputConfigurationHead* resour } void COutputManagementProtocol::updateAllOutputs() { - for (auto& m : g_pCompositor->m_vRealMonitors) { - for (auto& mgr : m_vManagers) { + for (auto const& m : g_pCompositor->m_vRealMonitors) { + for (auto const& mgr : m_vManagers) { mgr->ensureMonitorSent(m.get()); } } } SP COutputManagementProtocol::headFromResource(wl_resource* r) { - for (auto& h : m_vHeads) { + for (auto const& h : m_vHeads) { if (h->resource->resource() == r) return h; } @@ -596,10 +627,21 @@ SP COutputManagementProtocol::headFromResource(wl_resource* r) { } SP COutputManagementProtocol::modeFromResource(wl_resource* r) { - for (auto& h : m_vModes) { + for (auto const& h : m_vModes) { if (h->resource->resource() == r) return h; } return nullptr; } + +SP COutputManagementProtocol::getOutputStateFor(SP pMonitor) { + for (auto const& m : m_vManagers) { + if (!m->monitorStates.contains(pMonitor->szName)) + continue; + + return makeShared(m->monitorStates.at(pMonitor->szName)); + } + + return nullptr; +} diff --git a/src/protocols/OutputManagement.hpp b/src/protocols/OutputManagement.hpp index 36478d0b..f4a84475 100644 --- a/src/protocols/OutputManagement.hpp +++ b/src/protocols/OutputManagement.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "WaylandProtocol.hpp" #include "wlr-output-management-unstable-v1.hpp" #include "../helpers/signal/Signal.hpp" @@ -13,6 +14,43 @@ class CMonitor; class COutputHead; class COutputMode; +struct SMonitorRule; + +enum eWlrOutputCommittedProperties : uint32_t { + OUTPUT_HEAD_COMMITTED_MODE = (1 << 0), + OUTPUT_HEAD_COMMITTED_CUSTOM_MODE = (1 << 1), + OUTPUT_HEAD_COMMITTED_POSITION = (1 << 2), + OUTPUT_HEAD_COMMITTED_TRANSFORM = (1 << 3), + OUTPUT_HEAD_COMMITTED_SCALE = (1 << 4), + OUTPUT_HEAD_COMMITTED_ADAPTIVE_SYNC = (1 << 5), +}; + +struct SWlrManagerOutputState { + uint32_t committedProperties = 0; + + WP mode; + struct { + Vector2D size; + uint32_t refresh = 0; + } customMode; + Vector2D position; + wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL; + float scale = 1.F; + bool adaptiveSync = false; + bool enabled = true; +}; + +struct SWlrManagerSavedOutputState { + uint32_t committedProperties = 0; + Vector2D resolution; + uint32_t refresh = 0; + Vector2D position; + wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL; + float scale = 1.F; + bool adaptiveSync = false; + bool enabled = true; +}; + class COutputManager { public: COutputManager(SP resource_); @@ -21,6 +59,9 @@ class COutputManager { void ensureMonitorSent(CMonitor* pMonitor); void sendDone(); + // holds the states for this manager. + std::unordered_map monitorStates; + private: SP resource; bool stopped = false; @@ -80,30 +121,9 @@ class COutputConfigurationHead { public: COutputConfigurationHead(SP resource_, CMonitor* pMonitor_); - bool good(); + bool good(); - enum eCommittedProperties : uint32_t { - OUTPUT_HEAD_COMMITTED_MODE = (1 << 0), - OUTPUT_HEAD_COMMITTED_CUSTOM_MODE = (1 << 1), - OUTPUT_HEAD_COMMITTED_POSITION = (1 << 2), - OUTPUT_HEAD_COMMITTED_TRANSFORM = (1 << 3), - OUTPUT_HEAD_COMMITTED_SCALE = (1 << 4), - OUTPUT_HEAD_COMMITTED_ADAPTIVE_SYNC = (1 << 5), - }; - - uint32_t committedProperties = 0; - - struct { - WP mode; - struct { - Vector2D size; - uint32_t refresh = 0; - } customMode; - Vector2D position; - wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL; - float scale = 1.F; - bool adaptiveSync = false; - } state; + SWlrManagerOutputState state; private: SP resource; @@ -136,6 +156,9 @@ class COutputManagementProtocol : public IWaylandProtocol { virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); + // doesn't have to return one + SP getOutputStateFor(SP pMonitor); + private: void destroyResource(COutputManager* resource); void destroyResource(COutputHead* resource); diff --git a/src/protocols/OutputPower.cpp b/src/protocols/OutputPower.cpp index 597b9871..0c324bf0 100644 --- a/src/protocols/OutputPower.cpp +++ b/src/protocols/OutputPower.cpp @@ -2,8 +2,6 @@ #include "../Compositor.hpp" #include "core/Output.hpp" -#define LOGM PROTO::outputPower->protoLog - COutputPower::COutputPower(SP resource_, CMonitor* pMonitor_) : resource(resource_), pMonitor(pMonitor_) { if (!resource->resource()) return; diff --git a/src/protocols/PointerConstraints.cpp b/src/protocols/PointerConstraints.cpp index fd15242d..0f2dd991 100644 --- a/src/protocols/PointerConstraints.cpp +++ b/src/protocols/PointerConstraints.cpp @@ -5,8 +5,6 @@ #include "../managers/SeatManager.hpp" #include "core/Compositor.hpp" -#define LOGM PROTO::constraints->protoLog - CPointerConstraint::CPointerConstraint(SP resource_, SP surf, wl_resource* region_, zwpPointerConstraintsV1Lifetime lifetime_) : resourceL(resource_), locked(true), lifetime(lifetime_) { if (!resource_->resource()) diff --git a/src/protocols/PointerGestures.cpp b/src/protocols/PointerGestures.cpp index 86510779..4eb74102 100644 --- a/src/protocols/PointerGestures.cpp +++ b/src/protocols/PointerGestures.cpp @@ -4,8 +4,6 @@ #include "core/Seat.hpp" #include "core/Compositor.hpp" -#define LOGM PROTO::pointerGestures->protoLog - CPointerGestureSwipe::CPointerGestureSwipe(SP resource_) : resource(resource_) { if (!resource->resource()) return; @@ -113,7 +111,7 @@ void CPointerGesturesProtocol::swipeBegin(uint32_t timeMs, uint32_t fingers) { const auto SERIAL = g_pSeatManager->nextSerial(g_pSeatManager->state.pointerFocusResource.lock()); - for (auto& sw : m_vSwipes) { + for (auto const& sw : m_vSwipes) { if (sw->resource->client() != FOCUSEDCLIENT) continue; @@ -127,7 +125,7 @@ void CPointerGesturesProtocol::swipeUpdate(uint32_t timeMs, const Vector2D& delt const auto FOCUSEDCLIENT = g_pSeatManager->state.pointerFocusResource->client(); - for (auto& sw : m_vSwipes) { + for (auto const& sw : m_vSwipes) { if (sw->resource->client() != FOCUSEDCLIENT) continue; @@ -143,7 +141,7 @@ void CPointerGesturesProtocol::swipeEnd(uint32_t timeMs, bool cancelled) { const auto SERIAL = g_pSeatManager->nextSerial(g_pSeatManager->state.pointerFocusResource.lock()); - for (auto& sw : m_vSwipes) { + for (auto const& sw : m_vSwipes) { if (sw->resource->client() != FOCUSEDCLIENT) continue; @@ -159,7 +157,7 @@ void CPointerGesturesProtocol::pinchBegin(uint32_t timeMs, uint32_t fingers) { const auto SERIAL = g_pSeatManager->nextSerial(g_pSeatManager->state.pointerFocusResource.lock()); - for (auto& sw : m_vPinches) { + for (auto const& sw : m_vPinches) { if (sw->resource->client() != FOCUSEDCLIENT) continue; @@ -173,7 +171,7 @@ void CPointerGesturesProtocol::pinchUpdate(uint32_t timeMs, const Vector2D& delt const auto FOCUSEDCLIENT = g_pSeatManager->state.pointerFocusResource->client(); - for (auto& sw : m_vPinches) { + for (auto const& sw : m_vPinches) { if (sw->resource->client() != FOCUSEDCLIENT) continue; @@ -189,7 +187,7 @@ void CPointerGesturesProtocol::pinchEnd(uint32_t timeMs, bool cancelled) { const auto SERIAL = g_pSeatManager->nextSerial(g_pSeatManager->state.pointerFocusResource.lock()); - for (auto& sw : m_vPinches) { + for (auto const& sw : m_vPinches) { if (sw->resource->client() != FOCUSEDCLIENT) continue; @@ -205,7 +203,7 @@ void CPointerGesturesProtocol::holdBegin(uint32_t timeMs, uint32_t fingers) { const auto SERIAL = g_pSeatManager->nextSerial(g_pSeatManager->state.pointerFocusResource.lock()); - for (auto& sw : m_vHolds) { + for (auto const& sw : m_vHolds) { if (sw->resource->client() != FOCUSEDCLIENT) continue; @@ -221,7 +219,7 @@ void CPointerGesturesProtocol::holdEnd(uint32_t timeMs, bool cancelled) { const auto SERIAL = g_pSeatManager->nextSerial(g_pSeatManager->state.pointerFocusResource.lock()); - for (auto& sw : m_vHolds) { + for (auto const& sw : m_vHolds) { if (sw->resource->client() != FOCUSEDCLIENT) continue; diff --git a/src/protocols/PresentationTime.cpp b/src/protocols/PresentationTime.cpp index a2fc270c..71f74cd5 100644 --- a/src/protocols/PresentationTime.cpp +++ b/src/protocols/PresentationTime.cpp @@ -6,8 +6,6 @@ #include "core/Output.hpp" #include -#define LOGM PROTO::presentation->protoLog - CQueuedPresentationData::CQueuedPresentationData(SP surf) : surface(surf) { ; } @@ -16,7 +14,7 @@ void CQueuedPresentationData::setPresentationType(bool zeroCopy_) { zeroCopy = zeroCopy_; } -void CQueuedPresentationData::attachMonitor(CMonitor* pMonitor_) { +void CQueuedPresentationData::attachMonitor(SP pMonitor_) { pMonitor = pMonitor_; } @@ -60,17 +58,23 @@ void CPresentationFeedback::sendQueued(SP data, timespe if (reportedFlags & Aquamarine::IOutput::AQ_OUTPUT_PRESENT_HW_COMPLETION) flags |= WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION; + time_t tv_sec = 0; + if (sizeof(time_t) > 4) + tv_sec = when->tv_sec >> 32; + if (data->wasPresented) - resource->sendPresented((uint32_t)(when->tv_sec >> 32), (uint32_t)(when->tv_sec & 0xFFFFFFFF), (uint32_t)(when->tv_nsec), untilRefreshNs, (uint32_t)(seq >> 32), + resource->sendPresented((uint32_t)tv_sec, (uint32_t)(when->tv_sec & 0xFFFFFFFF), (uint32_t)(when->tv_nsec), untilRefreshNs, (uint32_t)(seq >> 32), (uint32_t)(seq & 0xFFFFFFFF), (wpPresentationFeedbackKind)flags); else resource->sendDiscarded(); + + done = true; } CPresentationProtocol::CPresentationProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { static auto P = g_pHookSystem->hookDynamic("monitorRemoved", [this](void* self, SCallbackInfo& info, std::any param) { const auto PMONITOR = std::any_cast(param); - std::erase_if(m_vQueue, [PMONITOR](const auto& other) { return !other->surface || other->pMonitor == PMONITOR; }); + std::erase_if(m_vQueue, [PMONITOR](const auto& other) { return !other->surface || other->pMonitor.get() == PMONITOR; }); }); } @@ -103,7 +107,7 @@ void CPresentationProtocol::onGetFeedback(CWpPresentation* pMgr, wl_resource* su } } -void CPresentationProtocol::onPresented(CMonitor* pMonitor, timespec* when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags) { +void CPresentationProtocol::onPresented(SP pMonitor, timespec* when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags) { timespec now; timespec* presentedAt = when; if (!presentedAt) { @@ -112,12 +116,12 @@ void CPresentationProtocol::onPresented(CMonitor* pMonitor, timespec* when, uint when = &now; } - for (auto& feedback : m_vFeedbacks) { + for (auto const& feedback : m_vFeedbacks) { if (!feedback->surface) continue; - for (auto& data : m_vQueue) { - if (!data->surface || data->surface != feedback->surface) + for (auto const& data : m_vQueue) { + if (!data->surface || data->surface != feedback->surface || (data->pMonitor && data->pMonitor != pMonitor)) continue; feedback->sendQueued(data, when, untilRefreshNs, seq, reportedFlags); @@ -127,7 +131,7 @@ void CPresentationProtocol::onPresented(CMonitor* pMonitor, timespec* when, uint } std::erase_if(m_vFeedbacks, [](const auto& other) { return !other->surface || other->done; }); - std::erase_if(m_vQueue, [pMonitor](const auto& other) { return !other->surface || other->pMonitor == pMonitor || !other->pMonitor; }); + std::erase_if(m_vQueue, [pMonitor](const auto& other) { return !other->surface || other->pMonitor == pMonitor || !other->pMonitor || other->done; }); } void CPresentationProtocol::queueData(SP data) { diff --git a/src/protocols/PresentationTime.hpp b/src/protocols/PresentationTime.hpp index 2c6ce3e9..421bb838 100644 --- a/src/protocols/PresentationTime.hpp +++ b/src/protocols/PresentationTime.hpp @@ -14,19 +14,19 @@ class CQueuedPresentationData { CQueuedPresentationData(SP surf); void setPresentationType(bool zeroCopy); - void attachMonitor(CMonitor* pMonitor); + void attachMonitor(SP pMonitor); void presented(); void discarded(); + bool done = false; + private: bool wasPresented = false; bool zeroCopy = false; - CMonitor* pMonitor = nullptr; + WP pMonitor; WP surface; - DYNLISTENER(destroySurface); - friend class CPresentationFeedback; friend class CPresentationProtocol; }; @@ -53,7 +53,7 @@ class CPresentationProtocol : public IWaylandProtocol { virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); - void onPresented(CMonitor* pMonitor, timespec* when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags); + void onPresented(SP pMonitor, timespec* when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags); void queueData(SP data); private: diff --git a/src/protocols/PrimarySelection.cpp b/src/protocols/PrimarySelection.cpp index 78eb8d63..f1db2d65 100644 --- a/src/protocols/PrimarySelection.cpp +++ b/src/protocols/PrimarySelection.cpp @@ -4,8 +4,6 @@ #include "core/Seat.hpp" #include "../config/ConfigValue.hpp" -#define LOGM PROTO::primarySelection->protoLog - CPrimarySelectionOffer::CPrimarySelectionOffer(SP resource_, SP source_) : source(source_), resource(resource_) { if (!good()) return; @@ -40,7 +38,7 @@ void CPrimarySelectionOffer::sendData() { if (!source) return; - for (auto& m : source->mimes()) { + for (auto const& m : source->mimes()) { resource->sendOffer(m.c_str()); } } @@ -179,7 +177,7 @@ CPrimarySelectionManager::CPrimarySelectionManager(SPself = RESOURCE; device = RESOURCE; - for (auto& s : sources) { + for (auto const& s : sources) { if (!s) continue; s->device = RESOURCE; @@ -274,7 +272,7 @@ void CPrimarySelectionProtocol::sendSelectionToDevice(SP source) { - for (auto& o : m_vOffers) { + for (auto const& o : m_vOffers) { if (o->source && o->source->hasDnd()) continue; o->dead = true; @@ -323,7 +321,7 @@ void CPrimarySelectionProtocol::updateSelection() { } void CPrimarySelectionProtocol::onPointerFocus() { - for (auto& o : m_vOffers) { + for (auto const& o : m_vOffers) { o->dead = true; } diff --git a/src/protocols/RelativePointer.cpp b/src/protocols/RelativePointer.cpp index e4818758..16248c02 100644 --- a/src/protocols/RelativePointer.cpp +++ b/src/protocols/RelativePointer.cpp @@ -65,7 +65,7 @@ void CRelativePointerProtocol::sendRelativeMotion(uint64_t time, const Vector2D& const auto FOCUSED = g_pSeatManager->state.pointerFocusResource->client(); - for (auto& rp : m_vRelativePointers) { + for (auto const& rp : m_vRelativePointers) { if (FOCUSED != rp->client()) continue; diff --git a/src/protocols/Screencopy.cpp b/src/protocols/Screencopy.cpp index a8afba84..1b06a1fa 100644 --- a/src/protocols/Screencopy.cpp +++ b/src/protocols/Screencopy.cpp @@ -9,8 +9,6 @@ #include -#define LOGM PROTO::screencopy->protoLog - CScreencopyFrame::~CScreencopyFrame() { if (buffer && buffer->locked()) buffer->unlock(); @@ -40,17 +38,6 @@ CScreencopyFrame::CScreencopyFrame(SP resource_, int32_t g_pHyprRenderer->makeEGLCurrent(); - if (g_pHyprOpenGL->m_mMonitorRenderResources.contains(pMonitor)) { - const auto& RDATA = g_pHyprOpenGL->m_mMonitorRenderResources.at(pMonitor); - // bind the fb for its format. Suppress gl errors. -#ifndef GLES2 - glBindFramebuffer(GL_READ_FRAMEBUFFER, RDATA.offloadFB.m_iFb); -#else - glBindFramebuffer(GL_FRAMEBUFFER, RDATA.offloadFB.m_iFb); -#endif - } else - LOGM(ERR, "No RDATA in screencopy???"); - shmFormat = g_pHyprOpenGL->getPreferredReadFormat(pMonitor); if (shmFormat == DRM_FORMAT_INVALID) { LOGM(ERR, "No format supported by renderer in capture output"); @@ -59,6 +46,10 @@ CScreencopyFrame::CScreencopyFrame(SP resource_, int32_t return; } + // TODO: hack, we can't bit flip so we'll format flip heh, GL_BGRA_EXT wont work here + if (shmFormat == DRM_FORMAT_XRGB2101010 || shmFormat == DRM_FORMAT_ARGB2101010) + shmFormat = DRM_FORMAT_XBGR2101010; + const auto PSHMINFO = FormatUtils::getPixelFormatFromDRM(shmFormat); if (!PSHMINFO) { LOGM(ERR, "No pixel format supported by renderer in capture output"); @@ -164,7 +155,7 @@ void CScreencopyFrame::copy(CZwlrScreencopyFrameV1* pFrame, wl_resource* buffer_ lockedSWCursors = true; // TODO: make it per-monitor if (!PROTO::screencopy->m_bTimerArmed) { - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { g_pPointerManager->lockSoftwareForMonitor(m); } PROTO::screencopy->m_bTimerArmed = true; @@ -247,7 +238,7 @@ bool CScreencopyFrame::copyShm() { g_pHyprRenderer->makeEGLCurrent(); CFramebuffer fb; - fb.alloc(box.w, box.h, g_pHyprRenderer->isNvidia() ? DRM_FORMAT_XBGR8888 : pMonitor->output->state->state().drmFormat); + fb.alloc(box.w, box.h, pMonitor->output->state->state().drmFormat); if (!g_pHyprRenderer->beginRender(pMonitor, fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, &fb, true)) { LOGM(ERR, "Can't copy: failed to begin rendering"); @@ -374,7 +365,7 @@ CScreencopyProtocol::CScreencopyProtocol(const wl_interface* iface, const int& v std::nullopt, [this](SP self, void* data) { // TODO: make it per-monitor - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { g_pPointerManager->unlockSoftwareForMonitor(m); } m_bTimerArmed = false; @@ -403,12 +394,12 @@ void CScreencopyProtocol::bindManager(wl_client* client, void* data, uint32_t ve void CScreencopyProtocol::destroyResource(CScreencopyClient* client) { std::erase_if(m_vClients, [&](const auto& other) { return other.get() == client; }); std::erase_if(m_vFrames, [&](const auto& other) { return other->client.get() == client; }); - std::erase_if(m_vFramesAwaitingWrite, [&](const auto& other) { return other->client.get() == client; }); + std::erase_if(m_vFramesAwaitingWrite, [&](const auto& other) { return !other || other->client.get() == client; }); } void CScreencopyProtocol::destroyResource(CScreencopyFrame* frame) { std::erase_if(m_vFrames, [&](const auto& other) { return other.get() == frame; }); - std::erase_if(m_vFramesAwaitingWrite, [&](const auto& other) { return other.get() == frame; }); + std::erase_if(m_vFramesAwaitingWrite, [&](const auto& other) { return !other || other.get() == frame; }); } void CScreencopyProtocol::onOutputCommit(CMonitor* pMonitor) { @@ -420,9 +411,12 @@ void CScreencopyProtocol::onOutputCommit(CMonitor* pMonitor) { std::vector> framesToRemove; // share frame if correct output - for (auto& f : m_vFramesAwaitingWrite) { + for (auto const& f : m_vFramesAwaitingWrite) { + if (!f) + continue; + if (!f->pMonitor || !f->buffer) { - framesToRemove.push_back(f); + framesToRemove.emplace_back(f); continue; } @@ -434,10 +428,10 @@ void CScreencopyProtocol::onOutputCommit(CMonitor* pMonitor) { f->client->lastFrame.reset(); ++f->client->frameCounter; - framesToRemove.push_back(f); + framesToRemove.emplace_back(f); } - for (auto& f : framesToRemove) { - destroyResource(f.get()); + for (auto const& f : framesToRemove) { + std::erase(m_vFramesAwaitingWrite, f); } } diff --git a/src/protocols/Screencopy.hpp b/src/protocols/Screencopy.hpp index cbc56edb..e121d0fd 100644 --- a/src/protocols/Screencopy.hpp +++ b/src/protocols/Screencopy.hpp @@ -54,7 +54,7 @@ class CScreencopyFrame { bool good(); - SP self; + WP self; WP client; private: @@ -92,7 +92,7 @@ class CScreencopyProtocol : public IWaylandProtocol { private: std::vector> m_vFrames; - std::vector> m_vFramesAwaitingWrite; + std::vector> m_vFramesAwaitingWrite; std::vector> m_vClients; SP m_pSoftwareCursorTimer; diff --git a/src/protocols/SecurityContext.cpp b/src/protocols/SecurityContext.cpp new file mode 100644 index 00000000..b42c3a97 --- /dev/null +++ b/src/protocols/SecurityContext.cpp @@ -0,0 +1,215 @@ +#include "SecurityContext.hpp" +#include "../Compositor.hpp" +#include + +static int onListenFdEvent(int fd, uint32_t mask, void* data) { + auto sc = (CSecurityContext*)data; + sc->onListen(mask); + return 0; +} + +static int onCloseFdEvent(int fd, uint32_t mask, void* data) { + auto sc = (CSecurityContext*)data; + sc->onClose(mask); + return 0; +} + +SP CSecurityContextSandboxedClient::create(int clientFD_) { + auto p = SP(new CSecurityContextSandboxedClient(clientFD_)); + if (!p->client) + return nullptr; + return p; +} + +static void onSecurityContextClientDestroy(wl_listener* l, void* d) { + CSecurityContextSandboxedClient* client = wl_container_of(l, client, destroyListener); + client->onDestroy(); +} + +CSecurityContextSandboxedClient::CSecurityContextSandboxedClient(int clientFD_) : clientFD(clientFD_) { + client = wl_client_create(g_pCompositor->m_sWLDisplay, clientFD); + if (!client) + return; + + destroyListener.notify = ::onSecurityContextClientDestroy; + wl_client_add_destroy_late_listener(client, &destroyListener); +} + +CSecurityContextSandboxedClient::~CSecurityContextSandboxedClient() { + wl_list_remove(&destroyListener.link); + close(clientFD); +} + +void CSecurityContextSandboxedClient::onDestroy() { + std::erase_if(PROTO::securityContext->m_vSandboxedClients, [this](const auto& e) { return e.get() == this; }); +} + +CSecurityContext::CSecurityContext(SP resource_, int listenFD_, int closeFD_) : listenFD(listenFD_), closeFD(closeFD_), resource(resource_) { + if (!good()) + return; + + resource->setDestroy([this](CWpSecurityContextV1* r) { + LOGM(LOG, "security_context at 0x{:x}: resource destroyed, keeping context until fd hangup", (uintptr_t)this); + resource = nullptr; + }); + resource->setOnDestroy([this](CWpSecurityContextV1* r) { + LOGM(LOG, "security_context at 0x{:x}: resource destroyed, keeping context until fd hangup", (uintptr_t)this); + resource = nullptr; + }); + + LOGM(LOG, "New security_context at 0x{:x}", (uintptr_t)this); + + resource->setSetSandboxEngine([this](CWpSecurityContextV1* r, const char* engine) { + if (!sandboxEngine.empty()) { + r->error(WP_SECURITY_CONTEXT_V1_ERROR_ALREADY_SET, "Sandbox engine already set"); + return; + } + + if (committed) { + r->error(WP_SECURITY_CONTEXT_V1_ERROR_ALREADY_USED, "Context already committed"); + return; + } + + sandboxEngine = engine ? engine : "(null)"; + LOGM(LOG, "security_context at 0x{:x} sets engine to {}", (uintptr_t)this, sandboxEngine); + }); + + resource->setSetAppId([this](CWpSecurityContextV1* r, const char* appid) { + if (!appID.empty()) { + r->error(WP_SECURITY_CONTEXT_V1_ERROR_ALREADY_SET, "Sandbox appid already set"); + return; + } + + if (committed) { + r->error(WP_SECURITY_CONTEXT_V1_ERROR_ALREADY_USED, "Context already committed"); + return; + } + + appID = appid ? appid : "(null)"; + LOGM(LOG, "security_context at 0x{:x} sets appid to {}", (uintptr_t)this, appID); + }); + + resource->setSetInstanceId([this](CWpSecurityContextV1* r, const char* instance) { + if (!instanceID.empty()) { + r->error(WP_SECURITY_CONTEXT_V1_ERROR_ALREADY_SET, "Sandbox instance already set"); + return; + } + + if (committed) { + r->error(WP_SECURITY_CONTEXT_V1_ERROR_ALREADY_USED, "Context already committed"); + return; + } + + instanceID = instance ? instance : "(null)"; + LOGM(LOG, "security_context at 0x{:x} sets instance to {}", (uintptr_t)this, instanceID); + }); + + resource->setCommit([this](CWpSecurityContextV1* r) { + committed = true; + + LOGM(LOG, "security_context at 0x{:x} commits", (uintptr_t)this); + + listenSource = wl_event_loop_add_fd(g_pCompositor->m_sWLEventLoop, listenFD, WL_EVENT_READABLE, ::onListenFdEvent, this); + closeSource = wl_event_loop_add_fd(g_pCompositor->m_sWLEventLoop, closeFD, 0, ::onCloseFdEvent, this); + + if (!listenSource || !closeSource) { + r->noMemory(); + return; + } + }); +} + +CSecurityContext::~CSecurityContext() { + if (listenSource) + wl_event_source_remove(listenSource); + if (closeSource) + wl_event_source_remove(closeSource); +} + +bool CSecurityContext::good() { + return resource->resource(); +} + +void CSecurityContext::onListen(uint32_t mask) { + if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) { + LOGM(ERR, "security_context at 0x{:x} got an error in listen", (uintptr_t)this); + PROTO::securityContext->destroyContext(this); + return; + } + + if (!(mask & WL_EVENT_READABLE)) + return; + + int clientFD = accept(listenFD, nullptr, nullptr); + if (clientFD < 0) { + LOGM(ERR, "security_context at 0x{:x} couldn't accept", (uintptr_t)this); + return; + } + + auto newClient = CSecurityContextSandboxedClient::create(clientFD); + if (!newClient) { + LOGM(ERR, "security_context at 0x{:x} couldn't create a client", (uintptr_t)this); + close(clientFD); + return; + } + + PROTO::securityContext->m_vSandboxedClients.emplace_back(newClient); + + LOGM(LOG, "security_context at 0x{:x} got a new wl_client 0x{:x}", (uintptr_t)this, (uintptr_t)newClient->client); +} + +void CSecurityContext::onClose(uint32_t mask) { + if (!(mask & (WL_EVENT_ERROR | WL_EVENT_HANGUP))) + return; + + PROTO::securityContext->destroyContext(this); +} + +CSecurityContextManagerResource::CSecurityContextManagerResource(SP resource_) : resource(resource_) { + if (!good()) + return; + + resource->setDestroy([this](CWpSecurityContextManagerV1* r) { PROTO::securityContext->destroyResource(this); }); + resource->setOnDestroy([this](CWpSecurityContextManagerV1* r) { PROTO::securityContext->destroyResource(this); }); + + resource->setCreateListener([](CWpSecurityContextManagerV1* r, uint32_t id, int32_t lfd, int32_t cfd) { + const auto RESOURCE = + PROTO::securityContext->m_vContexts.emplace_back(makeShared(makeShared(r->client(), r->version(), id), lfd, cfd)); + + if (!RESOURCE->good()) { + r->noMemory(); + PROTO::securityContext->m_vContexts.pop_back(); + return; + } + }); +} + +bool CSecurityContextManagerResource::good() { + return resource->resource(); +} + +CSecurityContextProtocol::CSecurityContextProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { + ; +} + +void CSecurityContextProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + const auto RESOURCE = m_vManagers.emplace_back(makeShared(makeShared(client, ver, id))); + + if (!RESOURCE->good()) { + wl_client_post_no_memory(client); + m_vManagers.pop_back(); + return; + } +} + +void CSecurityContextProtocol::destroyResource(CSecurityContextManagerResource* res) { + std::erase_if(m_vManagers, [&](const auto& other) { return other.get() == res; }); +} + +void CSecurityContextProtocol::destroyContext(CSecurityContext* context) { + std::erase_if(m_vContexts, [&](const auto& other) { return other.get() == context; }); +} + +bool CSecurityContextProtocol::isClientSandboxed(const wl_client* client) { + return std::find_if(m_vSandboxedClients.begin(), m_vSandboxedClients.end(), [client](const auto& e) { return e->client == client; }) != m_vSandboxedClients.end(); +} diff --git a/src/protocols/SecurityContext.hpp b/src/protocols/SecurityContext.hpp new file mode 100644 index 00000000..76313bcf --- /dev/null +++ b/src/protocols/SecurityContext.hpp @@ -0,0 +1,84 @@ +#pragma once + +#include +#include +#include +#include "WaylandProtocol.hpp" +#include "security-context-v1.hpp" + +class CSecurityContext { + public: + CSecurityContext(SP resource_, int listenFD_, int closeFD_); + ~CSecurityContext(); + + bool good(); + + std::string sandboxEngine, appID, instanceID; + int listenFD = -1, closeFD = -1; + + void onListen(uint32_t mask); + void onClose(uint32_t mask); + + private: + SP resource; + + wl_event_source * listenSource = nullptr, *closeSource = nullptr; + + bool committed = false; +}; + +class CSecurityContextManagerResource { + public: + CSecurityContextManagerResource(SP resource_); + + bool good(); + + private: + SP resource; +}; + +class CSecurityContextSandboxedClient { + public: + static SP create(int clientFD); + ~CSecurityContextSandboxedClient(); + + void onDestroy(); + + wl_listener destroyListener; + + private: + CSecurityContextSandboxedClient(int clientFD_); + + wl_client* client = nullptr; + int clientFD = -1; + + friend class CSecurityContextProtocol; + friend class CSecurityContext; +}; + +class CSecurityContextProtocol : public IWaylandProtocol { + public: + CSecurityContextProtocol(const wl_interface* iface, const int& ver, const std::string& name); + + virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); + + bool isClientSandboxed(const wl_client* client); + + private: + void destroyResource(CSecurityContextManagerResource* resource); + + void destroyContext(CSecurityContext* context); + + // + std::vector> m_vManagers; + std::vector> m_vContexts; + std::vector> m_vSandboxedClients; + + friend class CSecurityContextManagerResource; + friend class CSecurityContext; + friend class CSecurityContextSandboxedClient; +}; + +namespace PROTO { + inline UP securityContext; +}; \ No newline at end of file diff --git a/src/protocols/ServerDecorationKDE.cpp b/src/protocols/ServerDecorationKDE.cpp index 42da52a9..c7b98a9c 100644 --- a/src/protocols/ServerDecorationKDE.cpp +++ b/src/protocols/ServerDecorationKDE.cpp @@ -1,8 +1,6 @@ #include "ServerDecorationKDE.hpp" #include "core/Compositor.hpp" -#define LOGM PROTO::serverDecorationKDE->protoLog - CServerDecorationKDE::CServerDecorationKDE(SP resource_, SP surf) : resource(resource_) { if (!good()) return; diff --git a/src/protocols/SessionLock.cpp b/src/protocols/SessionLock.cpp index df97413c..b1f701bf 100644 --- a/src/protocols/SessionLock.cpp +++ b/src/protocols/SessionLock.cpp @@ -5,8 +5,6 @@ #include "core/Compositor.hpp" #include "core/Output.hpp" -#define LOGM PROTO::sessionLock->protoLog - CSessionLockSurface::CSessionLockSurface(SP resource_, SP surface_, CMonitor* pMonitor_, WP owner_) : resource(resource_), sessionLock(owner_), pSurface(surface_), pMonitor(pMonitor_) { if (!resource->resource()) @@ -177,13 +175,6 @@ void CSessionLockProtocol::onLock(CExtSessionLockManagerV1* pMgr, uint32_t id) { return; } - if (locked) { - LOGM(ERR, "Tried to lock a locked session"); - RESOURCE->inert = true; - RESOURCE->resource->sendFinished(); - return; - } - events.newLock.emit(RESOURCE); locked = true; @@ -196,7 +187,7 @@ void CSessionLockProtocol::onGetLockSurface(CExtSessionLockV1* lock, uint32_t id auto PMONITOR = CWLOutputResource::fromResource(output)->monitor.get(); SP sessionLock; - for (auto& l : m_vLocks) { + for (auto const& l : m_vLocks) { if (l->resource.get() == lock) { sessionLock = l; break; diff --git a/src/protocols/ShortcutsInhibit.cpp b/src/protocols/ShortcutsInhibit.cpp index 1a0433e6..308b385d 100644 --- a/src/protocols/ShortcutsInhibit.cpp +++ b/src/protocols/ShortcutsInhibit.cpp @@ -3,8 +3,6 @@ #include "../Compositor.hpp" #include "core/Compositor.hpp" -#define LOGM PROTO::shortcutsInhibit->protoLog - CKeyboardShortcutsInhibitor::CKeyboardShortcutsInhibitor(SP resource_, SP surf) : resource(resource_), pSurface(surf) { if (!resource->resource()) return; @@ -50,7 +48,7 @@ void CKeyboardShortcutsInhibitProtocol::onInhibit(CZwpKeyboardShortcutsInhibitMa SP surf = CWLSurfaceResource::fromResource(surface); const auto CLIENT = pMgr->client(); - for (auto& in : m_vInhibitors) { + for (auto const& in : m_vInhibitors) { if (in->surface() != surf) continue; @@ -76,7 +74,7 @@ bool CKeyboardShortcutsInhibitProtocol::isInhibited() { if (const auto PWINDOW = g_pCompositor->getWindowFromSurface(g_pCompositor->m_pLastFocus.lock()); PWINDOW && PWINDOW->m_sWindowData.noShortcutsInhibit.valueOrDefault()) return false; - for (auto& in : m_vInhibitors) { + for (auto const& in : m_vInhibitors) { if (in->surface() != g_pCompositor->m_pLastFocus) continue; diff --git a/src/protocols/SinglePixel.cpp b/src/protocols/SinglePixel.cpp new file mode 100644 index 00000000..d800539d --- /dev/null +++ b/src/protocols/SinglePixel.cpp @@ -0,0 +1,128 @@ +#include "SinglePixel.hpp" +#include +#include "render/Renderer.hpp" + +CSinglePixelBuffer::CSinglePixelBuffer(uint32_t id, wl_client* client, CColor col_) { + LOGM(LOG, "New single-pixel buffer with color 0x{:x}", col_.getAsHex()); + + color = col_.getAsHex(); + + g_pHyprRenderer->makeEGLCurrent(); + + opaque = col_.a >= 1.F; + + texture = makeShared(DRM_FORMAT_ARGB8888, (uint8_t*)&color, 4, Vector2D{1, 1}); + + resource = CWLBufferResource::create(makeShared(client, 1, id)); + + success = texture->m_iTexID; + + size = {1, 1}; + + if (!success) + Debug::log(ERR, "Failed creating a single pixel texture: null texture id"); +} + +CSinglePixelBuffer::~CSinglePixelBuffer() { + ; +} + +Aquamarine::eBufferCapability CSinglePixelBuffer::caps() { + return Aquamarine::eBufferCapability::BUFFER_CAPABILITY_DATAPTR; +} + +Aquamarine::eBufferType CSinglePixelBuffer::type() { + return Aquamarine::eBufferType::BUFFER_TYPE_SHM; +} + +bool CSinglePixelBuffer::isSynchronous() { + return true; +} + +void CSinglePixelBuffer::update(const CRegion& damage) { + ; +} + +Aquamarine::SDMABUFAttrs CSinglePixelBuffer::dmabuf() { + return {.success = false}; +} + +std::tuple CSinglePixelBuffer::beginDataPtr(uint32_t flags) { + return {(uint8_t*)&color, DRM_FORMAT_ARGB8888, 4}; +} + +void CSinglePixelBuffer::endDataPtr() { + ; +} + +bool CSinglePixelBuffer::good() { + return resource->good(); +} + +CSinglePixelBufferResource::CSinglePixelBufferResource(uint32_t id, wl_client* client, CColor color) { + buffer = makeShared(id, client, color); + + if (!buffer->good()) + return; + + buffer->resource->buffer = buffer; + + listeners.bufferResourceDestroy = buffer->events.destroy.registerListener([this](std::any d) { + listeners.bufferResourceDestroy.reset(); + PROTO::singlePixel->destroyResource(this); + }); +} + +CSinglePixelBufferResource::~CSinglePixelBufferResource() { + ; +} + +bool CSinglePixelBufferResource::good() { + return buffer->good(); +} + +CSinglePixelBufferManagerResource::CSinglePixelBufferManagerResource(SP resource_) : resource(resource_) { + if (!good()) + return; + + resource->setDestroy([this](CWpSinglePixelBufferManagerV1* r) { PROTO::singlePixel->destroyResource(this); }); + resource->setOnDestroy([this](CWpSinglePixelBufferManagerV1* r) { PROTO::singlePixel->destroyResource(this); }); + + resource->setCreateU32RgbaBuffer([this](CWpSinglePixelBufferManagerV1* res, uint32_t id, uint32_t r, uint32_t g, uint32_t b, uint32_t a) { + CColor color{r / (float)std::numeric_limits::max(), g / (float)std::numeric_limits::max(), b / (float)std::numeric_limits::max(), + a / (float)std::numeric_limits::max()}; + const auto RESOURCE = PROTO::singlePixel->m_vBuffers.emplace_back(makeShared(id, resource->client(), color)); + + if (!RESOURCE->good()) { + res->noMemory(); + PROTO::singlePixel->m_vBuffers.pop_back(); + return; + } + }); +} + +bool CSinglePixelBufferManagerResource::good() { + return resource->resource(); +} + +CSinglePixelProtocol::CSinglePixelProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { + ; +} + +void CSinglePixelProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + const auto RESOURCE = m_vManagers.emplace_back(makeShared(makeShared(client, ver, id))); + + if (!RESOURCE->good()) { + wl_client_post_no_memory(client); + m_vManagers.pop_back(); + return; + } +} + +void CSinglePixelProtocol::destroyResource(CSinglePixelBufferManagerResource* res) { + std::erase_if(m_vManagers, [&](const auto& other) { return other.get() == res; }); +} + +void CSinglePixelProtocol::destroyResource(CSinglePixelBufferResource* surf) { + std::erase_if(m_vBuffers, [&](const auto& other) { return other.get() == surf; }); +} diff --git a/src/protocols/SinglePixel.hpp b/src/protocols/SinglePixel.hpp new file mode 100644 index 00000000..ab74825c --- /dev/null +++ b/src/protocols/SinglePixel.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include +#include +#include +#include "WaylandProtocol.hpp" +#include "single-pixel-buffer-v1.hpp" +#include "types/Buffer.hpp" + +class CSinglePixelBuffer : public IHLBuffer { + public: + CSinglePixelBuffer(uint32_t id, wl_client* client, CColor col); + virtual ~CSinglePixelBuffer(); + + virtual Aquamarine::eBufferCapability caps(); + virtual Aquamarine::eBufferType type(); + virtual bool isSynchronous(); + virtual void update(const CRegion& damage); + virtual Aquamarine::SDMABUFAttrs dmabuf(); + virtual std::tuple beginDataPtr(uint32_t flags); + virtual void endDataPtr(); + // + bool good(); + bool success = false; + + private: + uint32_t color = 0x00000000; + + struct { + CHyprSignalListener resourceDestroy; + } listeners; +}; + +class CSinglePixelBufferResource { + public: + CSinglePixelBufferResource(uint32_t id, wl_client* client, CColor color); + ~CSinglePixelBufferResource(); + + bool good(); + + private: + SP buffer; + + struct { + CHyprSignalListener bufferResourceDestroy; + } listeners; +}; + +class CSinglePixelBufferManagerResource { + public: + CSinglePixelBufferManagerResource(SP resource_); + + bool good(); + + private: + SP resource; +}; + +class CSinglePixelProtocol : public IWaylandProtocol { + public: + CSinglePixelProtocol(const wl_interface* iface, const int& ver, const std::string& name); + + virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); + + private: + void destroyResource(CSinglePixelBufferManagerResource* resource); + void destroyResource(CSinglePixelBufferResource* resource); + + // + std::vector> m_vManagers; + std::vector> m_vBuffers; + + friend class CSinglePixelBufferManagerResource; + friend class CSinglePixelBufferResource; +}; + +namespace PROTO { + inline UP singlePixel; +}; \ No newline at end of file diff --git a/src/protocols/Tablet.cpp b/src/protocols/Tablet.cpp index 72c7cfde..d7f741b9 100644 --- a/src/protocols/Tablet.cpp +++ b/src/protocols/Tablet.cpp @@ -7,8 +7,6 @@ #include #include -#define LOGM PROTO::tablet->protoLog - CTabletPadStripV2Resource::CTabletPadStripV2Resource(SP resource_, uint32_t id_) : id(id_), resource(resource_) { if (!good()) return; @@ -98,7 +96,7 @@ bool CTabletPadV2Resource::good() { void CTabletPadV2Resource::sendData() { // this is dodgy as fuck. I hate wl_array. it's expanded wl_array_for_each because C++ would complain about the implicit casts - for (auto& p : pad->aq()->paths) { + for (auto const& p : pad->aq()->paths) { resource->sendPath(p.c_str()); } @@ -142,7 +140,7 @@ void CTabletV2Resource::sendData() { resource->sendName(tablet->deviceName.c_str()); resource->sendId(tablet->aq()->usbVendorID, tablet->aq()->usbProductID); - for (auto& p : tablet->aq()->paths) { + for (auto const& p : tablet->aq()->paths) { resource->sendPath(p.c_str()); } @@ -289,21 +287,21 @@ void CTabletSeat::sendTablet(SP tablet) { } void CTabletSeat::sendData() { - for (auto& tw : PROTO::tablet->tablets) { + for (auto const& tw : PROTO::tablet->tablets) { if (tw.expired()) continue; sendTablet(tw.lock()); } - for (auto& tw : PROTO::tablet->tools) { + for (auto const& tw : PROTO::tablet->tools) { if (tw.expired()) continue; sendTool(tw.lock()); } - for (auto& tw : PROTO::tablet->pads) { + for (auto const& tw : PROTO::tablet->pads) { if (tw.expired()) continue; @@ -369,7 +367,7 @@ void CTabletV2Protocol::onGetSeat(CZwpTabletManagerV2* pMgr, uint32_t id, wl_res } void CTabletV2Protocol::registerDevice(SP tablet) { - for (auto& s : m_vSeats) { + for (auto const& s : m_vSeats) { s->sendTablet(tablet); } @@ -377,7 +375,7 @@ void CTabletV2Protocol::registerDevice(SP tablet) { } void CTabletV2Protocol::registerDevice(SP tool) { - for (auto& s : m_vSeats) { + for (auto const& s : m_vSeats) { s->sendTool(tool); } @@ -385,7 +383,7 @@ void CTabletV2Protocol::registerDevice(SP tool) { } void CTabletV2Protocol::registerDevice(SP pad) { - for (auto& s : m_vSeats) { + for (auto const& s : m_vSeats) { s->sendPad(pad); } @@ -393,7 +391,7 @@ void CTabletV2Protocol::registerDevice(SP pad) { } void CTabletV2Protocol::unregisterDevice(SP tablet) { - for (auto& t : m_vTablets) { + for (auto const& t : m_vTablets) { if (t->tablet == tablet) { t->resource->sendRemoved(); t->inert = true; @@ -403,7 +401,7 @@ void CTabletV2Protocol::unregisterDevice(SP tablet) { } void CTabletV2Protocol::unregisterDevice(SP tool) { - for (auto& t : m_vTools) { + for (auto const& t : m_vTools) { if (t->tool == tool) { t->resource->sendRemoved(); t->inert = true; @@ -413,7 +411,7 @@ void CTabletV2Protocol::unregisterDevice(SP tool) { } void CTabletV2Protocol::unregisterDevice(SP pad) { - for (auto& t : m_vPads) { + for (auto const& t : m_vPads) { if (t->pad == pad) { t->resource->sendRemoved(); t->inert = true; @@ -428,7 +426,7 @@ void CTabletV2Protocol::recheckRegisteredDevices() { std::erase_if(pads, [](const auto& e) { return e.expired(); }); // now we need to send removed events - for (auto& t : m_vTablets) { + for (auto const& t : m_vTablets) { if (!t->tablet.expired() || t->inert) continue; @@ -436,7 +434,7 @@ void CTabletV2Protocol::recheckRegisteredDevices() { t->inert = true; } - for (auto& t : m_vTools) { + for (auto const& t : m_vTools) { if (!t->tool.expired() || t->inert) continue; @@ -450,7 +448,7 @@ void CTabletV2Protocol::recheckRegisteredDevices() { t->inert = true; } - for (auto& t : m_vPads) { + for (auto const& t : m_vPads) { if (!t->pad.expired() || t->inert) continue; @@ -460,7 +458,7 @@ void CTabletV2Protocol::recheckRegisteredDevices() { } void CTabletV2Protocol::pressure(SP tool, double value) { - for (auto& t : m_vTools) { + for (auto const& t : m_vTools) { if (t->tool != tool || !t->current) continue; @@ -470,7 +468,7 @@ void CTabletV2Protocol::pressure(SP tool, double value) { } void CTabletV2Protocol::distance(SP tool, double value) { - for (auto& t : m_vTools) { + for (auto const& t : m_vTools) { if (t->tool != tool || !t->current) continue; @@ -480,7 +478,7 @@ void CTabletV2Protocol::distance(SP tool, double value) { } void CTabletV2Protocol::rotation(SP tool, double value) { - for (auto& t : m_vTools) { + for (auto const& t : m_vTools) { if (t->tool != tool || !t->current) continue; @@ -490,7 +488,7 @@ void CTabletV2Protocol::rotation(SP tool, double value) { } void CTabletV2Protocol::slider(SP tool, double value) { - for (auto& t : m_vTools) { + for (auto const& t : m_vTools) { if (t->tool != tool || !t->current) continue; @@ -500,7 +498,7 @@ void CTabletV2Protocol::slider(SP tool, double value) { } void CTabletV2Protocol::wheel(SP tool, double value) { - for (auto& t : m_vTools) { + for (auto const& t : m_vTools) { if (t->tool != tool || !t->current) continue; @@ -510,7 +508,7 @@ void CTabletV2Protocol::wheel(SP tool, double value) { } void CTabletV2Protocol::tilt(SP tool, const Vector2D& value) { - for (auto& t : m_vTools) { + for (auto const& t : m_vTools) { if (t->tool != tool || !t->current) continue; @@ -520,7 +518,7 @@ void CTabletV2Protocol::tilt(SP tool, const Vector2D& value) { } void CTabletV2Protocol::up(SP tool) { - for (auto& t : m_vTools) { + for (auto const& t : m_vTools) { if (t->tool != tool || !t->current) continue; @@ -530,7 +528,7 @@ void CTabletV2Protocol::up(SP tool) { } void CTabletV2Protocol::down(SP tool) { - for (auto& t : m_vTools) { + for (auto const& t : m_vTools) { if (t->tool != tool || !t->current) continue; @@ -547,7 +545,7 @@ void CTabletV2Protocol::proximityIn(SP tool, SP tablet, SP SP toolResource; SP tabletResource; - for (auto& t : m_vTools) { + for (auto const& t : m_vTools) { if (t->tool != tool || t->resource->client() != CLIENT) continue; @@ -561,7 +559,7 @@ void CTabletV2Protocol::proximityIn(SP tool, SP tablet, SP toolResource = t; - for (auto& tab : m_vTablets) { + for (auto const& tab : m_vTablets) { if (tab->tablet != tablet) continue; @@ -589,7 +587,7 @@ void CTabletV2Protocol::proximityIn(SP tool, SP tablet, SP } void CTabletV2Protocol::proximityOut(SP tool) { - for (auto& t : m_vTools) { + for (auto const& t : m_vTools) { if (t->tool != tool || !t->current) continue; @@ -601,7 +599,7 @@ void CTabletV2Protocol::proximityOut(SP tool) { } void CTabletV2Protocol::buttonTool(SP tool, uint32_t button, uint32_t state) { - for (auto& t : m_vTools) { + for (auto const& t : m_vTools) { if (t->tool != tool || !t->current) continue; @@ -612,7 +610,7 @@ void CTabletV2Protocol::buttonTool(SP tool, uint32_t button, uint32 } void CTabletV2Protocol::motion(SP tool, const Vector2D& value) { - for (auto& t : m_vTools) { + for (auto const& t : m_vTools) { if (t->tool != tool || !t->current) continue; @@ -622,7 +620,7 @@ void CTabletV2Protocol::motion(SP tool, const Vector2D& value) { } void CTabletV2Protocol::mode(SP pad, uint32_t group, uint32_t mode, uint32_t timeMs) { - for (auto& t : m_vPads) { + for (auto const& t : m_vPads) { if (t->pad != pad) continue; if (t->groups.size() <= group) { @@ -635,7 +633,7 @@ void CTabletV2Protocol::mode(SP pad, uint32_t group, uint32_t mode, } void CTabletV2Protocol::buttonPad(SP pad, uint32_t button, uint32_t timeMs, uint32_t state) { - for (auto& t : m_vPads) { + for (auto const& t : m_vPads) { if (t->pad != pad) continue; t->resource->sendButton(timeMs, button, zwpTabletToolV2ButtonState{state}); diff --git a/src/protocols/TearingControl.cpp b/src/protocols/TearingControl.cpp index 7f3c0a18..66a4efd2 100644 --- a/src/protocols/TearingControl.cpp +++ b/src/protocols/TearingControl.cpp @@ -38,7 +38,7 @@ void CTearingControlProtocol::onControllerDestroy(CTearingControl* control) { } void CTearingControlProtocol::onWindowDestroy(PHLWINDOW pWindow) { - for (auto& c : m_vTearingControllers) { + for (auto const& c : m_vTearingControllers) { if (c->pWindow.lock() == pWindow) c->pWindow.reset(); } @@ -52,7 +52,7 @@ CTearingControl::CTearingControl(SP resource_, SPsetDestroy([this](CWpTearingControlV1* res) { PROTO::tearing->onControllerDestroy(this); }); resource->setSetPresentationHint([this](CWpTearingControlV1* res, wpTearingControlV1PresentationHint hint) { this->onHint(hint); }); - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (w->m_pWLSurface->resource() == surf_) { pWindow = w; break; diff --git a/src/protocols/TextInputV1.cpp b/src/protocols/TextInputV1.cpp index 78e910cb..dad74b53 100644 --- a/src/protocols/TextInputV1.cpp +++ b/src/protocols/TextInputV1.cpp @@ -3,8 +3,6 @@ #include "../Compositor.hpp" #include "core/Compositor.hpp" -#define LOGM PROTO::textInputV1->protoLog - CTextInputV1::~CTextInputV1() { events.destroy.emit(); } @@ -33,6 +31,7 @@ CTextInputV1::CTextInputV1(SP resource_) : resource(resource_) resource->setReset([this](CZwpTextInputV1* pMgr) { pendingSurrounding.isPending = false; pendingContentType.isPending = false; + events.reset.emit(); }); resource->setSetSurroundingText( diff --git a/src/protocols/TextInputV1.hpp b/src/protocols/TextInputV1.hpp index c85a1f31..d3b0d71b 100644 --- a/src/protocols/TextInputV1.hpp +++ b/src/protocols/TextInputV1.hpp @@ -37,6 +37,7 @@ class CTextInputV1 { CSignal onCommit; CSignal enable; CSignal disable; + CSignal reset; CSignal destroy; } events; @@ -59,7 +60,7 @@ class CTextInputV1 { friend class CTextInputV1Protocol; }; -class CTextInputV1Protocol : IWaylandProtocol { +class CTextInputV1Protocol : public IWaylandProtocol { public: CTextInputV1Protocol(const wl_interface* iface, const int& ver, const std::string& name); diff --git a/src/protocols/TextInputV3.cpp b/src/protocols/TextInputV3.cpp index 1302a57f..30374104 100644 --- a/src/protocols/TextInputV3.cpp +++ b/src/protocols/TextInputV3.cpp @@ -2,8 +2,6 @@ #include #include "core/Compositor.hpp" -#define LOGM PROTO::textInputV3->protoLog - void CTextInputV3::SState::reset() { cause = ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_INPUT_METHOD; surrounding.updated = false; @@ -21,18 +19,22 @@ CTextInputV3::CTextInputV3(SP resource_) : resource(resource_) resource->setOnDestroy([this](CZwpTextInputV3* r) { PROTO::textInputV3->destroyTextInput(this); }); resource->setCommit([this](CZwpTextInputV3* r) { - bool wasEnabled = current.enabled; + bool wasEnabled = current.enabled.value; current = pending; serial++; - if (wasEnabled && !current.enabled) { - current.reset(); + if (wasEnabled && !current.enabled.value) events.disable.emit(); - } else if (!wasEnabled && current.enabled) + else if (!wasEnabled && current.enabled.value) events.enable.emit(); + else if (current.enabled.value && current.enabled.isEnablePending && current.enabled.isDisablePending) + events.reset.emit(); else events.onCommit.emit(); + + pending.enabled.isEnablePending = false; + pending.enabled.isDisablePending = false; }); resource->setSetSurroundingText([this](CZwpTextInputV3* r, const char* text, int32_t cursor, int32_t anchor) { @@ -55,11 +57,15 @@ CTextInputV3::CTextInputV3(SP resource_) : resource(resource_) pending.box.cursorBox = {x, y, w, h}; }); - resource->setEnable([this](CZwpTextInputV3* r) { pending.enabled = true; }); + resource->setEnable([this](CZwpTextInputV3* r) { + pending.reset(); + pending.enabled.value = true; + pending.enabled.isEnablePending = true; + }); resource->setDisable([this](CZwpTextInputV3* r) { - pending.enabled = false; - pending.reset(); + pending.enabled.value = false; + pending.enabled.isDisablePending = true; }); } diff --git a/src/protocols/TextInputV3.hpp b/src/protocols/TextInputV3.hpp index 9f6284dc..ba8b75e6 100644 --- a/src/protocols/TextInputV3.hpp +++ b/src/protocols/TextInputV3.hpp @@ -31,6 +31,7 @@ class CTextInputV3 { CSignal onCommit; CSignal enable; CSignal disable; + CSignal reset; CSignal destroy; } events; @@ -53,7 +54,11 @@ class CTextInputV3 { CBox cursorBox; } box; - bool enabled = false; + struct { + bool isEnablePending = false; + bool isDisablePending = false; + bool value = false; + } enabled; zwpTextInputV3ChangeCause cause = ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_INPUT_METHOD; diff --git a/src/protocols/ToplevelExport.cpp b/src/protocols/ToplevelExport.cpp index fb3fde2b..66df5926 100644 --- a/src/protocols/ToplevelExport.cpp +++ b/src/protocols/ToplevelExport.cpp @@ -8,8 +8,6 @@ #include -#define LOGM PROTO::toplevelExport->protoLog - CToplevelExportClient::CToplevelExportClient(SP resource_) : resource(resource_) { if (!good()) return; @@ -247,7 +245,7 @@ bool CToplevelExportFrame::copyShm(timespec* now) { g_pHyprRenderer->makeEGLCurrent(); CFramebuffer outFB; - outFB.alloc(PMONITOR->vecPixelSize.x, PMONITOR->vecPixelSize.y, g_pHyprRenderer->isNvidia() ? DRM_FORMAT_XBGR8888 : PMONITOR->output->state->state().drmFormat); + outFB.alloc(PMONITOR->vecPixelSize.x, PMONITOR->vecPixelSize.y, PMONITOR->output->state->state().drmFormat); if (overlayCursor) { g_pPointerManager->lockSoftwareForMonitor(PMONITOR->self.lock()); @@ -356,12 +354,12 @@ void CToplevelExportProtocol::bindManager(wl_client* client, void* data, uint32_ void CToplevelExportProtocol::destroyResource(CToplevelExportClient* client) { std::erase_if(m_vClients, [&](const auto& other) { return other.get() == client; }); std::erase_if(m_vFrames, [&](const auto& other) { return other->client.get() == client; }); - std::erase_if(m_vFramesAwaitingWrite, [&](const auto& other) { return other->client.get() == client; }); + std::erase_if(m_vFramesAwaitingWrite, [&](const auto& other) { return !other || other->client.get() == client; }); } void CToplevelExportProtocol::destroyResource(CToplevelExportFrame* frame) { std::erase_if(m_vFrames, [&](const auto& other) { return other.get() == frame; }); - std::erase_if(m_vFramesAwaitingWrite, [&](const auto& other) { return other.get() == frame; }); + std::erase_if(m_vFramesAwaitingWrite, [&](const auto& other) { return !other || other.get() == frame; }); } void CToplevelExportProtocol::onOutputCommit(CMonitor* pMonitor) { @@ -371,12 +369,18 @@ void CToplevelExportProtocol::onOutputCommit(CMonitor* pMonitor) { std::vector> framesToRemove; // share frame if correct output - for (auto& f : m_vFramesAwaitingWrite) { - if (!f->pWindow || !validMapped(f->pWindow)) { - framesToRemove.push_back(f); + for (auto const& f : m_vFramesAwaitingWrite) { + if (!f) + continue; + + if (!validMapped(f->pWindow)) { + framesToRemove.emplace_back(f); continue; } + if (!f->pWindow) + continue; + const auto PWINDOW = f->pWindow; if (pMonitor != g_pCompositor->getMonitorFromID(PWINDOW->m_iMonitorID)) @@ -395,13 +399,13 @@ void CToplevelExportProtocol::onOutputCommit(CMonitor* pMonitor) { framesToRemove.push_back(f); } - for (auto& f : framesToRemove) { - destroyResource(f.get()); + for (auto const& f : framesToRemove) { + std::erase(m_vFramesAwaitingWrite, f); } } void CToplevelExportProtocol::onWindowUnmap(PHLWINDOW pWindow) { - for (auto& f : m_vFrames) { + for (auto const& f : m_vFrames) { if (f->pWindow == pWindow) f->pWindow.reset(); } diff --git a/src/protocols/ToplevelExport.hpp b/src/protocols/ToplevelExport.hpp index 638b69f0..9686431b 100644 --- a/src/protocols/ToplevelExport.hpp +++ b/src/protocols/ToplevelExport.hpp @@ -45,7 +45,7 @@ class CToplevelExportFrame { bool good(); - SP self; + WP self; WP client; private: @@ -85,7 +85,7 @@ class CToplevelExportProtocol : IWaylandProtocol { private: std::vector> m_vClients; std::vector> m_vFrames; - std::vector> m_vFramesAwaitingWrite; + std::vector> m_vFramesAwaitingWrite; void shareFrame(CToplevelExportFrame* frame); bool copyFrameDmabuf(CToplevelExportFrame* frame, timespec* now); diff --git a/src/protocols/Viewporter.cpp b/src/protocols/Viewporter.cpp index 78f3039f..58cb851d 100644 --- a/src/protocols/Viewporter.cpp +++ b/src/protocols/Viewporter.cpp @@ -2,8 +2,6 @@ #include "core/Compositor.hpp" #include -#define LOGM PROTO::viewport->protoLog - CViewportResource::CViewportResource(SP resource_, SP surface_) : surface(surface_), resource(resource_) { if (!good()) return; diff --git a/src/protocols/VirtualKeyboard.cpp b/src/protocols/VirtualKeyboard.cpp index 2642ec11..bb51315d 100644 --- a/src/protocols/VirtualKeyboard.cpp +++ b/src/protocols/VirtualKeyboard.cpp @@ -2,8 +2,6 @@ #include #include "../devices/IKeyboard.hpp" -#define LOGM PROTO::virtualKeyboard->protoLog - CVirtualKeyboardV1Resource::CVirtualKeyboardV1Resource(SP resource_) : resource(resource_) { if (!good()) return; @@ -110,7 +108,7 @@ void CVirtualKeyboardV1Resource::releasePressed() { timespec now; clock_gettime(CLOCK_MONOTONIC, &now); - for (auto& p : pressed) { + for (auto const& p : pressed) { events.key.emit(IKeyboard::SKeyEvent{ .timeMs = now.tv_sec * 1000 + now.tv_nsec / 1000000, .keycode = p, diff --git a/src/protocols/VirtualPointer.cpp b/src/protocols/VirtualPointer.cpp index 8626241a..eb92a640 100644 --- a/src/protocols/VirtualPointer.cpp +++ b/src/protocols/VirtualPointer.cpp @@ -1,8 +1,6 @@ #include "VirtualPointer.hpp" #include "core/Output.hpp" -#define LOGM PROTO::virtualPointer->protoLog - CVirtualPointerV1Resource::CVirtualPointerV1Resource(SP resource_, WP boundOutput_) : boundOutput(boundOutput_), resource(resource_) { if (!good()) return; diff --git a/src/protocols/WaylandProtocol.cpp b/src/protocols/WaylandProtocol.cpp index 954f160d..00b112b0 100644 --- a/src/protocols/WaylandProtocol.cpp +++ b/src/protocols/WaylandProtocol.cpp @@ -21,7 +21,7 @@ IWaylandProtocol::IWaylandProtocol(const wl_interface* iface, const int& ver, co m_pGlobal = wl_global_create(g_pCompositor->m_sWLDisplay, iface, ver, this, &bindManagerInternal); if (!m_pGlobal) { - protoLog(ERR, "could not create a global"); + LOGM(ERR, "could not create a global [{}]", m_szName); return; } @@ -30,7 +30,7 @@ IWaylandProtocol::IWaylandProtocol(const wl_interface* iface, const int& ver, co m_liDisplayDestroy.parent = this; wl_display_add_destroy_listener(g_pCompositor->m_sWLDisplay, &m_liDisplayDestroy.listener); - protoLog(LOG, "Registered global"); + LOGM(LOG, "Registered global [{}]", m_szName); } IWaylandProtocol::~IWaylandProtocol() { @@ -40,3 +40,7 @@ IWaylandProtocol::~IWaylandProtocol() { void IWaylandProtocol::removeGlobal() { wl_global_remove(m_pGlobal); } + +wl_global* IWaylandProtocol::getGlobal() { + return m_pGlobal; +} diff --git a/src/protocols/WaylandProtocol.hpp b/src/protocols/WaylandProtocol.hpp index 4d4e7925..056322ac 100644 --- a/src/protocols/WaylandProtocol.hpp +++ b/src/protocols/WaylandProtocol.hpp @@ -11,6 +11,35 @@ #define PROTO NProtocols +#define EXTRACT_CLASS_NAME() \ + []() constexpr -> std::string_view { \ + constexpr std::string_view prettyFunction = __PRETTY_FUNCTION__; \ + constexpr size_t colons = prettyFunction.find("::"); \ + if (colons != std::string_view::npos) { \ + constexpr size_t begin = prettyFunction.substr(0, colons).rfind(' ') + 1; \ + constexpr size_t end = colons - begin; \ + return prettyFunction.substr(begin, end); \ + } else { \ + return "Global"; \ + } \ + }() + +#define LOGM(level, ...) \ + do { \ + std::ostringstream oss; \ + if (level == WARN || level == ERR || level == CRIT) { \ + oss << "[" << __FILE__ << ":" << __LINE__ << "] "; \ + } else if (level == LOG || level == INFO || level == TRACE) { \ + oss << "[" << EXTRACT_CLASS_NAME() << "] "; \ + } \ + if constexpr (std::tuple_size::value == 1 && std::is_same_v) { \ + oss << __VA_ARGS__; \ + Debug::log(level, oss.str()); \ + } else { \ + Debug::log(level, std::format("{}{}", oss.str(), std::format(__VA_ARGS__))); \ + } \ + } while (0) + class IWaylandProtocol; struct IWaylandProtocolDestroyWrapper { wl_listener listener; @@ -22,15 +51,11 @@ class IWaylandProtocol { IWaylandProtocol(const wl_interface* iface, const int& ver, const std::string& name); virtual ~IWaylandProtocol(); - virtual void onDisplayDestroy(); - virtual void removeGlobal(); + virtual void onDisplayDestroy(); + virtual void removeGlobal(); + virtual wl_global* getGlobal(); - virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) = 0; - - template - void protoLog(LogLevel level, std::format_string fmt, Args&&... args) { - Debug::log(level, std::format("[{}] ", m_szName) + std::vformat(fmt.get(), std::make_format_args(args...))); - }; + virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) = 0; IWaylandProtocolDestroyWrapper m_liDisplayDestroy; diff --git a/src/protocols/XDGActivation.cpp b/src/protocols/XDGActivation.cpp index 40f33f02..4a6c7bfe 100644 --- a/src/protocols/XDGActivation.cpp +++ b/src/protocols/XDGActivation.cpp @@ -4,8 +4,6 @@ #include "core/Compositor.hpp" #include -#define LOGM PROTO::activation->protoLog - CXDGActivationToken::CXDGActivationToken(SP resource_) : resource(resource_) { if (!resource_->resource()) return; diff --git a/src/protocols/XDGDecoration.cpp b/src/protocols/XDGDecoration.cpp index 021a1141..07b1694c 100644 --- a/src/protocols/XDGDecoration.cpp +++ b/src/protocols/XDGDecoration.cpp @@ -1,8 +1,6 @@ #include "XDGDecoration.hpp" #include -#define LOGM PROTO::xdgDecoration->protoLog - CXDGDecoration::CXDGDecoration(SP resource_, wl_resource* toplevel) : resource(resource_), pToplevelResource(toplevel) { if (!resource->resource()) return; diff --git a/src/protocols/XDGDialog.cpp b/src/protocols/XDGDialog.cpp new file mode 100644 index 00000000..237cf3f3 --- /dev/null +++ b/src/protocols/XDGDialog.cpp @@ -0,0 +1,88 @@ +#include "XDGDialog.hpp" +#include "XDGShell.hpp" +#include "../desktop/WLSurface.hpp" +#include "../Compositor.hpp" +#include + +CXDGDialogV1Resource::CXDGDialogV1Resource(SP resource_, SP toplevel_) : resource(resource_), toplevel(toplevel_) { + if (!good()) + return; + + resource->setDestroy([this](CXdgDialogV1* r) { PROTO::xdgDialog->destroyResource(this); }); + resource->setOnDestroy([this](CXdgDialogV1* r) { PROTO::xdgDialog->destroyResource(this); }); + + resource->setSetModal([this](CXdgDialogV1* r) { + modal = true; + updateWindow(); + }); + + resource->setUnsetModal([this](CXdgDialogV1* r) { + modal = false; + updateWindow(); + }); +} + +void CXDGDialogV1Resource::updateWindow() { + if (!toplevel || !toplevel->parent || !toplevel->parent->owner) + return; + + auto HLSurface = CWLSurface::fromResource(toplevel->parent->owner->surface.lock()); + if (!HLSurface || !HLSurface->getWindow()) + return; + + g_pCompositor->updateWindowAnimatedDecorationValues(HLSurface->getWindow()); +} + +bool CXDGDialogV1Resource::good() { + return resource->resource(); +} + +CXDGWmDialogManagerResource::CXDGWmDialogManagerResource(SP resource_) : resource(resource_) { + if (!good()) + return; + + resource->setDestroy([this](CXdgWmDialogV1* r) { PROTO::xdgDialog->destroyResource(this); }); + resource->setOnDestroy([this](CXdgWmDialogV1* r) { PROTO::xdgDialog->destroyResource(this); }); + + resource->setGetXdgDialog([this](CXdgWmDialogV1* r, uint32_t id, wl_resource* toplevel) { + auto tl = CXDGToplevelResource::fromResource(toplevel); + if (!tl) { + r->error(-1, "Toplevel inert"); + return; + } + + const auto RESOURCE = PROTO::xdgDialog->m_vDialogs.emplace_back(makeShared(makeShared(r->client(), r->version(), id), tl)); + + if (!RESOURCE->good()) { + r->noMemory(); + return; + } + + tl->dialog = RESOURCE; + }); +} + +bool CXDGWmDialogManagerResource::good() { + return resource->resource(); +} + +CXDGDialogProtocol::CXDGDialogProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { + ; +} + +void CXDGDialogProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + const auto RESOURCE = m_vManagers.emplace_back(makeShared(makeShared(client, ver, id))); + + if (!RESOURCE->good()) { + wl_client_post_no_memory(client); + return; + } +} + +void CXDGDialogProtocol::destroyResource(CXDGWmDialogManagerResource* res) { + std::erase_if(m_vManagers, [&](const auto& other) { return other.get() == res; }); +} + +void CXDGDialogProtocol::destroyResource(CXDGDialogV1Resource* res) { + std::erase_if(m_vDialogs, [&](const auto& other) { return other.get() == res; }); +} diff --git a/src/protocols/XDGDialog.hpp b/src/protocols/XDGDialog.hpp new file mode 100644 index 00000000..a635bfac --- /dev/null +++ b/src/protocols/XDGDialog.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include +#include "WaylandProtocol.hpp" +#include "xdg-dialog-v1.hpp" + +class CXDGToplevelResource; + +class CXDGDialogV1Resource { + public: + CXDGDialogV1Resource(SP resource_, SP toplevel_); + + bool good(); + + bool modal = false; + + private: + SP resource; + WP toplevel; + + void updateWindow(); +}; + +class CXDGWmDialogManagerResource { + public: + CXDGWmDialogManagerResource(SP resource_); + + bool good(); + + private: + SP resource; +}; + +class CXDGDialogProtocol : public IWaylandProtocol { + public: + CXDGDialogProtocol(const wl_interface* iface, const int& ver, const std::string& name); + + virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); + + private: + void onManagerResourceDestroy(wl_resource* res); + void destroyResource(CXDGWmDialogManagerResource* res); + void destroyResource(CXDGDialogV1Resource* res); + + // + std::vector> m_vManagers; + std::vector> m_vDialogs; + + friend class CXDGWmDialogManagerResource; + friend class CXDGDialogV1Resource; +}; + +namespace PROTO { + inline UP xdgDialog; +}; diff --git a/src/protocols/XDGOutput.cpp b/src/protocols/XDGOutput.cpp index 073aa502..0598e713 100644 --- a/src/protocols/XDGOutput.cpp +++ b/src/protocols/XDGOutput.cpp @@ -12,8 +12,6 @@ // -#define LOGM PROTO::xdgOutput->protoLog - void CXDGOutputProtocol::onManagerResourceDestroy(wl_resource* res) { std::erase_if(m_vManagerResources, [&](const auto& other) { return other->resource() == res; }); } @@ -31,29 +29,20 @@ void CXDGOutputProtocol::bindManager(wl_client* client, void* data, uint32_t ver return; } - RESOURCE->setDestroy([this](CZxdgOutputManagerV1* res) { this->onManagerResourceDestroy(res->resource()); }); - RESOURCE->setOnDestroy([this](CZxdgOutputManagerV1* res) { this->onManagerResourceDestroy(res->resource()); }); - RESOURCE->setGetXdgOutput([this](CZxdgOutputManagerV1* mgr, uint32_t id, wl_resource* output) { this->onManagerGetXDGOutput(mgr, id, output); }); + RESOURCE->setDestroy([this](CZxdgOutputManagerV1* res) { onManagerResourceDestroy(res->resource()); }); + RESOURCE->setOnDestroy([this](CZxdgOutputManagerV1* res) { onManagerResourceDestroy(res->resource()); }); + RESOURCE->setGetXdgOutput([this](CZxdgOutputManagerV1* mgr, uint32_t id, wl_resource* output) { onManagerGetXDGOutput(mgr, id, output); }); } CXDGOutputProtocol::CXDGOutputProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { static auto P = g_pHookSystem->hookDynamic("monitorLayoutChanged", [this](void* self, SCallbackInfo& info, std::any param) { this->updateAllOutputs(); }); static auto P2 = g_pHookSystem->hookDynamic("configReloaded", [this](void* self, SCallbackInfo& info, std::any param) { this->updateAllOutputs(); }); - static auto P3 = g_pHookSystem->hookDynamic("monitorRemoved", [this](void* self, SCallbackInfo& info, std::any param) { - const auto PMONITOR = std::any_cast(param); - for (auto& o : m_vXDGOutputs) { - if (o->monitor == PMONITOR) - o->monitor = nullptr; - } - }); } void CXDGOutputProtocol::onManagerGetXDGOutput(CZxdgOutputManagerV1* mgr, uint32_t id, wl_resource* outputResource) { - const auto OUTPUT = CWLOutputResource::fromResource(outputResource); - - const auto PMONITOR = OUTPUT->monitor.get(); - - const auto CLIENT = mgr->client(); + const auto OUTPUT = CWLOutputResource::fromResource(outputResource); + const auto PMONITOR = OUTPUT->monitor.lock(); + const auto CLIENT = mgr->client(); CXDGOutput* pXDGOutput = m_vXDGOutputs.emplace_back(std::make_unique(makeShared(CLIENT, mgr->version(), id), PMONITOR)).get(); #ifndef NO_XWAYLAND @@ -62,14 +51,20 @@ void CXDGOutputProtocol::onManagerGetXDGOutput(CZxdgOutputManagerV1* mgr, uint32 #endif pXDGOutput->client = CLIENT; + pXDGOutput->outputProto = OUTPUT->owner; + if (!pXDGOutput->resource->resource()) { m_vXDGOutputs.pop_back(); mgr->noMemory(); return; } - if (!PMONITOR) + if (!PMONITOR) { + LOGM(ERR, "New xdg_output from client {:x} ({}) has no CMonitor?!", (uintptr_t)CLIENT, pXDGOutput->isXWayland ? "xwayland" : "not xwayland"); return; + } + + LOGM(LOG, "New xdg_output for {}: client {:x} ({})", PMONITOR->szName, (uintptr_t)CLIENT, pXDGOutput->isXWayland ? "xwayland" : "not xwayland"); const auto XDGVER = pXDGOutput->resource->version(); @@ -86,8 +81,9 @@ void CXDGOutputProtocol::onManagerGetXDGOutput(CZxdgOutputManagerV1* mgr, uint32 } void CXDGOutputProtocol::updateAllOutputs() { - for (auto& o : m_vXDGOutputs) { + LOGM(LOG, "updating all xdg_output heads"); + for (auto const& o : m_vXDGOutputs) { if (!o->monitor) continue; @@ -99,7 +95,7 @@ void CXDGOutputProtocol::updateAllOutputs() { // -CXDGOutput::CXDGOutput(SP resource_, CMonitor* monitor_) : monitor(monitor_), resource(resource_) { +CXDGOutput::CXDGOutput(SP resource_, SP monitor_) : monitor(monitor_), resource(resource_) { if (!resource->resource()) return; @@ -110,7 +106,7 @@ CXDGOutput::CXDGOutput(SP resource_, CMonitor* monitor_) : monito void CXDGOutput::sendDetails() { static auto PXWLFORCESCALEZERO = CConfigValue("xwayland:force_zero_scaling"); - if (!monitor) + if (!monitor || !outputProto || outputProto->isDefunct()) return; const auto POS = isXWayland ? monitor->vecXWaylandPosition : monitor->vecPosition; diff --git a/src/protocols/XDGOutput.hpp b/src/protocols/XDGOutput.hpp index 73e7d53e..520f3aaa 100644 --- a/src/protocols/XDGOutput.hpp +++ b/src/protocols/XDGOutput.hpp @@ -6,16 +6,18 @@ class CMonitor; class CXDGOutputProtocol; +class CWLOutputProtocol; class CXDGOutput { public: - CXDGOutput(SP resource, CMonitor* monitor_); + CXDGOutput(SP resource, SP monitor_); void sendDetails(); private: - CMonitor* monitor = nullptr; + WP monitor; SP resource; + WP outputProto; std::optional overridePosition; @@ -30,12 +32,12 @@ class CXDGOutputProtocol : public IWaylandProtocol { CXDGOutputProtocol(const wl_interface* iface, const int& ver, const std::string& name); virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); + void updateAllOutputs(); private: void onManagerResourceDestroy(wl_resource* res); void onOutputResourceDestroy(wl_resource* res); void onManagerGetXDGOutput(CZxdgOutputManagerV1* mgr, uint32_t id, wl_resource* outputResource); - void updateAllOutputs(); // std::vector> m_vManagerResources; diff --git a/src/protocols/XDGShell.cpp b/src/protocols/XDGShell.cpp index 4aa5d373..25d8b1ba 100644 --- a/src/protocols/XDGShell.cpp +++ b/src/protocols/XDGShell.cpp @@ -1,12 +1,12 @@ #include "XDGShell.hpp" +#include "XDGDialog.hpp" #include #include "../Compositor.hpp" #include "../managers/SeatManager.hpp" #include "core/Seat.hpp" #include "core/Compositor.hpp" #include - -#define LOGM PROTO::xdgShell->protoLog +#include void SXDGPositionerState::setAnchor(xdgPositionerAnchor edges) { anchor.setTop(edges == XDG_POSITIONER_ANCHOR_TOP || edges == XDG_POSITIONER_ANCHOR_TOP_LEFT || edges == XDG_POSITIONER_ANCHOR_TOP_RIGHT); @@ -209,15 +209,25 @@ CXDGToplevelResource::CXDGToplevelResource(SP resource_, SPsetSetParent([this](CXdgToplevel* r, wl_resource* parentR) { + auto oldParent = parent; + + if (parent) + std::erase(parent->children, self); + auto newp = parentR ? CXDGToplevelResource::fromResource(parentR) : nullptr; parent = newp; - LOGM(LOG, "Toplevel {:x} sets parent to {:x}", (uintptr_t)this, (uintptr_t)newp.get()); + if (parent) + parent->children.emplace_back(self); + + LOGM(LOG, "Toplevel {:x} sets parent to {:x}{}", (uintptr_t)this, (uintptr_t)newp.get(), (oldParent ? std::format(" (was {:x})", (uintptr_t)oldParent.get()) : "")); }); } CXDGToplevelResource::~CXDGToplevelResource() { events.destroy.emit(); + if (parent) + std::erase_if(parent->children, [this](const auto& other) { return !other || other.get() == this; }); } SP CXDGToplevelResource::fromResource(wl_resource* res) { @@ -229,6 +239,10 @@ bool CXDGToplevelResource::good() { return resource->resource(); } +bool CXDGToplevelResource::anyChildModal() { + return std::ranges::any_of(children, [](const auto& child) { return child && child->dialog && child->dialog->modal; }); +} + uint32_t CXDGToplevelResource::setSize(const Vector2D& size) { pendingApply.size = size; applyState(); @@ -390,7 +404,7 @@ CXDGSurfaceResource::CXDGSurfaceResource(SP resource_, SPm_vWindows.emplace_back(CWindow::create(self.lock())); - for (auto& p : popups) { + for (auto const& p : popups) { if (!p) continue; events.newPopup.emit(p); @@ -531,29 +545,66 @@ CBox CXDGPositionerRules::getPosition(CBox constraint, const Vector2D& parentCoo auto anchorRect = state.anchorRect.copy().translate(parentCoord); - auto width = state.requestedSize.x; - auto height = state.requestedSize.y; + auto width = state.requestedSize.x; + auto height = state.requestedSize.y; + auto gravity = state.gravity; auto anchorX = state.anchor.left() ? anchorRect.x : state.anchor.right() ? anchorRect.extent().x : anchorRect.middle().x; auto anchorY = state.anchor.top() ? anchorRect.y : state.anchor.bottom() ? anchorRect.extent().y : anchorRect.middle().y; - auto calcEffectiveX = [&]() { return state.gravity.left() ? anchorX - width : state.gravity.right() ? anchorX : anchorX - width / 2; }; - auto calcEffectiveY = [&]() { return state.gravity.top() ? anchorY - height : state.gravity.bottom() ? anchorY : anchorY - height / 2; }; + auto calcEffectiveX = [&](CEdges anchorGravity, double anchorX) { return anchorGravity.left() ? anchorX - width : anchorGravity.right() ? anchorX : anchorX - width / 2; }; + auto calcEffectiveY = [&](CEdges anchorGravity, double anchorY) { return anchorGravity.top() ? anchorY - height : anchorGravity.bottom() ? anchorY : anchorY - height / 2; }; - auto effectiveX = calcEffectiveX(); - auto effectiveY = calcEffectiveY(); + auto calcRemainingWidth = [&](double effectiveX) { + auto width = state.requestedSize.x; + if (effectiveX < constraint.x) { + auto diff = constraint.x - effectiveX; + effectiveX = constraint.x; + width -= diff; + } + + auto effectiveX2 = effectiveX + width; + if (effectiveX2 > constraint.extent().x) + width -= effectiveX2 - constraint.extent().x; + + return std::make_pair(effectiveX, width); + }; + + auto calcRemainingHeight = [&](double effectiveY) { + auto height = state.requestedSize.y; + if (effectiveY < constraint.y) { + auto diff = constraint.y - effectiveY; + effectiveY = constraint.y; + height -= diff; + } + + auto effectiveY2 = effectiveY + height; + if (effectiveY2 > constraint.extent().y) + height -= effectiveY2 - constraint.extent().y; + + return std::make_pair(effectiveY, height); + }; + + auto effectiveX = calcEffectiveX(gravity, anchorX); + auto effectiveY = calcEffectiveY(gravity, anchorY); // Note: the usage of offset is a guess which maintains compatibility with other compositors that were tested. // It considers the offset when deciding whether or not to flip but does not actually flip the offset, instead // applying it after the flip step. if (state.constraintAdjustment & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X) { - auto flip = (state.gravity.left() && effectiveX + state.offset.x < constraint.x) || (state.gravity.right() && effectiveX + state.offset.x + width > constraint.extent().x); + auto flip = (gravity.left() && effectiveX + state.offset.x < constraint.x) || (gravity.right() && effectiveX + state.offset.x + width > constraint.extent().x); if (flip) { - state.gravity ^= CEdges::LEFT | CEdges::RIGHT; - anchorX = state.anchor.left() ? anchorRect.extent().x : state.anchor.right() ? anchorRect.x : anchorX; - effectiveX = calcEffectiveX(); + auto newGravity = gravity ^ (CEdges::LEFT | CEdges::RIGHT); + auto newAnchorX = state.anchor.left() ? anchorRect.extent().x : state.anchor.right() ? anchorRect.x : anchorX; + auto newEffectiveX = calcEffectiveX(newGravity, newAnchorX); + + if (calcRemainingWidth(newEffectiveX).second > calcRemainingWidth(effectiveX).second) { + gravity = newGravity; + anchorX = newAnchorX; + effectiveX = newEffectiveX; + } } } @@ -561,9 +612,15 @@ CBox CXDGPositionerRules::getPosition(CBox constraint, const Vector2D& parentCoo auto flip = (state.gravity.top() && effectiveY + state.offset.y < constraint.y) || (state.gravity.bottom() && effectiveY + state.offset.y + height > constraint.extent().y); if (flip) { - state.gravity ^= CEdges::TOP | CEdges::BOTTOM; - anchorY = state.anchor.top() ? anchorRect.extent().y : state.anchor.bottom() ? anchorRect.y : anchorY; - effectiveY = calcEffectiveY(); + auto newGravity = gravity ^ (CEdges::TOP | CEdges::BOTTOM); + auto newAnchorY = state.anchor.top() ? anchorRect.extent().y : state.anchor.bottom() ? anchorRect.y : anchorY; + auto newEffectiveY = calcEffectiveY(newGravity, newAnchorY); + + if (calcRemainingHeight(newEffectiveY).second > calcRemainingHeight(effectiveY).second) { + gravity = newGravity; + anchorY = newAnchorY; + effectiveY = newEffectiveY; + } } } @@ -589,27 +646,15 @@ CBox CXDGPositionerRules::getPosition(CBox constraint, const Vector2D& parentCoo } if (state.constraintAdjustment & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X) { - if (effectiveX < constraint.x) { - auto diff = constraint.x - effectiveX; - effectiveX = constraint.x; - width -= diff; - } - - auto effectiveX2 = effectiveX + width; - if (effectiveX2 > constraint.extent().x) - width -= effectiveX2 - constraint.extent().x; + auto [newX, newWidth] = calcRemainingWidth(effectiveX); + effectiveX = newX; + width = newWidth; } if (state.constraintAdjustment & XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y) { - if (effectiveY < constraint.y) { - auto diff = constraint.y - effectiveY; - effectiveY = constraint.y; - height -= diff; - } - - auto effectiveY2 = effectiveY + height; - if (effectiveY2 > constraint.extent().y) - height -= effectiveY2 - constraint.extent().y; + auto [newY, newHeight] = calcRemainingHeight(effectiveY); + effectiveY = newY; + height = newHeight; } return {effectiveX - parentCoord.x, effectiveY - parentCoord.y, width, height}; @@ -685,7 +730,7 @@ CXDGShellProtocol::CXDGShellProtocol(const wl_interface* iface, const int& ver, grab->keyboard = true; grab->pointer = true; grab->setCallback([this]() { - for (auto& g : grabbed) { + for (auto const& g : grabbed) { g->done(); } grabbed.clear(); @@ -750,7 +795,7 @@ void CXDGShellProtocol::addOrStartGrab(SP popup) { void CXDGShellProtocol::onPopupDestroy(WP popup) { if (popup == grabOwner) { g_pSeatManager->setGrab(nullptr); - for (auto& g : grabbed) { + for (auto const& g : grabbed) { g->done(); } grabbed.clear(); diff --git a/src/protocols/XDGShell.hpp b/src/protocols/XDGShell.hpp index 81d10613..9c766c20 100644 --- a/src/protocols/XDGShell.hpp +++ b/src/protocols/XDGShell.hpp @@ -18,6 +18,7 @@ class CXDGToplevelResource; class CXDGPopupResource; class CSeatGrab; class CWLSurfaceResource; +class CXDGDialogV1Resource; struct SXDGPositionerState { Vector2D requestedSize; @@ -134,7 +135,12 @@ class CXDGToplevelResource { Vector2D maxSize = {1337420, 694200}; } pending, current; - WP parent; + WP parent; + WP dialog; + + bool anyChildModal(); + + std::vector> children; private: SP resource; diff --git a/src/protocols/XWaylandShell.cpp b/src/protocols/XWaylandShell.cpp index 6cc5256f..d6c3b1a8 100644 --- a/src/protocols/XWaylandShell.cpp +++ b/src/protocols/XWaylandShell.cpp @@ -2,8 +2,6 @@ #include "core/Compositor.hpp" #include -#define LOGM PROTO::xwaylandShell->protoLog - CXWaylandSurfaceResource::CXWaylandSurfaceResource(SP resource_, SP surface_) : surface(surface_), resource(resource_) { if (!good()) return; diff --git a/src/protocols/core/Compositor.cpp b/src/protocols/core/Compositor.cpp index 4a6fa40f..3d4b63c1 100644 --- a/src/protocols/core/Compositor.cpp +++ b/src/protocols/core/Compositor.cpp @@ -7,13 +7,12 @@ #include "Subcompositor.hpp" #include "../Viewporter.hpp" #include "../../helpers/Monitor.hpp" +#include "../../helpers/sync/SyncReleaser.hpp" #include "../PresentationTime.hpp" #include "../DRMSyncobj.hpp" #include "../../render/Renderer.hpp" #include -#define LOGM PROTO::compositor->protoLog - class CDefaultSurfaceRole : public ISurfaceRole { public: virtual eSurfaceRole role() { @@ -69,7 +68,8 @@ CWLSurfaceResource::CWLSurfaceResource(SP resource_) : resource(reso resource->setOnDestroy([this](CWlSurface* r) { destroy(); }); resource->setAttach([this](CWlSurface* r, wl_resource* buffer, int32_t x, int32_t y) { - pending.offset = {x, y}; + pending.offset = {x, y}; + pending.newBuffer = true; if (!buffer) { pending.buffer.reset(); @@ -241,7 +241,7 @@ void CWLSurfaceResource::frame(timespec* now) { if (callbacks.empty()) return; - for (auto& c : callbacks) { + for (auto const& c : callbacks) { c->send(now); } @@ -257,10 +257,10 @@ void CWLSurfaceResource::bfHelper(std::vector> nodes, std std::vector> nodes2; // first, gather all nodes below - for (auto& n : nodes) { + for (auto const& n : nodes) { std::erase_if(n->subsurfaces, [](const auto& e) { return e.expired(); }); // subsurfaces is sorted lowest -> highest - for (auto& c : n->subsurfaces) { + for (auto const& c : n->subsurfaces) { if (c->zIndex >= 0) break; if (c->surface.expired()) @@ -274,7 +274,7 @@ void CWLSurfaceResource::bfHelper(std::vector> nodes, std nodes2.clear(); - for (auto& n : nodes) { + for (auto const& n : nodes) { Vector2D offset = {}; if (n->role->role() == SURFACE_ROLE_SUBSURFACE) { auto subsurface = ((CSubsurfaceRole*)n->role.get())->subsurface.lock(); @@ -284,8 +284,8 @@ void CWLSurfaceResource::bfHelper(std::vector> nodes, std fn(n, offset, data); } - for (auto& n : nodes) { - for (auto& c : n->subsurfaces) { + for (auto const& n : nodes) { + for (auto const& c : n->subsurfaces) { if (c->zIndex < 0) continue; if (c->surface.expired()) @@ -310,7 +310,7 @@ std::pair, Vector2D> CWLSurfaceResource::at(const Vector2 void* data) { ((std::vector, Vector2D>>*)data)->emplace_back(std::make_pair<>(surf, offset)); }, &surfs); - for (auto& [surf, pos] : surfs | std::views::reverse) { + for (auto const& [surf, pos] : surfs | std::views::reverse) { if (!allowsInput) { const auto BOX = CBox{pos, surf->current.size}; if (BOX.containsPoint(localCoords)) @@ -428,6 +428,12 @@ void CWLSurfaceResource::commitPendingState() { current = pending; pending.damage.clear(); pending.bufferDamage.clear(); + pending.newBuffer = false; + + events.roleCommit.emit(); + + if (syncobj && syncobj->current.releaseTimeline && syncobj->current.releaseTimeline->timeline && current.buffer && current.buffer->buffer) + current.buffer->releaser = makeShared(syncobj->current.releaseTimeline->timeline, syncobj->current.releasePoint); if (current.texture) current.texture->m_eTransform = wlTransformToHyprutils(current.transform); @@ -442,8 +448,10 @@ void CWLSurfaceResource::commitPendingState() { // release the buffer if it's synchronous as update() has done everything thats needed // so we can let the app know we're done. - if (current.buffer->buffer->isSynchronous()) + if (current.buffer->buffer->isSynchronous()) { dropCurrentBuffer(); + dropPendingBuffer(); // pending atm is just a copied ref of the current, drop it too to send a release + } } // TODO: we should _accumulate_ and not replace above if sync @@ -501,23 +509,18 @@ void CWLSurfaceResource::updateCursorShm() { memcpy(shmData.data(), pixelData, bufLen); } -void CWLSurfaceResource::presentFeedback(timespec* when, CMonitor* pMonitor, bool needsExplicitSync) { +void CWLSurfaceResource::presentFeedback(timespec* when, SP pMonitor) { frame(when); auto FEEDBACK = makeShared(self.lock()); FEEDBACK->attachMonitor(pMonitor); FEEDBACK->presented(); PROTO::presentation->queueData(FEEDBACK); - if (!pMonitor || !pMonitor->outTimeline || !syncobj || !needsExplicitSync) + if (!pMonitor || !pMonitor->outTimeline || !syncobj) return; // attach explicit sync g_pHyprRenderer->explicitPresented.emplace_back(self.lock()); - - if (syncobj->acquirePoint > pMonitor->lastWaitPoint) { - Debug::log(TRACE, "presentFeedback lastWaitPoint {} -> {}", pMonitor->lastWaitPoint, syncobj->acquirePoint); - pMonitor->lastWaitPoint = syncobj->acquirePoint; - } } CWLCompositorResource::CWLCompositorResource(SP resource_) : resource(resource_) { @@ -586,3 +589,9 @@ void CWLCompositorProtocol::destroyResource(CWLSurfaceResource* resource) { void CWLCompositorProtocol::destroyResource(CWLRegionResource* resource) { std::erase_if(m_vRegions, [&](const auto& other) { return other.get() == resource; }); } + +void CWLCompositorProtocol::forEachSurface(std::function)> fn) { + for (auto& surf : m_vSurfaces) { + fn(surf); + } +} diff --git a/src/protocols/core/Compositor.hpp b/src/protocols/core/Compositor.hpp index 460ec755..b3c067c9 100644 --- a/src/protocols/core/Compositor.hpp +++ b/src/protocols/core/Compositor.hpp @@ -75,8 +75,9 @@ class CWLSurfaceResource { Vector2D sourceSize(); struct { - CSignal precommit; - CSignal commit; + CSignal precommit; // before commit + CSignal roleCommit; // commit for role objects, before regular commit + CSignal commit; // after commit CSignal map; CSignal unmap; CSignal newSubsurface; @@ -97,7 +98,8 @@ class CWLSurfaceResource { Vector2D destination; CBox source; } viewport; - bool rejected = false; + bool rejected = false; + bool newBuffer = false; // void reset() { @@ -122,7 +124,7 @@ class CWLSurfaceResource { void breadthfirst(std::function, const Vector2D&, void*)> fn, void* data); CRegion accumulateCurrentBufferDamage(); - void presentFeedback(timespec* when, CMonitor* pMonitor, bool needsExplicitSync = false); + void presentFeedback(timespec* when, SP pMonitor); void lockPendingState(); void unlockPendingState(); @@ -167,6 +169,8 @@ class CWLCompositorProtocol : public IWaylandProtocol { virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); + void forEachSurface(std::function)> fn); + struct { CSignal newSurface; // SP } events; diff --git a/src/protocols/core/DataDevice.cpp b/src/protocols/core/DataDevice.cpp index fe3905d0..5644f243 100644 --- a/src/protocols/core/DataDevice.cpp +++ b/src/protocols/core/DataDevice.cpp @@ -6,8 +6,6 @@ #include "Seat.hpp" #include "Compositor.hpp" -#define LOGM PROTO::data->protoLog - CWLDataOfferResource::CWLDataOfferResource(SP resource_, SP source_) : source(source_), resource(resource_) { if (!good()) return; @@ -69,6 +67,13 @@ CWLDataOfferResource::CWLDataOfferResource(SP resource_, SPhasDnd() || dead) + return; + + source->sendDndFinished(); +} + bool CWLDataOfferResource::good() { return resource->resource(); } @@ -77,12 +82,21 @@ void CWLDataOfferResource::sendData() { if (!source) return; - if (resource->version() >= 3) { - resource->sendSourceActions(7); - resource->sendAction(WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE); + const auto SOURCEACTIONS = source->actions(); + + if (resource->version() >= 3 && SOURCEACTIONS > 0) { + resource->sendSourceActions(SOURCEACTIONS); + if (SOURCEACTIONS & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE) + resource->sendAction(WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE); + else if (SOURCEACTIONS & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) + resource->sendAction(WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY); + else { + LOGM(ERR, "Client bug? dnd source has no action move or copy. Sending move, f this."); + resource->sendAction(WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE); + } } - for (auto& m : source->mimes()) { + for (auto const& m : source->mimes()) { LOGM(LOG, " | offer {:x} supports mime {}", (uintptr_t)this, m); resource->sendOffer(m.c_str()); } @@ -108,7 +122,7 @@ CWLDataSourceResource::CWLDataSourceResource(SP resource_, SPsetOffer([this](CWlDataSource* r, const char* mime) { mimeTypes.push_back(mime); }); resource->setSetActions([this](CWlDataSource* r, uint32_t a) { LOGM(LOG, "DataSource {:x} actions {}", (uintptr_t)this, a); - actions = (wl_data_device_manager_dnd_action)a; + supportedActions = a; }); } @@ -175,6 +189,7 @@ void CWLDataSourceResource::sendDndDropPerformed() { if (resource->version() < 3) return; resource->sendDndDropPerformed(); + dropped = true; } void CWLDataSourceResource::sendDndFinished() { @@ -189,6 +204,10 @@ void CWLDataSourceResource::sendDndAction(wl_data_device_manager_dnd_action a) { resource->sendAction(a); } +uint32_t CWLDataSourceResource::actions() { + return supportedActions; +} + CWLDataDeviceResource::CWLDataDeviceResource(SP resource_) : resource(resource_) { if (!good()) return; @@ -308,7 +327,7 @@ CWLDataDeviceManagerResource::CWLDataDeviceManagerResource(SPself = RESOURCE; - for (auto& s : sources) { + for (auto const& s : sources) { if (!s) continue; s->device = RESOURCE; @@ -392,7 +411,7 @@ void CWLDataDeviceProtocol::onDestroyDataSource(WP source } void CWLDataDeviceProtocol::setSelection(SP source) { - for (auto& o : m_vOffers) { + for (auto const& o : m_vOffers) { if (o->source && o->source->hasDnd()) continue; o->dead = true; @@ -441,7 +460,7 @@ void CWLDataDeviceProtocol::updateSelection() { } void CWLDataDeviceProtocol::onKeyboardFocus() { - for (auto& o : m_vOffers) { + for (auto const& o : m_vOffers) { o->dead = true; } @@ -507,7 +526,10 @@ void CWLDataDeviceProtocol::initiateDrag(WP currentSource if (!box.has_value()) return; - dnd.focusedDevice->sendMotion(0 /* this is a hack */, V - box->pos()); + timespec timeNow; + clock_gettime(CLOCK_MONOTONIC, &timeNow); + + dnd.focusedDevice->sendMotion(timeNow.tv_sec * 1000 + timeNow.tv_nsec / 1000000, V - box->pos()); LOGM(LOG, "Drag motion {}", V - box->pos()); } }); @@ -608,7 +630,7 @@ bool CWLDataDeviceProtocol::wasDragSuccessful() { if (!dnd.focusedDevice || !dnd.currentSource) return false; - for (auto& o : m_vOffers) { + for (auto const& o : m_vOffers) { if (o->dead || !o->source || !o->source->hasDnd()) continue; diff --git a/src/protocols/core/DataDevice.hpp b/src/protocols/core/DataDevice.hpp index 22bb9376..50e9ac61 100644 --- a/src/protocols/core/DataDevice.hpp +++ b/src/protocols/core/DataDevice.hpp @@ -29,6 +29,7 @@ class CMonitor; class CWLDataOfferResource { public: CWLDataOfferResource(SP resource_, SP source_); + ~CWLDataOfferResource(); bool good(); void sendData(); @@ -63,20 +64,22 @@ class CWLDataSourceResource : public IDataSource { virtual bool hasDnd(); virtual bool dndDone(); virtual void error(uint32_t code, const std::string& msg); + virtual void sendDndFinished(); + virtual uint32_t actions(); // wl_data_device_manager.dnd_action void sendDndDropPerformed(); - void sendDndFinished(); void sendDndAction(wl_data_device_manager_dnd_action a); bool used = false; bool dnd = false; bool dndSuccess = false; + bool dropped = false; WP device; WP self; std::vector mimeTypes; - uint32_t actions = 0; + uint32_t supportedActions = 0; private: SP resource; diff --git a/src/protocols/core/Output.cpp b/src/protocols/core/Output.cpp index 878d1484..e9f35abc 100644 --- a/src/protocols/core/Output.cpp +++ b/src/protocols/core/Output.cpp @@ -1,4 +1,6 @@ #include "Output.hpp" +#include "Compositor.hpp" +#include "../../Compositor.hpp" #include "../../helpers/Monitor.hpp" CWLOutputResource::CWLOutputResource(SP resource_, SP pMonitor) : monitor(pMonitor), resource(resource_) { @@ -21,14 +23,31 @@ CWLOutputResource::CWLOutputResource(SP resource_, SP pMoni PROTO::outputs.at(monitor->szName)->destroyResource(this); }); - resource->sendGeometry(0, 0, monitor->output->physicalSize.x, monitor->output->physicalSize.y, (wl_output_subpixel)monitor->output->subpixel, monitor->output->make.c_str(), - monitor->output->model.c_str(), monitor->transform); if (resource->version() >= 4) { resource->sendName(monitor->szName.c_str()); resource->sendDescription(monitor->szDescription.c_str()); } updateState(); + + PROTO::compositor->forEachSurface([](SP surf) { + auto HLSurf = CWLSurface::fromResource(surf); + + if (!HLSurf) + return; + + const auto GEOMETRY = HLSurf->getSurfaceBoxGlobal(); + + if (!GEOMETRY.has_value()) + return; + + for (auto& m : g_pCompositor->m_vMonitors) { + if (!m->logicalBox().overlaps(*GEOMETRY)) + continue; + + surf->enter(m); + } + }); } SP CWLOutputResource::fromResource(wl_resource* res) { @@ -55,7 +74,10 @@ void CWLOutputResource::updateState() { if (resource->version() >= 2) resource->sendScale(std::ceil(monitor->scale)); - resource->sendMode((wl_output_mode)(WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED), monitor->vecPixelSize.x, monitor->vecPixelSize.y, monitor->refreshRate * 1000.0); + resource->sendMode((wl_output_mode)(WL_OUTPUT_MODE_CURRENT), monitor->vecPixelSize.x, monitor->vecPixelSize.y, monitor->refreshRate * 1000.0); + + resource->sendGeometry(0, 0, monitor->output->physicalSize.x, monitor->output->physicalSize.y, (wl_output_subpixel)monitor->output->subpixel, monitor->output->make.c_str(), + monitor->output->model.c_str(), monitor->transform); if (resource->version() >= 2) resource->sendDone(); @@ -65,7 +87,7 @@ CWLOutputProtocol::CWLOutputProtocol(const wl_interface* iface, const int& ver, IWaylandProtocol(iface, ver, name), monitor(pMonitor), szName(pMonitor->szName) { listeners.modeChanged = monitor->events.modeChanged.registerListener([this](std::any d) { - for (auto& o : m_vOutputs) { + for (auto const& o : m_vOutputs) { o->updateState(); } }); @@ -95,7 +117,7 @@ void CWLOutputProtocol::destroyResource(CWLOutputResource* resource) { } SP CWLOutputProtocol::outputResourceFrom(wl_client* client) { - for (auto& r : m_vOutputs) { + for (auto const& r : m_vOutputs) { if (r->client() != client) continue; @@ -118,7 +140,10 @@ bool CWLOutputProtocol::isDefunct() { } void CWLOutputProtocol::sendDone() { - for (auto& r : m_vOutputs) { + if (defunct) + return; + + for (auto const& r : m_vOutputs) { r->resource->sendDone(); } } diff --git a/src/protocols/core/Seat.cpp b/src/protocols/core/Seat.cpp index bb6a9d4d..6ae0ddc4 100644 --- a/src/protocols/core/Seat.cpp +++ b/src/protocols/core/Seat.cpp @@ -9,8 +9,6 @@ #include -#define LOGM PROTO::seat->protoLog - CWLTouchResource::CWLTouchResource(SP resource_, SP owner_) : owner(owner_), resource(resource_) { if (!good()) return; @@ -174,7 +172,7 @@ void CWLPointerResource::sendLeave() { if (!PROTO::data->dndActive()) { timespec now; clock_gettime(CLOCK_MONOTONIC, &now); - for (auto& b : pressedButtons) { + for (auto const& b : pressedButtons) { sendButton(now.tv_sec * 1000 + now.tv_nsec / 1000000, b, WL_POINTER_BUTTON_STATE_RELEASED); } } @@ -527,7 +525,7 @@ void CWLSeatProtocol::updateCapabilities(uint32_t caps) { currentCaps = caps; - for (auto& s : m_vSeatResources) { + for (auto const& s : m_vSeatResources) { s->sendCapabilities(caps); } } @@ -536,7 +534,7 @@ void CWLSeatProtocol::updateKeymap() { if (!(currentCaps & eHIDCapabilityType::HID_INPUT_CAPABILITY_KEYBOARD)) return; - for (auto& k : m_vKeyboards) { + for (auto const& k : m_vKeyboards) { k->sendKeymap(g_pSeatManager->keyboard.lock()); } } @@ -545,13 +543,13 @@ void CWLSeatProtocol::updateRepeatInfo(uint32_t rate, uint32_t delayMs) { if (!(currentCaps & eHIDCapabilityType::HID_INPUT_CAPABILITY_KEYBOARD)) return; - for (auto& k : m_vKeyboards) { + for (auto const& k : m_vKeyboards) { k->repeatInfo(rate, delayMs); } } SP CWLSeatProtocol::seatResourceForClient(wl_client* client) { - for (auto& r : m_vSeatResources) { + for (auto const& r : m_vSeatResources) { if (r->client() == client) return r; } diff --git a/src/protocols/core/Shm.cpp b/src/protocols/core/Shm.cpp index 75c2134a..a8c98bb0 100644 --- a/src/protocols/core/Shm.cpp +++ b/src/protocols/core/Shm.cpp @@ -7,8 +7,6 @@ #include "../../Compositor.hpp" #include "../../helpers/Format.hpp" -#define LOGM PROTO::shm->protoLog - CWLSHMBuffer::CWLSHMBuffer(SP pool_, uint32_t id, int32_t offset_, const Vector2D& size_, int32_t stride_, uint32_t fmt_) { if (!pool_->pool->data) return; @@ -65,7 +63,7 @@ Aquamarine::SSHMAttrs CWLSHMBuffer::shm() { } std::tuple CWLSHMBuffer::beginDataPtr(uint32_t flags) { - return {(uint8_t*)pool->data + offset, fmt, size.x * size.y * 4}; + return {(uint8_t*)pool->data + offset, fmt, stride * size.y}; } void CWLSHMBuffer::endDataPtr() { @@ -173,7 +171,7 @@ CWLSHMResource::CWLSHMResource(SP resource_) : resource(resource_) { }); // send a few supported formats. No need for any other I think? - for (auto& s : PROTO::shm->shmFormats) { + for (auto const& s : PROTO::shm->shmFormats) { resource->sendFormat((wl_shm_format)s); } } @@ -195,11 +193,8 @@ void CWLSHMProtocol::bindManager(wl_client* client, void* data, uint32_t ver, ui DRM_FORMAT_XBGR8888, DRM_FORMAT_ABGR8888, DRM_FORMAT_XRGB2101010, DRM_FORMAT_ARGB2101010, DRM_FORMAT_XBGR2101010, DRM_FORMAT_ABGR2101010, }; - for (auto& fmt : g_pHyprOpenGL->getDRMFormats()) { - if (std::find(supportedShmFourccFormats.begin(), supportedShmFourccFormats.end(), fmt.drmFormat) == supportedShmFourccFormats.end()) - continue; - - shmFormats.push_back(fmt.drmFormat); + for (auto const& fmt : supportedShmFourccFormats) { + shmFormats.push_back(fmt); } } diff --git a/src/protocols/core/Subcompositor.cpp b/src/protocols/core/Subcompositor.cpp index 2a7c06dc..46e20305 100644 --- a/src/protocols/core/Subcompositor.cpp +++ b/src/protocols/core/Subcompositor.cpp @@ -2,8 +2,6 @@ #include "Compositor.hpp" #include -#define LOGM PROTO::subcompositor->protoLog - CWLSubsurfaceResource::CWLSubsurfaceResource(SP resource_, SP surface_, SP parent_) : surface(surface_), parent(parent_), resource(resource_) { if (!good()) @@ -24,7 +22,7 @@ CWLSubsurfaceResource::CWLSubsurfaceResource(SP resource_, SP void { - for (auto& c : parent->subsurfaces) { + for (auto const& c : parent->subsurfaces) { if (c->zIndex >= idx) c->zIndex++; } @@ -55,7 +53,7 @@ CWLSubsurfaceResource::CWLSubsurfaceResource(SP resource_, SP void { - for (auto& c : parent->subsurfaces) { + for (auto const& c : parent->subsurfaces) { if (c->zIndex <= idx) c->zIndex--; } diff --git a/src/protocols/types/Buffer.cpp b/src/protocols/types/Buffer.cpp index 40f2eaf8..ea12f017 100644 --- a/src/protocols/types/Buffer.cpp +++ b/src/protocols/types/Buffer.cpp @@ -9,11 +9,6 @@ void IHLBuffer::sendRelease() { resource->sendRelease(); } -void IHLBuffer::sendReleaseWithSurface(SP surf) { - if (resource && resource->good()) - resource->sendReleaseWithSurface(surf); -} - void IHLBuffer::lock() { nLocks++; } @@ -27,26 +22,13 @@ void IHLBuffer::unlock() { sendRelease(); } -void IHLBuffer::unlockWithSurface(SP surf) { - nLocks--; - - ASSERT(nLocks >= 0); - - if (nLocks == 0) - sendReleaseWithSurface(surf); -} - bool IHLBuffer::locked() { return nLocks > 0; } void IHLBuffer::unlockOnBufferRelease(WP surf) { - unlockSurface = surf; hlEvents.backendRelease = events.backendRelease.registerListener([this](std::any data) { - if (unlockSurface.expired()) - unlock(); - else - unlockWithSurface(unlockSurface.lock()); + unlock(); hlEvents.backendRelease.reset(); }); } @@ -59,8 +41,5 @@ CHLBufferReference::~CHLBufferReference() { if (buffer.expired()) return; - if (surface) - buffer->unlockWithSurface(surface.lock()); - else - buffer->unlock(); + buffer->unlock(); } diff --git a/src/protocols/types/Buffer.hpp b/src/protocols/types/Buffer.hpp index d2157181..7d84dce7 100644 --- a/src/protocols/types/Buffer.hpp +++ b/src/protocols/types/Buffer.hpp @@ -6,6 +6,8 @@ #include +class CSyncReleaser; + class IHLBuffer : public Aquamarine::IBuffer { public: virtual ~IHLBuffer(); @@ -15,10 +17,8 @@ class IHLBuffer : public Aquamarine::IBuffer { virtual bool isSynchronous() = 0; // whether the updates to this buffer are synchronous, aka happen over cpu virtual bool good() = 0; virtual void sendRelease(); - virtual void sendReleaseWithSurface(SP); virtual void lock(); virtual void unlock(); - virtual void unlockWithSurface(SP surf); virtual bool locked(); void unlockOnBufferRelease(WP surf /* optional */); @@ -29,12 +29,11 @@ class IHLBuffer : public Aquamarine::IBuffer { struct { CHyprSignalListener backendRelease; + CHyprSignalListener backendRelease2; // for explicit ds } hlEvents; private: - int nLocks = 0; - - WP unlockSurface; + int nLocks = 0; }; // for ref-counting. Releases in ~dtor @@ -44,7 +43,8 @@ class CHLBufferReference { CHLBufferReference(SP buffer, SP surface); ~CHLBufferReference(); - WP buffer; + WP buffer; + SP releaser; private: WP surface; diff --git a/src/protocols/types/DMABuffer.cpp b/src/protocols/types/DMABuffer.cpp index 63a26c76..a8c7248e 100644 --- a/src/protocols/types/DMABuffer.cpp +++ b/src/protocols/types/DMABuffer.cpp @@ -16,8 +16,15 @@ CDMABuffer::CDMABuffer(uint32_t id, wl_client* client, Aquamarine::SDMABUFAttrs auto eglImage = g_pHyprOpenGL->createEGLImage(attrs); - if (!eglImage) - return; + if (!eglImage) { + Debug::log(ERR, "CDMABuffer: failed to import EGLImage, retrying as implicit"); + attrs.modifier = DRM_FORMAT_MOD_INVALID; + eglImage = g_pHyprOpenGL->createEGLImage(attrs); + if (!eglImage) { + Debug::log(ERR, "CDMABuffer: failed to import EGLImage"); + return; + } + } texture = makeShared(attrs, eglImage); // texture takes ownership of the eglImage opaque = FormatUtils::isFormatOpaque(attrs.format); diff --git a/src/protocols/types/DataDevice.cpp b/src/protocols/types/DataDevice.cpp index eb6969cc..36a7a157 100644 --- a/src/protocols/types/DataDevice.cpp +++ b/src/protocols/types/DataDevice.cpp @@ -19,3 +19,11 @@ void IDataSource::markUsed() { eDataSourceType IDataSource::type() { return DATA_SOURCE_TYPE_WAYLAND; } + +void IDataSource::sendDndFinished() { + ; +} + +uint32_t IDataSource::actions() { + return 7; // all +} diff --git a/src/protocols/types/DataDevice.hpp b/src/protocols/types/DataDevice.hpp index 948f47a0..a62cc35e 100644 --- a/src/protocols/types/DataDevice.hpp +++ b/src/protocols/types/DataDevice.hpp @@ -21,10 +21,12 @@ class IDataSource { virtual void cancelled() = 0; virtual bool hasDnd(); virtual bool dndDone(); + virtual void sendDndFinished(); virtual bool used(); virtual void markUsed(); virtual void error(uint32_t code, const std::string& msg) = 0; virtual eDataSourceType type(); + virtual uint32_t actions(); // wl_data_device_manager.dnd_action struct { CSignal destroy; diff --git a/src/protocols/types/WLBuffer.cpp b/src/protocols/types/WLBuffer.cpp index d34a867d..eb7d1fde 100644 --- a/src/protocols/types/WLBuffer.cpp +++ b/src/protocols/types/WLBuffer.cpp @@ -32,16 +32,6 @@ void CWLBufferResource::sendRelease() { resource->sendRelease(); } -void CWLBufferResource::sendReleaseWithSurface(SP surf) { - sendRelease(); - - if (!surf || !surf->syncobj) - return; - - if (drmSyncobjTimelineSignal(g_pCompositor->m_iDRMFD, &surf->syncobj->releaseTimeline->timeline->handle, &surf->syncobj->releasePoint, 1)) - Debug::log(ERR, "sendReleaseWithSurface: drmSyncobjTimelineSignal failed"); -} - wl_resource* CWLBufferResource::getResource() { return resource->resource(); } diff --git a/src/protocols/types/WLBuffer.hpp b/src/protocols/types/WLBuffer.hpp index 59512128..787abe1f 100644 --- a/src/protocols/types/WLBuffer.hpp +++ b/src/protocols/types/WLBuffer.hpp @@ -17,7 +17,6 @@ class CWLBufferResource { bool good(); void sendRelease(); - void sendReleaseWithSurface(SP); wl_resource* getResource(); WP buffer; diff --git a/src/render/Framebuffer.cpp b/src/render/Framebuffer.cpp index 67629e23..c48ff6f3 100644 --- a/src/render/Framebuffer.cpp +++ b/src/render/Framebuffer.cpp @@ -12,9 +12,10 @@ bool CFramebuffer::alloc(int w, int h, uint32_t drmFormat) { uint32_t glFormat = FormatUtils::drmFormatToGL(drmFormat); uint32_t glType = FormatUtils::glFormatToType(glFormat); - if (m_iFb == (uint32_t)-1) { + if (!m_iFbAllocated) { firstAlloc = true; glGenFramebuffers(1, &m_iFb); + m_iFbAllocated = true; } if (m_cTex->m_iTexID == 0) { @@ -88,12 +89,12 @@ void CFramebuffer::bind() { } void CFramebuffer::release() { - if (m_iFb != (uint32_t)-1 && m_iFb) + if (m_iFbAllocated) glDeleteFramebuffers(1, &m_iFb); m_cTex->destroyTexture(); - m_iFb = -1; - m_vSize = Vector2D(); + m_iFbAllocated = false; + m_vSize = Vector2D(); } CFramebuffer::~CFramebuffer() { @@ -101,5 +102,5 @@ CFramebuffer::~CFramebuffer() { } bool CFramebuffer::isAllocated() { - return m_iFb != (GLuint)-1; + return m_iFbAllocated; } \ No newline at end of file diff --git a/src/render/Framebuffer.hpp b/src/render/Framebuffer.hpp index a46a4859..ca7f9e8a 100644 --- a/src/render/Framebuffer.hpp +++ b/src/render/Framebuffer.hpp @@ -18,7 +18,8 @@ class CFramebuffer { Vector2D m_vSize; SP m_cTex; - GLuint m_iFb = -1; + GLuint m_iFb; + bool m_iFbAllocated{false}; SP m_pStencilTex; }; \ No newline at end of file diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index ea388df0..833e2ccb 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -213,7 +213,7 @@ EGLDeviceEXT CHyprOpenGLImpl::eglDeviceFromDRMFD(int drmFD) { return EGL_NO_DEVICE_EXT; } - for (auto& d : devices) { + for (auto const& d : devices) { auto devName = m_sProc.eglQueryDeviceStringEXT(d, EGL_DRM_DEVICE_FILE_EXT); if (!devName) continue; @@ -338,6 +338,8 @@ CHyprOpenGLImpl::CHyprOpenGLImpl() { initDRMFormats(); + initAssets(); + static auto P = g_pHookSystem->hookDynamic("preRender", [&](void* self, SCallbackInfo& info, std::any data) { preRender(std::any_cast(data)); }); RASSERT(eglMakeCurrent(m_pEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT), "Couldn't unset current EGL!"); @@ -435,7 +437,7 @@ void CHyprOpenGLImpl::initDRMFormats() { std::vector dmaFormats; - for (auto& fmt : formats) { + for (auto const& fmt : formats) { std::vector mods; if (!DISABLE_MODS) { auto ret = getModsForFormat(fmt); @@ -460,7 +462,7 @@ void CHyprOpenGLImpl::initDRMFormats() { auto fmtName = drmGetFormatName(fmt); Debug::log(LOG, "EGL: GPU Supports Format {} (0x{:x})", fmtName ? fmtName : "?unknown?", fmt); - for (auto& mod : mods) { + for (auto const& mod : mods) { auto modName = drmGetFormatModifierName(mod); modifierData.emplace_back(std::make_pair<>(mod, modName ? modName : "?unknown?")); free(modName); @@ -476,7 +478,7 @@ void CHyprOpenGLImpl::initDRMFormats() { return true; }); - for (auto& [m, name] : modifierData) { + for (auto const& [m, name] : modifierData) { Debug::log(LOG, "EGL: | with modifier {} (0x{:x})", name, m); mods.emplace_back(m); } @@ -654,7 +656,7 @@ bool CHyprOpenGLImpl::passRequiresIntrospection(CMonitor* pMonitor) { if (!pMonitor->solitaryClient.expired()) return false; - for (auto& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]) { + for (auto const& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]) { const auto XRAYMODE = ls->xray == -1 ? *PXRAY : ls->xray; if (ls->forceBlur && !XRAYMODE) return true; @@ -663,7 +665,7 @@ bool CHyprOpenGLImpl::passRequiresIntrospection(CMonitor* pMonitor) { return true; } - for (auto& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) { + for (auto const& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) { const auto XRAYMODE = ls->xray == -1 ? *PXRAY : ls->xray; if (ls->forceBlur && !XRAYMODE) return true; @@ -673,7 +675,7 @@ bool CHyprOpenGLImpl::passRequiresIntrospection(CMonitor* pMonitor) { } // these two block optimization - for (auto& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]) { + for (auto const& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]) { if (ls->forceBlur) return true; @@ -681,7 +683,7 @@ bool CHyprOpenGLImpl::passRequiresIntrospection(CMonitor* pMonitor) { return true; } - for (auto& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]) { + for (auto const& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]) { if (ls->forceBlur) return true; @@ -690,7 +692,7 @@ bool CHyprOpenGLImpl::passRequiresIntrospection(CMonitor* pMonitor) { } if (*PBLURSPECIAL) { - for (auto& ws : g_pCompositor->m_vWorkspaces) { + for (auto const& ws : g_pCompositor->m_vWorkspaces) { if (!ws->m_bIsSpecialWorkspace || ws->m_iMonitorID != pMonitor->ID) continue; @@ -704,7 +706,7 @@ bool CHyprOpenGLImpl::passRequiresIntrospection(CMonitor* pMonitor) { if (*PXRAY) return false; - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (!w->m_bIsMapped || w->isHidden()) continue; @@ -753,14 +755,12 @@ void CHyprOpenGLImpl::beginSimple(CMonitor* pMonitor, const CRegion& damage, SP< glViewport(0, 0, pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y); - matrixProjection(m_RenderData.projection, pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y, WL_OUTPUT_TRANSFORM_NORMAL); + m_RenderData.projection = Mat3x3::outputProjection(pMonitor->vecPixelSize, HYPRUTILS_TRANSFORM_NORMAL); - matrixIdentity(m_RenderData.monitorProjection.data()); + m_RenderData.monitorProjection = Mat3x3::identity(); if (pMonitor->transform != WL_OUTPUT_TRANSFORM_NORMAL) { const Vector2D tfmd = pMonitor->transform % 2 == 1 ? Vector2D{FBO->m_vSize.y, FBO->m_vSize.x} : FBO->m_vSize; - matrixTranslate(m_RenderData.monitorProjection.data(), FBO->m_vSize.x / 2.0, FBO->m_vSize.y / 2.0); - matrixTransform(m_RenderData.monitorProjection.data(), wlTransformToHyprutils(pMonitor->transform)); - matrixTranslate(m_RenderData.monitorProjection.data(), -tfmd.x / 2.0, -tfmd.y / 2.0); + m_RenderData.monitorProjection.translate(FBO->m_vSize / 2.0).transform(wlTransformToHyprutils(pMonitor->transform)).translate(-tfmd / 2.0); } m_RenderData.pCurrentMonData = &m_mMonitorRenderResources[pMonitor]; @@ -807,7 +807,7 @@ void CHyprOpenGLImpl::begin(CMonitor* pMonitor, const CRegion& damage_, CFramebu glViewport(0, 0, pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y); - matrixProjection(m_RenderData.projection, pMonitor->vecPixelSize.x, pMonitor->vecPixelSize.y, WL_OUTPUT_TRANSFORM_NORMAL); + m_RenderData.projection = Mat3x3::outputProjection(pMonitor->vecPixelSize, HYPRUTILS_TRANSFORM_NORMAL); m_RenderData.monitorProjection = pMonitor->projMatrix; @@ -1168,7 +1168,7 @@ void CHyprOpenGLImpl::clear(const CColor& color) { glClearColor(color.r, color.g, color.b, color.a); if (!m_RenderData.damage.empty()) { - for (auto& RECT : m_RenderData.damage.getRects()) { + for (auto const& RECT : m_RenderData.damage.getRects()) { scissor(&RECT); glClear(GL_COLOR_BUFFER_BIT); } @@ -1247,14 +1247,14 @@ void CHyprOpenGLImpl::renderRectWithBlur(CBox* box, const CColor& col, int round glEnable(GL_STENCIL_TEST); - glStencilFunc(GL_ALWAYS, 1, -1); + glStencilFunc(GL_ALWAYS, 1, 0xFF); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); renderRect(box, CColor(0, 0, 0, 0), round); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glStencilFunc(GL_EQUAL, 1, -1); + glStencilFunc(GL_EQUAL, 1, 0xFF); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); scissor(box); @@ -1269,7 +1269,7 @@ void CHyprOpenGLImpl::renderRectWithBlur(CBox* box, const CColor& col, int round glClearStencil(0); glClear(GL_STENCIL_BUFFER_BIT); glDisable(GL_STENCIL_TEST); - glStencilMask(-1); + glStencilMask(0xFF); glStencilFunc(GL_ALWAYS, 1, 0xFF); scissor((CBox*)nullptr); @@ -1287,20 +1287,17 @@ void CHyprOpenGLImpl::renderRectWithDamage(CBox* box, const CColor& col, CRegion box = &newBox; - float matrix[9]; - projectBox(matrix, newBox, wlTransformToHyprutils(invertTransform(!m_bEndFrame ? WL_OUTPUT_TRANSFORM_NORMAL : m_RenderData.pMonitor->transform)), newBox.rot, - m_RenderData.monitorProjection.data()); - - float glMatrix[9]; - matrixMultiply(glMatrix, m_RenderData.projection, matrix); + Mat3x3 matrix = m_RenderData.monitorProjection.projectBox( + newBox, wlTransformToHyprutils(invertTransform(!m_bEndFrame ? WL_OUTPUT_TRANSFORM_NORMAL : m_RenderData.pMonitor->transform)), newBox.rot); + Mat3x3 glMatrix = m_RenderData.projection.copy().multiply(matrix); glUseProgram(m_RenderData.pCurrentMonData->m_shQUAD.program); #ifndef GLES2 - glUniformMatrix3fv(m_RenderData.pCurrentMonData->m_shQUAD.proj, 1, GL_TRUE, glMatrix); + glUniformMatrix3fv(m_RenderData.pCurrentMonData->m_shQUAD.proj, 1, GL_TRUE, glMatrix.getMatrix().data()); #else - matrixTranspose(glMatrix, glMatrix); - glUniformMatrix3fv(m_RenderData.pCurrentMonData->m_shQUAD.proj, 1, GL_FALSE, glMatrix); + glMatrix.transpose(); + glUniformMatrix3fv(m_RenderData.pCurrentMonData->m_shQUAD.proj, 1, GL_FALSE, glMatrix.getMatrix().data()); #endif // premultiply the color as well as we don't work with straight alpha @@ -1327,13 +1324,13 @@ void CHyprOpenGLImpl::renderRectWithDamage(CBox* box, const CColor& col, CRegion damageClip.intersect(*damage); if (!damageClip.empty()) { - for (auto& RECT : damageClip.getRects()) { + for (auto const& RECT : damageClip.getRects()) { scissor(&RECT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } } } else { - for (auto& RECT : damage->getRects()) { + for (auto const& RECT : damage->getRects()) { scissor(&RECT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } @@ -1376,8 +1373,7 @@ void CHyprOpenGLImpl::renderTextureInternalWithDamage(SP tex, CBox* pB CBox newBox = *pBox; m_RenderData.renderModif.applyToBox(newBox); - static auto PDIMINACTIVE = CConfigValue("decoration:dim_inactive"); - static auto PDT = CConfigValue("debug:damage_tracking"); + static auto PDT = CConfigValue("debug:damage_tracking"); // get the needed transform for this texture const bool TRANSFORMS_MATCH = wlTransformToHyprutils(m_RenderData.pMonitor->transform) == tex->m_eTransform; // FIXME: combine them properly!!! @@ -1385,11 +1381,8 @@ void CHyprOpenGLImpl::renderTextureInternalWithDamage(SP tex, CBox* pB if (m_bEndFrame || TRANSFORMS_MATCH) TRANSFORM = wlTransformToHyprutils(invertTransform(m_RenderData.pMonitor->transform)); - float matrix[9]; - projectBox(matrix, newBox, TRANSFORM, newBox.rot, m_RenderData.monitorProjection.data()); - - float glMatrix[9]; - matrixMultiply(glMatrix, m_RenderData.projection, matrix); + Mat3x3 matrix = m_RenderData.monitorProjection.projectBox(newBox, TRANSFORM, newBox.rot); + Mat3x3 glMatrix = m_RenderData.projection.copy().multiply(matrix); if (waitTimeline != nullptr) { if (!waitForTimelinePoint(waitTimeline, waitPoint)) { @@ -1430,6 +1423,9 @@ void CHyprOpenGLImpl::renderTextureInternalWithDamage(SP tex, CBox* pB glActiveTexture(GL_TEXTURE0); glBindTexture(tex->m_iTarget, tex->m_iTexID); + glTexParameteri(tex->m_iTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(tex->m_iTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + if (m_RenderData.useNearestNeighbor) { glTexParameteri(tex->m_iTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(tex->m_iTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST); @@ -1441,10 +1437,10 @@ void CHyprOpenGLImpl::renderTextureInternalWithDamage(SP tex, CBox* pB glUseProgram(shader->program); #ifndef GLES2 - glUniformMatrix3fv(shader->proj, 1, GL_TRUE, glMatrix); + glUniformMatrix3fv(shader->proj, 1, GL_TRUE, glMatrix.getMatrix().data()); #else - matrixTranspose(glMatrix, glMatrix); - glUniformMatrix3fv(shader->proj, 1, GL_FALSE, glMatrix); + glMatrix.transpose(); + glUniformMatrix3fv(shader->proj, 1, GL_FALSE, glMatrix.getMatrix().data()); #endif glUniform1i(shader->tex, 0); @@ -1491,7 +1487,7 @@ void CHyprOpenGLImpl::renderTextureInternalWithDamage(SP tex, CBox* pB glUniform2f(shader->fullSize, FULLSIZE.x, FULLSIZE.y); glUniform1f(shader->radius, round); - if (allowDim && m_pCurrentWindow.lock() && *PDIMINACTIVE) { + if (allowDim && m_pCurrentWindow.lock()) { glUniform1i(shader->applyTint, 1); const auto DIM = m_pCurrentWindow->m_fDimPercent.value(); glUniform3f(shader->tint, 1.f - DIM, 1.f - DIM, 1.f - DIM); @@ -1523,13 +1519,13 @@ void CHyprOpenGLImpl::renderTextureInternalWithDamage(SP tex, CBox* pB damageClip.intersect(*damage); if (!damageClip.empty()) { - for (auto& RECT : damageClip.getRects()) { + for (auto const& RECT : damageClip.getRects()) { scissor(&RECT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } } } else { - for (auto& RECT : damage->getRects()) { + for (auto const& RECT : damage->getRects()) { scissor(&RECT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } @@ -1555,13 +1551,10 @@ void CHyprOpenGLImpl::renderTexturePrimitive(SP tex, CBox* pBox) { // get transform const auto TRANSFORM = wlTransformToHyprutils(invertTransform(!m_bEndFrame ? WL_OUTPUT_TRANSFORM_NORMAL : m_RenderData.pMonitor->transform)); - float matrix[9]; - projectBox(matrix, newBox, TRANSFORM, newBox.rot, m_RenderData.monitorProjection.data()); + Mat3x3 matrix = m_RenderData.monitorProjection.projectBox(newBox, TRANSFORM, newBox.rot); + Mat3x3 glMatrix = m_RenderData.projection.copy().multiply(matrix); - float glMatrix[9]; - matrixMultiply(glMatrix, m_RenderData.projection, matrix); - - CShader* shader = &m_RenderData.pCurrentMonData->m_shPASSTHRURGBA; + CShader* shader = &m_RenderData.pCurrentMonData->m_shPASSTHRURGBA; glActiveTexture(GL_TEXTURE0); glBindTexture(tex->m_iTarget, tex->m_iTexID); @@ -1569,10 +1562,10 @@ void CHyprOpenGLImpl::renderTexturePrimitive(SP tex, CBox* pBox) { glUseProgram(shader->program); #ifndef GLES2 - glUniformMatrix3fv(shader->proj, 1, GL_TRUE, glMatrix); + glUniformMatrix3fv(shader->proj, 1, GL_TRUE, glMatrix.getMatrix().data()); #else - matrixTranspose(glMatrix, glMatrix); - glUniformMatrix3fv(shader->proj, 1, GL_FALSE, glMatrix); + glMatrix.transpose(); + glUniformMatrix3fv(shader->proj, 1, GL_FALSE, glMatrix.getMatrix().data()); #endif glUniform1i(shader->tex, 0); @@ -1582,7 +1575,7 @@ void CHyprOpenGLImpl::renderTexturePrimitive(SP tex, CBox* pBox) { glEnableVertexAttribArray(shader->posAttrib); glEnableVertexAttribArray(shader->texAttrib); - for (auto& RECT : m_RenderData.damage.getRects()) { + for (auto const& RECT : m_RenderData.damage.getRects()) { scissor(&RECT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } @@ -1609,21 +1602,18 @@ void CHyprOpenGLImpl::renderTextureMatte(SP tex, CBox* pBox, CFramebuf // get transform const auto TRANSFORM = wlTransformToHyprutils(invertTransform(!m_bEndFrame ? WL_OUTPUT_TRANSFORM_NORMAL : m_RenderData.pMonitor->transform)); - float matrix[9]; - projectBox(matrix, newBox, TRANSFORM, newBox.rot, m_RenderData.monitorProjection.data()); + Mat3x3 matrix = m_RenderData.monitorProjection.projectBox(newBox, TRANSFORM, newBox.rot); + Mat3x3 glMatrix = m_RenderData.projection.copy().multiply(matrix); - float glMatrix[9]; - matrixMultiply(glMatrix, m_RenderData.projection, matrix); - - CShader* shader = &m_RenderData.pCurrentMonData->m_shMATTE; + CShader* shader = &m_RenderData.pCurrentMonData->m_shMATTE; glUseProgram(shader->program); #ifndef GLES2 - glUniformMatrix3fv(shader->proj, 1, GL_TRUE, glMatrix); + glUniformMatrix3fv(shader->proj, 1, GL_TRUE, glMatrix.getMatrix().data()); #else - matrixTranspose(glMatrix, glMatrix); - glUniformMatrix3fv(shader->proj, 1, GL_FALSE, glMatrix); + glMatrix.transpose(); + glUniformMatrix3fv(shader->proj, 1, GL_FALSE, glMatrix.getMatrix().data()); #endif glUniform1i(shader->tex, 0); glUniform1i(shader->alphaMatte, 1); @@ -1640,7 +1630,7 @@ void CHyprOpenGLImpl::renderTextureMatte(SP tex, CBox* pBox, CFramebuf glEnableVertexAttribArray(shader->posAttrib); glEnableVertexAttribArray(shader->texAttrib); - for (auto& RECT : m_RenderData.damage.getRects()) { + for (auto const& RECT : m_RenderData.damage.getRects()) { scissor(&RECT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } @@ -1666,13 +1656,10 @@ CFramebuffer* CHyprOpenGLImpl::blurMainFramebufferWithDamage(float a, CRegion* o glDisable(GL_STENCIL_TEST); // get transforms for the full monitor - const auto TRANSFORM = wlTransformToHyprutils(invertTransform(m_RenderData.pMonitor->transform)); - float matrix[9]; + const auto TRANSFORM = wlTransformToHyprutils(invertTransform(m_RenderData.pMonitor->transform)); CBox MONITORBOX = {0, 0, m_RenderData.pMonitor->vecTransformedSize.x, m_RenderData.pMonitor->vecTransformedSize.y}; - projectBox(matrix, MONITORBOX, TRANSFORM, 0, m_RenderData.monitorProjection.data()); - - float glMatrix[9]; - matrixMultiply(glMatrix, m_RenderData.projection, matrix); + Mat3x3 matrix = m_RenderData.monitorProjection.projectBox(MONITORBOX, TRANSFORM); + Mat3x3 glMatrix = m_RenderData.projection.copy().multiply(matrix); // get the config settings static auto PBLURSIZE = CConfigValue("decoration:blur:size"); @@ -1709,10 +1696,10 @@ CFramebuffer* CHyprOpenGLImpl::blurMainFramebufferWithDamage(float a, CRegion* o glUseProgram(m_RenderData.pCurrentMonData->m_shBLURPREPARE.program); #ifndef GLES2 - glUniformMatrix3fv(m_RenderData.pCurrentMonData->m_shBLURPREPARE.proj, 1, GL_TRUE, glMatrix); + glUniformMatrix3fv(m_RenderData.pCurrentMonData->m_shBLURPREPARE.proj, 1, GL_TRUE, glMatrix.getMatrix().data()); #else - matrixTranspose(glMatrix, glMatrix); - glUniformMatrix3fv(m_RenderData.pCurrentMonData->m_shBLURPREPARE.proj, 1, GL_FALSE, glMatrix); + glMatrix.transpose(); + glUniformMatrix3fv(m_RenderData.pCurrentMonData->m_shBLURPREPARE.proj, 1, GL_FALSE, glMatrix.getMatrix().data()); #endif glUniform1f(m_RenderData.pCurrentMonData->m_shBLURPREPARE.contrast, *PBLURCONTRAST); glUniform1f(m_RenderData.pCurrentMonData->m_shBLURPREPARE.brightness, *PBLURBRIGHTNESS); @@ -1725,7 +1712,7 @@ CFramebuffer* CHyprOpenGLImpl::blurMainFramebufferWithDamage(float a, CRegion* o glEnableVertexAttribArray(m_RenderData.pCurrentMonData->m_shBLURPREPARE.texAttrib); if (!damage.empty()) { - for (auto& RECT : damage.getRects()) { + for (auto const& RECT : damage.getRects()) { scissor(&RECT, false /* this region is already transformed */); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } @@ -1754,10 +1741,10 @@ CFramebuffer* CHyprOpenGLImpl::blurMainFramebufferWithDamage(float a, CRegion* o // prep two shaders #ifndef GLES2 - glUniformMatrix3fv(pShader->proj, 1, GL_TRUE, glMatrix); + glUniformMatrix3fv(pShader->proj, 1, GL_TRUE, glMatrix.getMatrix().data()); #else - matrixTranspose(glMatrix, glMatrix); - glUniformMatrix3fv(pShader->proj, 1, GL_FALSE, glMatrix); + glMatrix.transpose(); + glUniformMatrix3fv(pShader->proj, 1, GL_FALSE, glMatrix.getMatrix().data()); #endif glUniform1f(pShader->radius, *PBLURSIZE * a); // this makes the blursize change with a if (pShader == &m_RenderData.pCurrentMonData->m_shBLUR1) { @@ -1778,7 +1765,7 @@ CFramebuffer* CHyprOpenGLImpl::blurMainFramebufferWithDamage(float a, CRegion* o glEnableVertexAttribArray(pShader->texAttrib); if (!pDamage->empty()) { - for (auto& RECT : pDamage->getRects()) { + for (auto const& RECT : pDamage->getRects()) { scissor(&RECT, false /* this region is already transformed */); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } @@ -1802,12 +1789,12 @@ CFramebuffer* CHyprOpenGLImpl::blurMainFramebufferWithDamage(float a, CRegion* o CRegion tempDamage{damage}; // and draw - for (int i = 1; i <= *PBLURPASSES; ++i) { + for (auto i = 1; i <= *PBLURPASSES; ++i) { tempDamage = damage.copy().scale(1.f / (1 << i)); drawPass(&m_RenderData.pCurrentMonData->m_shBLUR1, &tempDamage); // down } - for (int i = *PBLURPASSES - 1; i >= 0; --i) { + for (auto i = *PBLURPASSES - 1; i >= 0; --i) { tempDamage = damage.copy().scale(1.f / (1 << i)); // when upsampling we make the region twice as big drawPass(&m_RenderData.pCurrentMonData->m_shBLUR2, &tempDamage); // up } @@ -1831,10 +1818,10 @@ CFramebuffer* CHyprOpenGLImpl::blurMainFramebufferWithDamage(float a, CRegion* o glUseProgram(m_RenderData.pCurrentMonData->m_shBLURFINISH.program); #ifndef GLES2 - glUniformMatrix3fv(m_RenderData.pCurrentMonData->m_shBLURFINISH.proj, 1, GL_TRUE, glMatrix); + glUniformMatrix3fv(m_RenderData.pCurrentMonData->m_shBLURFINISH.proj, 1, GL_TRUE, glMatrix.getMatrix().data()); #else - matrixTranspose(glMatrix, glMatrix); - glUniformMatrix3fv(m_RenderData.pCurrentMonData->m_shBLURFINISH.proj, 1, GL_FALSE, glMatrix); + glMatrix.transpose(); + glUniformMatrix3fv(m_RenderData.pCurrentMonData->m_shBLURFINISH.proj, 1, GL_FALSE, glMatrix.getMatrix().data()); #endif glUniform1f(m_RenderData.pCurrentMonData->m_shBLURFINISH.noise, *PBLURNOISE); glUniform1f(m_RenderData.pCurrentMonData->m_shBLURFINISH.brightness, *PBLURBRIGHTNESS); @@ -1848,7 +1835,7 @@ CFramebuffer* CHyprOpenGLImpl::blurMainFramebufferWithDamage(float a, CRegion* o glEnableVertexAttribArray(m_RenderData.pCurrentMonData->m_shBLURFINISH.texAttrib); if (!damage.empty()) { - for (auto& RECT : damage.getRects()) { + for (auto const& RECT : damage.getRects()) { scissor(&RECT, false /* this region is already transformed */); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } @@ -1924,7 +1911,7 @@ void CHyprOpenGLImpl::preRender(CMonitor* pMonitor) { }; bool hasWindows = false; - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (w->m_pWorkspace == pMonitor->activeWorkspace && !w->isHidden() && w->m_bIsMapped && (!w->m_bIsFloating || *PBLURXRAY)) { // check if window is valid @@ -1936,9 +1923,9 @@ void CHyprOpenGLImpl::preRender(CMonitor* pMonitor) { } } - for (auto& m : g_pCompositor->m_vMonitors) { - for (auto& lsl : m->m_aLayerSurfaceLayers) { - for (auto& ls : lsl) { + for (auto const& m : g_pCompositor->m_vMonitors) { + for (auto const& lsl : m->m_aLayerSurfaceLayers) { + for (auto const& ls : lsl) { if (!ls->layerSurface || ls->xray != 1) continue; @@ -2091,7 +2078,7 @@ void CHyprOpenGLImpl::renderTextureWithBlur(SP tex, CBox* pBox, float glEnable(GL_STENCIL_TEST); - glStencilFunc(GL_ALWAYS, 1, -1); + glStencilFunc(GL_ALWAYS, 1, 0xFF); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); @@ -2101,7 +2088,7 @@ void CHyprOpenGLImpl::renderTextureWithBlur(SP tex, CBox* pBox, float renderTexture(tex, pBox, a, round, true, true); // discard opaque glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glStencilFunc(GL_EQUAL, 1, -1); + glStencilFunc(GL_EQUAL, 1, 0xFF); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); // stencil done. Render everything. @@ -2124,7 +2111,7 @@ void CHyprOpenGLImpl::renderTextureWithBlur(SP tex, CBox* pBox, float glDisable(GL_STENCIL_TEST); renderTextureInternalWithDamage(tex, pBox, a, &texDamage, round, false, false, true, true); - glStencilMask(-1); + glStencilMask(0xFF); glStencilFunc(GL_ALWAYS, 1, 0xFF); scissor((CBox*)nullptr); } @@ -2164,12 +2151,9 @@ void CHyprOpenGLImpl::renderBorder(CBox* box, const CGradientValueData& grad, in round += round == 0 ? 0 : scaledBorderSize; - float matrix[9]; - projectBox(matrix, newBox, wlTransformToHyprutils(invertTransform(!m_bEndFrame ? WL_OUTPUT_TRANSFORM_NORMAL : m_RenderData.pMonitor->transform)), newBox.rot, - m_RenderData.monitorProjection.data()); - - float glMatrix[9]; - matrixMultiply(glMatrix, m_RenderData.projection, matrix); + Mat3x3 matrix = m_RenderData.monitorProjection.projectBox( + newBox, wlTransformToHyprutils(invertTransform(!m_bEndFrame ? WL_OUTPUT_TRANSFORM_NORMAL : m_RenderData.pMonitor->transform)), newBox.rot); + Mat3x3 glMatrix = m_RenderData.projection.copy().multiply(matrix); const auto BLEND = m_bBlend; blend(true); @@ -2177,10 +2161,10 @@ void CHyprOpenGLImpl::renderBorder(CBox* box, const CGradientValueData& grad, in glUseProgram(m_RenderData.pCurrentMonData->m_shBORDER1.program); #ifndef GLES2 - glUniformMatrix3fv(m_RenderData.pCurrentMonData->m_shBORDER1.proj, 1, GL_TRUE, glMatrix); + glUniformMatrix3fv(m_RenderData.pCurrentMonData->m_shBORDER1.proj, 1, GL_TRUE, glMatrix.getMatrix().data()); #else - matrixTranspose(glMatrix, glMatrix); - glUniformMatrix3fv(m_RenderData.pCurrentMonData->m_shBORDER1.proj, 1, GL_FALSE, glMatrix); + glMatrix.transpose(); + glUniformMatrix3fv(m_RenderData.pCurrentMonData->m_shBORDER1.proj, 1, GL_FALSE, glMatrix.getMatrix().data()); #endif static_assert(sizeof(CColor) == 4 * sizeof(float)); // otherwise the line below this will fail @@ -2215,13 +2199,13 @@ void CHyprOpenGLImpl::renderBorder(CBox* box, const CGradientValueData& grad, in damageClip.intersect(m_RenderData.damage); if (!damageClip.empty()) { - for (auto& RECT : damageClip.getRects()) { + for (auto const& RECT : damageClip.getRects()) { scissor(&RECT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } } } else { - for (auto& RECT : m_RenderData.damage.getRects()) { + for (auto const& RECT : m_RenderData.damage.getRects()) { scissor(&RECT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } @@ -2470,22 +2454,19 @@ void CHyprOpenGLImpl::renderRoundedShadow(CBox* box, int round, int range, const const auto col = color; - float matrix[9]; - projectBox(matrix, newBox, wlTransformToHyprutils(invertTransform(!m_bEndFrame ? WL_OUTPUT_TRANSFORM_NORMAL : m_RenderData.pMonitor->transform)), newBox.rot, - m_RenderData.monitorProjection.data()); - - float glMatrix[9]; - matrixMultiply(glMatrix, m_RenderData.projection, matrix); + Mat3x3 matrix = m_RenderData.monitorProjection.projectBox( + newBox, wlTransformToHyprutils(invertTransform(!m_bEndFrame ? WL_OUTPUT_TRANSFORM_NORMAL : m_RenderData.pMonitor->transform)), newBox.rot); + Mat3x3 glMatrix = m_RenderData.projection.copy().multiply(matrix); glEnable(GL_BLEND); glUseProgram(m_RenderData.pCurrentMonData->m_shSHADOW.program); #ifndef GLES2 - glUniformMatrix3fv(m_RenderData.pCurrentMonData->m_shSHADOW.proj, 1, GL_TRUE, glMatrix); + glUniformMatrix3fv(m_RenderData.pCurrentMonData->m_shSHADOW.proj, 1, GL_TRUE, glMatrix.getMatrix().data()); #else - matrixTranspose(glMatrix, glMatrix); - glUniformMatrix3fv(m_RenderData.pCurrentMonData->m_shSHADOW.proj, 1, GL_FALSE, glMatrix); + glMatrix.transpose(); + glUniformMatrix3fv(m_RenderData.pCurrentMonData->m_shSHADOW.proj, 1, GL_FALSE, glMatrix.getMatrix().data()); #endif glUniform4f(m_RenderData.pCurrentMonData->m_shSHADOW.color, col.r, col.g, col.b, col.a * a); @@ -2512,13 +2493,13 @@ void CHyprOpenGLImpl::renderRoundedShadow(CBox* box, int round, int range, const damageClip.intersect(m_RenderData.damage); if (!damageClip.empty()) { - for (auto& RECT : damageClip.getRects()) { + for (auto const& RECT : damageClip.getRects()) { scissor(&RECT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } } } else { - for (auto& RECT : m_RenderData.damage.getRects()) { + for (auto const& RECT : m_RenderData.damage.getRects()) { scissor(&RECT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } @@ -2564,11 +2545,11 @@ void CHyprOpenGLImpl::renderMirrored() { return; // replace monitor projection to undo the mirrored monitor's projection - matrixIdentity(m_RenderData.monitorProjection.data()); - matrixTranslate(m_RenderData.monitorProjection.data(), monitor->vecPixelSize.x / 2.0, monitor->vecPixelSize.y / 2.0); - matrixTransform(m_RenderData.monitorProjection.data(), wlTransformToHyprutils(monitor->transform)); - matrixTransform(m_RenderData.monitorProjection.data(), wlTransformToHyprutils(invertTransform(mirrored->transform))); - matrixTranslate(m_RenderData.monitorProjection.data(), -monitor->vecTransformedSize.x / 2.0, -monitor->vecTransformedSize.y / 2.0); + m_RenderData.monitorProjection = Mat3x3::identity() + .translate(monitor->vecPixelSize / 2.0) + .transform(wlTransformToHyprutils(monitor->transform)) + .transform(wlTransformToHyprutils(invertTransform(mirrored->transform))) + .translate(-monitor->vecTransformedSize / 2.0); // clear stuff outside of mirrored area (e.g. when changing to mirrored) clear(CColor(0, 0, 0, 0)); @@ -2614,14 +2595,17 @@ void CHyprOpenGLImpl::renderSplash(cairo_t* const CAIRO, cairo_surface_t* const cairo_surface_flush(CAIROSURFACE); } -void CHyprOpenGLImpl::createBackgroundTexture(const std::string& texPath) { - const auto CAIROSURFACE = cairo_image_surface_create_from_png(texPath.c_str()); - const auto CAIROFORMAT = cairo_image_surface_get_format(CAIROSURFACE); +SP CHyprOpenGLImpl::loadAsset(const std::string& file) { + const auto CAIROSURFACE = cairo_image_surface_create_from_png(file.c_str()); - m_pBackgroundTexture = makeShared(); + if (!CAIROSURFACE) + return nullptr; - m_pBackgroundTexture->allocate(); - m_pBackgroundTexture->m_vSize = {cairo_image_surface_get_width(CAIROSURFACE), cairo_image_surface_get_height(CAIROSURFACE)}; + const auto CAIROFORMAT = cairo_image_surface_get_format(CAIROSURFACE); + auto tex = makeShared(); + + tex->allocate(); + tex->m_vSize = {cairo_image_surface_get_width(CAIROSURFACE), cairo_image_surface_get_height(CAIROSURFACE)}; const GLint glIFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? #ifdef GLES2 @@ -2634,7 +2618,7 @@ void CHyprOpenGLImpl::createBackgroundTexture(const std::string& texPath) { const GLint glType = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_FLOAT : GL_UNSIGNED_BYTE; const auto DATA = cairo_image_surface_get_data(CAIROSURFACE); - glBindTexture(GL_TEXTURE_2D, m_pBackgroundTexture->m_iTexID); + glBindTexture(GL_TEXTURE_2D, tex->m_iTexID); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); #ifndef GLES2 @@ -2643,9 +2627,105 @@ void CHyprOpenGLImpl::createBackgroundTexture(const std::string& texPath) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED); } #endif - glTexImage2D(GL_TEXTURE_2D, 0, glIFormat, m_pBackgroundTexture->m_vSize.x, m_pBackgroundTexture->m_vSize.y, 0, glFormat, glType, DATA); + glTexImage2D(GL_TEXTURE_2D, 0, glIFormat, tex->m_vSize.x, tex->m_vSize.y, 0, glFormat, glType, DATA); cairo_surface_destroy(CAIROSURFACE); + + return tex; +} + +SP CHyprOpenGLImpl::renderText(const std::string& text, CColor col, int pt, bool italic) { + SP tex = makeShared(); + + static auto FONT = CConfigValue("misc:font_family"); + + const auto FONTFAMILY = *FONT; + const auto FONTSIZE = pt; + const auto COLOR = col; + + auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1920, 1080 /* arbitrary, just for size */); + auto CAIRO = cairo_create(CAIROSURFACE); + + PangoLayout* layoutText = pango_cairo_create_layout(CAIRO); + PangoFontDescription* pangoFD = pango_font_description_new(); + + pango_font_description_set_family_static(pangoFD, FONTFAMILY.c_str()); + pango_font_description_set_absolute_size(pangoFD, FONTSIZE * PANGO_SCALE); + pango_font_description_set_style(pangoFD, italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL); + pango_font_description_set_weight(pangoFD, PANGO_WEIGHT_NORMAL); + pango_layout_set_font_description(layoutText, pangoFD); + + cairo_set_source_rgba(CAIRO, COLOR.r, COLOR.g, COLOR.b, COLOR.a); + + int textW = 0, textH = 0; + pango_layout_set_text(layoutText, text.c_str(), -1); + pango_layout_get_size(layoutText, &textW, &textH); + textW /= PANGO_SCALE; + textH /= PANGO_SCALE; + + pango_font_description_free(pangoFD); + g_object_unref(layoutText); + cairo_destroy(CAIRO); + cairo_surface_destroy(CAIROSURFACE); + + CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, textW, textH); + CAIRO = cairo_create(CAIROSURFACE); + + layoutText = pango_cairo_create_layout(CAIRO); + pangoFD = pango_font_description_new(); + + pango_font_description_set_family_static(pangoFD, FONTFAMILY.c_str()); + pango_font_description_set_absolute_size(pangoFD, FONTSIZE * PANGO_SCALE); + pango_font_description_set_style(pangoFD, italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL); + pango_font_description_set_weight(pangoFD, PANGO_WEIGHT_NORMAL); + pango_layout_set_font_description(layoutText, pangoFD); + pango_layout_set_text(layoutText, text.c_str(), -1); + + cairo_set_source_rgba(CAIRO, COLOR.r, COLOR.g, COLOR.b, COLOR.a); + + cairo_move_to(CAIRO, 0, 0); + pango_cairo_show_layout(CAIRO, layoutText); + + pango_font_description_free(pangoFD); + g_object_unref(layoutText); + + cairo_surface_flush(CAIROSURFACE); + + tex->allocate(); + tex->m_vSize = {cairo_image_surface_get_width(CAIROSURFACE), cairo_image_surface_get_height(CAIROSURFACE)}; + + const auto DATA = cairo_image_surface_get_data(CAIROSURFACE); + glBindTexture(GL_TEXTURE_2D, tex->m_iTexID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +#ifndef GLES2 + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED); +#endif + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex->m_vSize.x, tex->m_vSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA); + + cairo_destroy(CAIRO); + cairo_surface_destroy(CAIROSURFACE); + + return tex; +} + +void CHyprOpenGLImpl::initAssets() { + std::string assetsPath = ""; +#ifndef DATAROOTDIR + assetsPath = "/usr/share/hypr/"; +#else + assetsPath = std::format("{}{}", DATAROOTDIR, "/hypr/"); +#endif + + m_pLockDeadTexture = loadAsset(assetsPath + "lockdead.png"); + m_pLockDead2Texture = loadAsset(assetsPath + "lockdead2.png"); + + m_pLockTtyTextTexture = renderText(std::format("Running on tty {}", + g_pCompositor->m_pAqBackend->hasSession() && g_pCompositor->m_pAqBackend->session->vt > 0 ? + std::to_string(g_pCompositor->m_pAqBackend->session->vt) : + "unknown"), + CColor{0.9F, 0.9F, 0.9F, 0.7F}, 20, true); } void CHyprOpenGLImpl::createBGTextureForMonitor(CMonitor* pMonitor) { @@ -2688,15 +2768,13 @@ void CHyprOpenGLImpl::createBGTextureForMonitor(CMonitor* pMonitor) { texPath += ".png"; // check if wallpapers exist - if (!std::filesystem::exists(texPath)) { - // try local - texPath = texPath.substr(0, 5) + "local/" + texPath.substr(5); - - if (!std::filesystem::exists(texPath)) - return; // the texture will be empty, oh well. We'll clear with a solid color anyways. + std::error_code err; + if (!std::filesystem::exists(texPath, err)) { + Debug::log(ERR, "createBGTextureForMonitor: failed, file \"{}\" doesn't exist or access denied, ec: {}", texPath, err.message()); + return; // the texture will be empty, oh well. We'll clear with a solid color anyways. } - createBackgroundTexture(texPath); + m_pBackgroundTexture = loadAsset(texPath); } // create a new one with cairo @@ -2822,16 +2900,15 @@ void CHyprOpenGLImpl::destroyMonitorResources(CMonitor* pMonitor) { } void CHyprOpenGLImpl::saveMatrix() { - memcpy(m_RenderData.savedProjection, m_RenderData.projection, 9 * sizeof(float)); + m_RenderData.savedProjection = m_RenderData.projection; } void CHyprOpenGLImpl::setMatrixScaleTranslate(const Vector2D& translate, const float& scale) { - matrixScale(m_RenderData.projection, scale, scale); - matrixTranslate(m_RenderData.projection, translate.x, translate.y); + m_RenderData.projection.scale(scale).translate(translate); } void CHyprOpenGLImpl::restoreMatrix() { - memcpy(m_RenderData.projection, m_RenderData.savedProjection, 9 * sizeof(float)); + m_RenderData.projection = m_RenderData.savedProjection; } void CHyprOpenGLImpl::bindOffMain() { @@ -2870,7 +2947,7 @@ SP CHyprOpenGLImpl::createEGLSync(int fenceFD) { std::vector attribs; int dupFd = -1; if (fenceFD > 0) { - int dupFd = fcntl(fenceFD, F_DUPFD_CLOEXEC, 0); + dupFd = fcntl(fenceFD, F_DUPFD_CLOEXEC, 0); if (dupFd < 0) { Debug::log(ERR, "createEGLSync: dup failed"); return nullptr; @@ -2889,8 +2966,18 @@ SP CHyprOpenGLImpl::createEGLSync(int fenceFD) { return nullptr; } - auto eglsync = SP(new CEGLSync); - eglsync->sync = sync; + // we need to flush otherwise we might not get a valid fd + glFlush(); + + int fd = g_pHyprOpenGL->m_sProc.eglDupNativeFenceFDANDROID(g_pHyprOpenGL->m_pEglDisplay, sync); + if (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) { + Debug::log(ERR, "eglDupNativeFenceFDANDROID failed"); + return nullptr; + } + + auto eglsync = SP(new CEGLSync); + eglsync->sync = sync; + eglsync->m_iFd = fd; return eglsync; } @@ -2920,7 +3007,7 @@ void SRenderModifData::applyToBox(CBox& box) { if (!enabled) return; - for (auto& [type, val] : modifs) { + for (auto const& [type, val] : modifs) { try { switch (type) { case RMOD_TYPE_SCALE: box.scale(std::any_cast(val)); break; @@ -2945,7 +3032,7 @@ void SRenderModifData::applyToRegion(CRegion& rg) { if (!enabled) return; - for (auto& [type, val] : modifs) { + for (auto const& [type, val] : modifs) { try { switch (type) { case RMOD_TYPE_SCALE: rg.scale(std::any_cast(val)); break; @@ -2963,7 +3050,7 @@ float SRenderModifData::combinedScale() { return 1; float scale = 1.f; - for (auto& [type, val] : modifs) { + for (auto const& [type, val] : modifs) { try { switch (type) { case RMOD_TYPE_SCALE: scale *= std::any_cast(val); break; @@ -2983,19 +3070,13 @@ CEGLSync::~CEGLSync() { if (g_pHyprOpenGL->m_sProc.eglDestroySyncKHR(g_pHyprOpenGL->m_pEglDisplay, sync) != EGL_TRUE) Debug::log(ERR, "eglDestroySyncKHR failed"); + + if (m_iFd >= 0) + close(m_iFd); } -int CEGLSync::dupFenceFD() { - if (sync == EGL_NO_SYNC_KHR) - return -1; - - int fd = g_pHyprOpenGL->m_sProc.eglDupNativeFenceFDANDROID(g_pHyprOpenGL->m_pEglDisplay, sync); - if (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) { - Debug::log(ERR, "eglDupNativeFenceFDANDROID failed"); - return -1; - } - - return fd; +int CEGLSync::fd() { + return m_iFd; } bool CEGLSync::wait() { diff --git a/src/render/OpenGL.hpp b/src/render/OpenGL.hpp index 1e8325c1..0d1c267b 100644 --- a/src/render/OpenGL.hpp +++ b/src/render/OpenGL.hpp @@ -94,35 +94,35 @@ struct SMonitorRenderData { }; struct SCurrentRenderData { - CMonitor* pMonitor = nullptr; - PHLWORKSPACE pWorkspace = nullptr; - float projection[9]; - float savedProjection[9]; - std::array monitorProjection; + CMonitor* pMonitor = nullptr; + PHLWORKSPACE pWorkspace = nullptr; + Mat3x3 projection; + Mat3x3 savedProjection; + Mat3x3 monitorProjection; - SMonitorRenderData* pCurrentMonData = nullptr; - CFramebuffer* currentFB = nullptr; // current rendering to - CFramebuffer* mainFB = nullptr; // main to render to - CFramebuffer* outFB = nullptr; // out to render to (if offloaded, etc) + SMonitorRenderData* pCurrentMonData = nullptr; + CFramebuffer* currentFB = nullptr; // current rendering to + CFramebuffer* mainFB = nullptr; // main to render to + CFramebuffer* outFB = nullptr; // out to render to (if offloaded, etc) - CRegion damage; - CRegion finalDamage; // damage used for funal off -> main + CRegion damage; + CRegion finalDamage; // damage used for funal off -> main - SRenderModifData renderModif; - float mouseZoomFactor = 1.f; - bool mouseZoomUseMouse = true; // true by default - bool useNearestNeighbor = false; - bool forceIntrospection = false; // cleaned in ::end() - bool blockScreenShader = false; - bool simplePass = false; + SRenderModifData renderModif; + float mouseZoomFactor = 1.f; + bool mouseZoomUseMouse = true; // true by default + bool useNearestNeighbor = false; + bool forceIntrospection = false; // cleaned in ::end() + bool blockScreenShader = false; + bool simplePass = false; - Vector2D primarySurfaceUVTopLeft = Vector2D(-1, -1); - Vector2D primarySurfaceUVBottomRight = Vector2D(-1, -1); + Vector2D primarySurfaceUVTopLeft = Vector2D(-1, -1); + Vector2D primarySurfaceUVBottomRight = Vector2D(-1, -1); - CBox clipBox = {}; // scaled coordinates + CBox clipBox = {}; // scaled coordinates - uint32_t discardMode = DISCARD_OPAQUE; - float discardOpacity = 0.f; + uint32_t discardMode = DISCARD_OPAQUE; + float discardOpacity = 0.f; }; class CEGLSync { @@ -131,12 +131,14 @@ class CEGLSync { EGLSyncKHR sync = nullptr; - int dupFenceFD(); + int fd(); bool wait(); private: CEGLSync() = default; + int m_iFd = -1; + friend class CHyprOpenGLImpl; }; @@ -275,7 +277,7 @@ class CHyprOpenGLImpl { CShader m_sFinalScreenShader; CTimer m_tGlobalTimer; - SP m_pBackgroundTexture; + SP m_pBackgroundTexture, m_pLockDeadTexture, m_pLockDead2Texture, m_pLockTtyTextTexture; void logShaderError(const GLuint&, bool program = false); GLuint createProgram(const std::string&, const std::string&, bool dynamic = false); @@ -285,7 +287,9 @@ class CHyprOpenGLImpl { void initDRMFormats(); void initEGL(bool gbm); EGLDeviceEXT eglDeviceFromDRMFD(int drmFD); - void createBackgroundTexture(const std::string& path); + SP loadAsset(const std::string& file); + SP renderText(const std::string& text, CColor col, int pt, bool italic = false); + void initAssets(); // std::optional> getModsForFormat(EGLint format); diff --git a/src/render/Renderbuffer.cpp b/src/render/Renderbuffer.cpp index 58ed88d6..c4425ce9 100644 --- a/src/render/Renderbuffer.cpp +++ b/src/render/Renderbuffer.cpp @@ -35,7 +35,8 @@ CRenderbuffer::CRenderbuffer(SP buffer, uint32_t format) : glBindRenderbuffer(GL_RENDERBUFFER, 0); glGenFramebuffers(1, &m_sFramebuffer.m_iFb); - m_sFramebuffer.m_vSize = buffer->size; + m_sFramebuffer.m_iFbAllocated = true; + m_sFramebuffer.m_vSize = buffer->size; m_sFramebuffer.bind(); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_iRBO); diff --git a/src/render/Renderbuffer.hpp b/src/render/Renderbuffer.hpp index e6bfa909..ff06bd5a 100644 --- a/src/render/Renderbuffer.hpp +++ b/src/render/Renderbuffer.hpp @@ -2,7 +2,6 @@ #include "../helpers/signal/Signal.hpp" #include "../helpers/memory/Memory.hpp" -#include "../helpers/WLListener.hpp" #include "Framebuffer.hpp" #include diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index 127ae187..bc95abc6 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -1,9 +1,11 @@ #include "Renderer.hpp" #include "../Compositor.hpp" #include "../helpers/math/Math.hpp" +#include "../helpers/sync/SyncReleaser.hpp" #include #include #include +#include #include "../config/ConfigValue.hpp" #include "../managers/CursorManager.hpp" #include "../managers/PointerManager.hpp" @@ -20,6 +22,9 @@ #include "../helpers/sync/SyncTimeline.hpp" #include "debug/Log.hpp" +#include +using namespace Hyprutils::Utils; + extern "C" { #include } @@ -32,7 +37,7 @@ static int cursorTicker(void* data) { CHyprRenderer::CHyprRenderer() { if (g_pCompositor->m_pAqBackend->hasSession()) { - for (auto& dev : g_pCompositor->m_pAqBackend->session->sessionDevices) { + for (auto const& dev : g_pCompositor->m_pAqBackend->session->sessionDevices) { const auto DRMV = drmGetVersion(dev->fd); if (!DRMV) continue; @@ -92,8 +97,56 @@ CHyprRenderer::CHyprRenderer() { ensureCursorRenderingMode(); }); + static auto P3 = g_pHookSystem->hookDynamic("focusedMon", [&](void* self, SCallbackInfo& info, std::any param) { + g_pEventLoopManager->doLater([this]() { + if (!g_pHyprError->active()) + return; + for (auto& m : g_pCompositor->m_vMonitors) { + arrangeLayersForMonitor(m->ID); + } + }); + }); + m_pCursorTicker = wl_event_loop_add_timer(g_pCompositor->m_sWLEventLoop, cursorTicker, nullptr); wl_event_source_timer_update(m_pCursorTicker, 500); + + m_tRenderUnfocusedTimer = makeShared( + std::nullopt, + [this](SP self, void* data) { + static auto PFPS = CConfigValue("misc:render_unfocused_fps"); + + if (m_vRenderUnfocused.empty()) + return; + + timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + bool dirty = false; + for (auto& w : m_vRenderUnfocused) { + if (!w) { + dirty = true; + continue; + } + + if (!w->m_pWLSurface || !w->m_pWLSurface->resource() || shouldRenderWindow(w.lock())) + continue; + + w->m_pWLSurface->resource()->frame(&now); + auto FEEDBACK = makeShared(w->m_pWLSurface->resource()); + FEEDBACK->attachMonitor(g_pCompositor->m_pLastMonitor.lock()); + FEEDBACK->discarded(); + PROTO::presentation->queueData(FEEDBACK); + } + + if (dirty) + std::erase_if(m_vRenderUnfocused, [](const auto& e) { return !e || !e->m_sWindowData.renderUnfocused.valueOr(false); }); + + if (!m_vRenderUnfocused.empty()) + m_tRenderUnfocusedTimer->updateTimeout(std::chrono::milliseconds(1000 / *PFPS)); + }, + nullptr); + + g_pEventLoopManager->addTimer(m_tRenderUnfocusedTimer); } CHyprRenderer::~CHyprRenderer() { @@ -107,7 +160,7 @@ static void renderSurface(SP surface, int x, int y, void* da const auto& TEXTURE = surface->current.texture; const auto RDATA = (SRenderData*)data; - const auto INTERACTIVERESIZEINPROGRESS = RDATA->pWindow && g_pInputManager->currentlyDraggedWindow.lock() == RDATA->pWindow && g_pInputManager->dragMode == MBIND_RESIZE; + const auto INTERACTIVERESIZEINPROGRESS = RDATA->pWindow && g_pInputManager->currentlyDraggedWindow && g_pInputManager->dragMode == MBIND_RESIZE; // this is bad, probably has been logged elsewhere. Means the texture failed // uploading to the GPU. @@ -115,8 +168,8 @@ static void renderSurface(SP surface, int x, int y, void* da return; // explicit sync: wait for the timeline, if any - if (surface->syncobj && surface->syncobj->acquireTimeline) { - if (!g_pHyprOpenGL->waitForTimelinePoint(surface->syncobj->acquireTimeline->timeline, surface->syncobj->acquirePoint)) { + if (surface->syncobj && surface->syncobj->current.acquireTimeline) { + if (!g_pHyprOpenGL->waitForTimelinePoint(surface->syncobj->current.acquireTimeline->timeline, surface->syncobj->current.acquirePoint)) { Debug::log(ERR, "Renderer: failed to wait for explicit timeline"); return; } @@ -137,6 +190,7 @@ static void renderSurface(SP surface, int x, int y, void* da // however, if surface buffer w / h < box, we need to adjust them const auto PWINDOW = PSURFACE ? PSURFACE->getWindow() : nullptr; + // center the surface if it's smaller than the viewport we assign it if (PSURFACE && !PSURFACE->m_bFillIgnoreSmall && PSURFACE->small() /* guarantees PWINDOW */) { const auto CORRECT = PSURFACE->correctSmallVec(); const auto SIZE = PSURFACE->getViewporterCorrectedSize(); @@ -152,17 +206,6 @@ static void renderSurface(SP surface, int x, int y, void* da } } - if (!INTERACTIVERESIZEINPROGRESS && PSURFACE && PWINDOW && PWINDOW->m_vRealSize.goal().floor() > PWINDOW->m_vReportedSize && PWINDOW->m_vReportedSize > Vector2D{1, 1}) { - Vector2D size = - Vector2D{windowBox.w * (PWINDOW->m_vReportedSize.x / PWINDOW->m_vRealSize.value().x), windowBox.h * (PWINDOW->m_vReportedSize.y / PWINDOW->m_vRealSize.value().y)}; - Vector2D correct = Vector2D{windowBox.w, windowBox.h} - size; - - windowBox.translate(correct / 2.0); - - windowBox.w = size.x; - windowBox.h = size.y; - } - } else { // here we clamp to 2, these might be some tiny specks windowBox = {(int)outputX + RDATA->x + x, (int)outputY + RDATA->y + y, std::max((float)surface->current.size.x, 2.F), std::max((float)surface->current.size.y, 2.F)}; if (RDATA->pWindow && RDATA->pWindow->m_vRealSize.isBeingAnimated() && RDATA->surface && RDATA->surface != surface && RDATA->squishOversized /* subsurface */) { @@ -182,12 +225,14 @@ static void renderSurface(SP surface, int x, int y, void* da if (windowBox.width <= 1 || windowBox.height <= 1) { if (!g_pHyprRenderer->m_bBlockSurfaceFeedback) { Debug::log(TRACE, "presentFeedback for invisible surface"); - surface->presentFeedback(RDATA->when, RDATA->pMonitor); + surface->presentFeedback(RDATA->when, RDATA->pMonitor->self.lock()); } return; // invisible } + const auto PROJSIZEUNSCALED = windowBox.size(); + windowBox.scale(RDATA->pMonitor->scale); windowBox.round(); @@ -196,7 +241,7 @@ static void renderSurface(SP surface, int x, int y, void* da DELTALESSTHAN(windowBox.height, surface->current.bufferSize.y, 3) /* off by one-or-two */ && (!RDATA->pWindow || (!RDATA->pWindow->m_vRealSize.isBeingAnimated() && !INTERACTIVERESIZEINPROGRESS)) /* not window or not animated/resizing */; - g_pHyprRenderer->calculateUVForSurface(RDATA->pWindow, surface, RDATA->surface == surface, windowBox.size(), MISALIGNEDFSV1); + g_pHyprRenderer->calculateUVForSurface(RDATA->pWindow, surface, RDATA->pMonitor->self.lock(), RDATA->surface == surface, windowBox.size(), PROJSIZEUNSCALED, MISALIGNEDFSV1); // check for fractional scale surfaces misaligning the buffer size // in those cases it's better to just force nearest neighbor @@ -237,7 +282,7 @@ static void renderSurface(SP surface, int x, int y, void* da } if (!g_pHyprRenderer->m_bBlockSurfaceFeedback) - surface->presentFeedback(RDATA->when, RDATA->pMonitor); + surface->presentFeedback(RDATA->when, RDATA->pMonitor->self.lock()); g_pHyprOpenGL->blend(true); @@ -263,6 +308,11 @@ bool CHyprRenderer::shouldRenderWindow(PHLWINDOW pWindow, CMonitor* pMonitor) { if (pWindow->m_bPinned) return true; + // if the window is being moved to a workspace that is not invisible, and the alpha is > 0.F, render it. + if (pWindow->m_iMonitorMovedFrom != -1 && pWindow->m_fMovingToWorkspaceAlpha.isBeingAnimated() && pWindow->m_fMovingToWorkspaceAlpha.value() > 0.F && + !g_pCompositor->isWorkspaceVisible(pWindow->m_pWorkspace)) + return true; + const auto PWINDOWWORKSPACE = pWindow->m_pWorkspace; if (PWINDOWWORKSPACE && PWINDOWWORKSPACE->m_iMonitorID == pMonitor->ID) { if (PWINDOWWORKSPACE->m_vRenderOffset.isBeingAnimated() || PWINDOWWORKSPACE->m_fAlpha.isBeingAnimated() || PWINDOWWORKSPACE->m_bForceRendering) @@ -328,7 +378,7 @@ bool CHyprRenderer::shouldRenderWindow(PHLWINDOW pWindow) { if (g_pCompositor->isWorkspaceVisible(pWindow->m_pWorkspace)) return true; - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { if (PWORKSPACE && PWORKSPACE->m_iMonitorID == m->ID && (PWORKSPACE->m_vRenderOffset.isBeingAnimated() || PWORKSPACE->m_fAlpha.isBeingAnimated())) return true; @@ -345,7 +395,7 @@ void CHyprRenderer::renderWorkspaceWindowsFullscreen(CMonitor* pMonitor, PHLWORK EMIT_HOOK_EVENT("render", RENDER_PRE_WINDOWS); // loop over the tiled windows that are fading out - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (!shouldRenderWindow(w, pMonitor)) continue; @@ -362,7 +412,7 @@ void CHyprRenderer::renderWorkspaceWindowsFullscreen(CMonitor* pMonitor, PHLWORK } // and floating ones too - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (!shouldRenderWindow(w, pMonitor)) continue; @@ -382,7 +432,7 @@ void CHyprRenderer::renderWorkspaceWindowsFullscreen(CMonitor* pMonitor, PHLWORK } // TODO: this pass sucks - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { const auto PWORKSPACE = w->m_pWorkspace; if (w->m_pWorkspace != pWorkspace || !w->isFullscreen()) { @@ -415,7 +465,7 @@ void CHyprRenderer::renderWorkspaceWindowsFullscreen(CMonitor* pMonitor, PHLWORK } // then render windows over fullscreen. - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (w->m_pWorkspace != pWorkspaceWindow->m_pWorkspace || (!w->m_bCreatedOverFullscreen && !w->m_bPinned) || (!w->m_bIsMapped && !w->m_bFadingOut) || w->isFullscreen()) continue; @@ -435,7 +485,7 @@ void CHyprRenderer::renderWorkspaceWindows(CMonitor* pMonitor, PHLWORKSPACE pWor EMIT_HOOK_EVENT("render", RENDER_PRE_WINDOWS); // Non-floating main - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (w->isHidden() || (!w->m_bIsMapped && !w->m_bFadingOut)) continue; @@ -462,7 +512,7 @@ void CHyprRenderer::renderWorkspaceWindows(CMonitor* pMonitor, PHLWORKSPACE pWor renderWindow(lastWindow, pMonitor, time, true, RENDER_PASS_MAIN); // Non-floating popup - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (w->isHidden() || (!w->m_bIsMapped && !w->m_bFadingOut)) continue; @@ -480,7 +530,7 @@ void CHyprRenderer::renderWorkspaceWindows(CMonitor* pMonitor, PHLWORKSPACE pWor } // floating on top - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (w->isHidden() || (!w->m_bIsMapped && !w->m_bFadingOut)) continue; @@ -537,14 +587,18 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, CMonitor* pMonitor, timespec if (ignoreAllGeometry) decorate = false; + // whether to use m_fMovingToWorkspaceAlpha, only if fading out into an invisible ws + const bool USE_WORKSPACE_FADE_ALPHA = pWindow->m_iMonitorMovedFrom != -1 && !g_pCompositor->isWorkspaceVisible(pWindow->m_pWorkspace); + renderdata.surface = pWindow->m_pWLSurface->resource(); renderdata.dontRound = pWindow->isEffectiveInternalFSMode(FSMODE_FULLSCREEN) || pWindow->m_sWindowData.noRounding.valueOrDefault(); - renderdata.fadeAlpha = pWindow->m_fAlpha.value() * (pWindow->m_bPinned ? 1.f : PWORKSPACE->m_fAlpha.value()); - renderdata.alpha = pWindow->m_fActiveInactiveAlpha.value(); - renderdata.decorate = decorate && !pWindow->m_bX11DoesntWantBorders && !pWindow->isEffectiveInternalFSMode(FSMODE_FULLSCREEN); - renderdata.rounding = ignoreAllGeometry || renderdata.dontRound ? 0 : pWindow->rounding() * pMonitor->scale; - renderdata.blur = !ignoreAllGeometry; // if it shouldn't, it will be ignored later - renderdata.pWindow = pWindow; + renderdata.fadeAlpha = pWindow->m_fAlpha.value() * (pWindow->m_bPinned || USE_WORKSPACE_FADE_ALPHA ? 1.f : PWORKSPACE->m_fAlpha.value()) * + (USE_WORKSPACE_FADE_ALPHA ? pWindow->m_fMovingToWorkspaceAlpha.value() : 1.F); + renderdata.alpha = pWindow->m_fActiveInactiveAlpha.value(); + renderdata.decorate = decorate && !pWindow->m_bX11DoesntWantBorders && !pWindow->isEffectiveInternalFSMode(FSMODE_FULLSCREEN); + renderdata.rounding = ignoreAllGeometry || renderdata.dontRound ? 0 : pWindow->rounding() * pMonitor->scale; + renderdata.blur = !ignoreAllGeometry; // if it shouldn't, it will be ignored later + renderdata.pWindow = pWindow; if (ignoreAllGeometry) { renderdata.alpha = 1.f; @@ -582,20 +636,20 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, CMonitor* pMonitor, timespec if (TRANSFORMERSPRESENT) { g_pHyprOpenGL->bindOffMain(); - for (auto& t : pWindow->m_vTransformers) { + for (auto const& t : pWindow->m_vTransformers) { t->preWindowRender(&renderdata); } } if (renderdata.decorate) { - for (auto& wd : pWindow->m_dWindowDecorations) { + for (auto const& wd : pWindow->m_dWindowDecorations) { if (wd->getDecorationLayer() != DECORATION_LAYER_BOTTOM) continue; wd->draw(pMonitor, renderdata.alpha * renderdata.fadeAlpha); } - for (auto& wd : pWindow->m_dWindowDecorations) { + for (auto const& wd : pWindow->m_dWindowDecorations) { if (wd->getDecorationLayer() != DECORATION_LAYER_UNDER) continue; @@ -622,7 +676,7 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, CMonitor* pMonitor, timespec g_pHyprOpenGL->m_RenderData.useNearestNeighbor = false; if (renderdata.decorate) { - for (auto& wd : pWindow->m_dWindowDecorations) { + for (auto const& wd : pWindow->m_dWindowDecorations) { if (wd->getDecorationLayer() != DECORATION_LAYER_OVER) continue; @@ -633,7 +687,7 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, CMonitor* pMonitor, timespec if (TRANSFORMERSPRESENT) { CFramebuffer* last = g_pHyprOpenGL->m_RenderData.currentFB; - for (auto& t : pWindow->m_vTransformers) { + for (auto const& t : pWindow->m_vTransformers) { last = t->transform(last); } @@ -697,7 +751,7 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, CMonitor* pMonitor, timespec } if (decorate) { - for (auto& wd : pWindow->m_dWindowDecorations) { + for (auto const& wd : pWindow->m_dWindowDecorations) { if (wd->getDecorationLayer() != DECORATION_LAYER_OVERLAY) continue; @@ -713,6 +767,9 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, CMonitor* pMonitor, timespec } void CHyprRenderer::renderLayer(PHLLS pLayer, CMonitor* pMonitor, timespec* time, bool popups) { + if (!pLayer) + return; + static auto PDIMAROUND = CConfigValue("decoration:dim_around"); if (*PDIMAROUND && pLayer->dimAround && !m_bRenderingSnapshot && !popups) { @@ -824,9 +881,7 @@ void CHyprRenderer::renderAllClientsForWorkspace(CMonitor* pMonitor, PHLWORKSPAC if (g_pSessionLockManager->isSessionLocked() && !g_pSessionLockManager->isSessionLockPresent()) { // locked with no exclusive, draw only red - CBox boxe = {0, 0, INT16_MAX, INT16_MAX}; - const float A = g_pSessionLockManager->getRedScreenAlphaForMonitor(pMonitor->ID); - g_pHyprOpenGL->renderRect(&boxe, CColor(1.0, 0.2, 0.2, A)); + renderSessionLockMissing(pMonitor); return; } @@ -846,16 +901,16 @@ void CHyprRenderer::renderAllClientsForWorkspace(CMonitor* pMonitor, PHLWORKSPAC } g_pHyprOpenGL->blend(true); - for (auto& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]) { + for (auto const& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]) { renderLayer(ls.lock(), pMonitor, time); } - for (auto& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]) { + for (auto const& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]) { renderLayer(ls.lock(), pMonitor, time); } - for (auto& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) { + for (auto const& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) { renderLayer(ls.lock(), pMonitor, time); } - for (auto& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]) { + for (auto const& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]) { renderLayer(ls.lock(), pMonitor, time); } @@ -886,10 +941,10 @@ void CHyprRenderer::renderAllClientsForWorkspace(CMonitor* pMonitor, PHLWORKSPAC } g_pHyprOpenGL->blend(true); - for (auto& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]) { + for (auto const& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]) { renderLayer(ls.lock(), pMonitor, time); } - for (auto& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]) { + for (auto const& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]) { renderLayer(ls.lock(), pMonitor, time); } @@ -909,7 +964,7 @@ void CHyprRenderer::renderAllClientsForWorkspace(CMonitor* pMonitor, PHLWORKSPAC g_pHyprOpenGL->m_RenderData.damage = preOccludedDamage; // and then special - for (auto& ws : g_pCompositor->m_vWorkspaces) { + for (auto const& ws : g_pCompositor->m_vWorkspaces) { if (ws->m_iMonitorID == pMonitor->ID && ws->m_fAlpha.value() > 0.f && ws->m_bIsSpecialWorkspace) { const auto SPECIALANIMPROGRS = ws->m_vRenderOffset.isBeingAnimated() ? ws->m_vRenderOffset.getCurveValue() : ws->m_fAlpha.getCurveValue(); const bool ANIMOUT = !pMonitor->activeSpecialWorkspace; @@ -929,7 +984,7 @@ void CHyprRenderer::renderAllClientsForWorkspace(CMonitor* pMonitor, PHLWORKSPAC } // special - for (auto& ws : g_pCompositor->m_vWorkspaces) { + for (auto const& ws : g_pCompositor->m_vWorkspaces) { if (ws->m_fAlpha.value() > 0.f && ws->m_bIsSpecialWorkspace) { if (ws->m_bHasFullscreenWindow) renderWorkspaceWindowsFullscreen(pMonitor, ws, time); @@ -939,7 +994,7 @@ void CHyprRenderer::renderAllClientsForWorkspace(CMonitor* pMonitor, PHLWORKSPAC } // pinned always above - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (w->isHidden() && !w->m_bIsMapped && !w->m_bFadingOut) continue; @@ -956,21 +1011,21 @@ void CHyprRenderer::renderAllClientsForWorkspace(CMonitor* pMonitor, PHLWORKSPAC EMIT_HOOK_EVENT("render", RENDER_POST_WINDOWS); // Render surfaces above windows for monitor - for (auto& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) { + for (auto const& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) { renderLayer(ls.lock(), pMonitor, time); } // Render IME popups - for (auto& imep : g_pInputManager->m_sIMERelay.m_vIMEPopups) { + for (auto const& imep : g_pInputManager->m_sIMERelay.m_vIMEPopups) { renderIMEPopup(imep.get(), pMonitor, time); } - for (auto& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]) { + for (auto const& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]) { renderLayer(ls.lock(), pMonitor, time); } - for (auto& lsl : pMonitor->m_aLayerSurfaceLayers) { - for (auto& ls : lsl) { + for (auto const& lsl : pMonitor->m_aLayerSurfaceLayers) { + for (auto const& ls : lsl) { renderLayer(ls.lock(), pMonitor, time, true); } } @@ -986,28 +1041,52 @@ void CHyprRenderer::renderLockscreen(CMonitor* pMonitor, timespec* now, const CB if (g_pSessionLockManager->isSessionLocked()) { Vector2D translate = {geometry.x, geometry.y}; - float scale = (float)geometry.width / pMonitor->vecPixelSize.x; const auto PSLS = g_pSessionLockManager->getSessionLockSurfaceForMonitor(pMonitor->ID); - if (!PSLS) { - // locked with no surface, fill with red - const auto ALPHA = g_pSessionLockManager->getRedScreenAlphaForMonitor(pMonitor->ID); - - CBox monbox = {translate.x, translate.y, pMonitor->vecTransformedSize.x * scale, pMonitor->vecTransformedSize.y * scale}; - g_pHyprOpenGL->renderRect(&monbox, CColor(1.0, 0.2, 0.2, ALPHA)); - - if (ALPHA < 1.f) /* animate */ - damageMonitor(pMonitor); - else - g_pSessionLockManager->onLockscreenRenderedOnMonitor(pMonitor->ID); - } else { + if (!PSLS) + renderSessionLockMissing(pMonitor); + else { renderSessionLockSurface(PSLS, pMonitor, now); g_pSessionLockManager->onLockscreenRenderedOnMonitor(pMonitor->ID); } } } -void CHyprRenderer::calculateUVForSurface(PHLWINDOW pWindow, SP pSurface, bool main, const Vector2D& projSize, bool fixMisalignedFSV1) { +void CHyprRenderer::renderSessionLockMissing(CMonitor* pMonitor) { + const auto ALPHA = g_pSessionLockManager->getRedScreenAlphaForMonitor(pMonitor->ID); + + CBox monbox = {{}, pMonitor->vecPixelSize}; + + const bool ANY_PRESENT = g_pSessionLockManager->anySessionLockSurfacesPresent(); + + if (ANY_PRESENT) { + // render image2, without instructions. Lock still "alive", unless texture dead + if (g_pHyprOpenGL->m_pLockDead2Texture) + g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_pLockDead2Texture, &monbox, ALPHA); + else + g_pHyprOpenGL->renderRect(&monbox, CColor(1.0, 0.2, 0.2, ALPHA)); + } else { + // render image, with instructions. Lock is gone. + if (g_pHyprOpenGL->m_pLockDeadTexture) { + g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_pLockDeadTexture, &monbox, ALPHA); + + // also render text for the tty number + if (g_pHyprOpenGL->m_pLockTtyTextTexture) { + CBox texbox = {{}, g_pHyprOpenGL->m_pLockTtyTextTexture->m_vSize}; + g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_pLockTtyTextTexture, &texbox, 1.F); + } + } else + g_pHyprOpenGL->renderRect(&monbox, CColor(1.0, 0.2, 0.2, ALPHA)); + } + + if (ALPHA < 1.f) /* animate */ + damageMonitor(pMonitor); + else + g_pSessionLockManager->onLockscreenRenderedOnMonitor(pMonitor->ID); +} + +void CHyprRenderer::calculateUVForSurface(PHLWINDOW pWindow, SP pSurface, SP pMonitor, bool main, const Vector2D& projSize, + const Vector2D& projSizeUnscaled, bool fixMisalignedFSV1) { if (!pWindow || !pWindow->m_bIsX11) { Vector2D uvTL; Vector2D uvBR = Vector2D(1, 1); @@ -1036,6 +1115,22 @@ void CHyprRenderer::calculateUVForSurface(PHLWINDOW pWindow, SPscale); + const bool SCALE_UNAWARE = MONITOR_WL_SCALE != pSurface->current.scale && !pSurface->current.viewport.hasDestination; + const auto EXPECTED_SIZE = + ((pSurface->current.viewport.hasDestination ? pSurface->current.viewport.destination : pSurface->current.bufferSize / pSurface->current.scale) * pMonitor->scale) + .round(); + if (!SCALE_UNAWARE && (EXPECTED_SIZE.x < projSize.x || EXPECTED_SIZE.y < projSize.y)) { + // this will not work with shm AFAIK, idk why. + // NOTE: this math is wrong if we have a source... or geom updates later, but I don't think we can do much + const auto FIX = projSize / EXPECTED_SIZE; + uvBR = uvBR * FIX; + } + g_pHyprOpenGL->m_RenderData.primarySurfaceUVTopLeft = uvTL; g_pHyprOpenGL->m_RenderData.primarySurfaceUVBottomRight = uvBR; @@ -1051,18 +1146,17 @@ void CHyprRenderer::calculateUVForSurface(PHLWINDOW pWindow, SPm_pXDGSurface->current.geometry; // ignore X and Y, adjust uv - if (geom.x != 0 || geom.y != 0 || geom.width > pWindow->m_vRealSize.value().x || geom.height > pWindow->m_vRealSize.value().y) { + if (geom.x != 0 || geom.y != 0 || geom.width > projSizeUnscaled.x || geom.height > projSizeUnscaled.y) { const auto XPERC = (double)geom.x / (double)pSurface->current.size.x; const auto YPERC = (double)geom.y / (double)pSurface->current.size.y; const auto WPERC = (double)(geom.x + geom.width) / (double)pSurface->current.size.x; const auto HPERC = (double)(geom.y + geom.height) / (double)pSurface->current.size.y; const auto TOADDTL = Vector2D(XPERC * (uvBR.x - uvTL.x), YPERC * (uvBR.y - uvTL.y)); - uvBR = uvBR - Vector2D(1.0 - WPERC * (uvBR.x - uvTL.x), 1.0 - HPERC * (uvBR.y - uvTL.y)); + uvBR = uvBR - Vector2D((1.0 - WPERC) * (uvBR.x - uvTL.x), (1.0 - HPERC) * (uvBR.y - uvTL.y)); uvTL = uvTL + TOADDTL; - // TODO: make this passed to the func. Might break in the future. - auto maxSize = pWindow->m_vRealSize.value(); + auto maxSize = projSizeUnscaled; if (pWindow->m_pWLSurface->small() && !pWindow->m_pWLSurface->m_bFillIgnoreSmall) maxSize = pWindow->m_pWLSurface->getViewporterCorrectedSize(); @@ -1095,7 +1189,7 @@ void CHyprRenderer::renderMonitor(CMonitor* pMonitor) { static auto PDEBUGOVERLAY = CConfigValue("debug:overlay"); static auto PDAMAGETRACKINGMODE = CConfigValue("debug:damage_tracking"); static auto PDAMAGEBLINK = CConfigValue("debug:damage_blink"); - static auto PNODIRECTSCANOUT = CConfigValue("misc:no_direct_scanout"); + static auto PDIRECTSCANOUT = CConfigValue("render:direct_scanout"); static auto PVFR = CConfigValue("misc:vfr"); static auto PZOOMFACTOR = CConfigValue("cursor:zoom_factor"); static auto PANIMENABLED = CConfigValue("animations:enabled"); @@ -1195,12 +1289,16 @@ void CHyprRenderer::renderMonitor(CMonitor* pMonitor) { pMonitor->tearingState.activelyTearing = shouldTear; - if (!*PNODIRECTSCANOUT && !shouldTear) { + if (*PDIRECTSCANOUT && !shouldTear) { if (pMonitor->attemptDirectScanout()) { return; } else if (!pMonitor->lastScanout.expired()) { Debug::log(LOG, "Left a direct scanout."); pMonitor->lastScanout.reset(); + + // reset DRM format, make sure it's the one we want. + pMonitor->output->state->setFormat(pMonitor->prevDrmFormat); + pMonitor->drmFormat = pMonitor->prevDrmFormat; } } @@ -1389,73 +1487,81 @@ void CHyprRenderer::renderMonitor(CMonitor* pMonitor) { pMonitor->pendingFrame = false; - const float µs = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - renderStart).count() / 1000.f; - g_pDebugOverlay->renderData(pMonitor, µs); + const float durationUs = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - renderStart).count() / 1000.f; + g_pDebugOverlay->renderData(pMonitor, durationUs); if (*PDEBUGOVERLAY == 1) { if (pMonitor == g_pCompositor->m_vMonitors.front().get()) { - const float µsNoOverlay = µs - std::chrono::duration_cast(endRenderOverlay - renderStartOverlay).count() / 1000.f; - g_pDebugOverlay->renderDataNoOverlay(pMonitor, µsNoOverlay); + const float noOverlayUs = durationUs - std::chrono::duration_cast(endRenderOverlay - renderStartOverlay).count() / 1000.f; + g_pDebugOverlay->renderDataNoOverlay(pMonitor, noOverlayUs); } else { - g_pDebugOverlay->renderDataNoOverlay(pMonitor, µs); + g_pDebugOverlay->renderDataNoOverlay(pMonitor, durationUs); } } } bool CHyprRenderer::commitPendingAndDoExplicitSync(CMonitor* pMonitor) { - static auto PENABLEEXPLICIT = CConfigValue("experimental:explicit_sync"); - // apply timelines for explicit sync + // save inFD otherwise reset will reset it + auto inFD = pMonitor->output->state->state().explicitInFence; pMonitor->output->state->resetExplicitFences(); + if (inFD >= 0) + pMonitor->output->state->setExplicitInFence(inFD); - bool anyExplicit = !explicitPresented.empty(); - if (anyExplicit) { - Debug::log(TRACE, "Explicit sync presented begin"); - auto inFence = pMonitor->inTimeline->exportAsSyncFileFD(pMonitor->lastWaitPoint); - if (inFence < 0) - Debug::log(ERR, "Export lastWaitPoint {} as sync explicitInFence failed", pMonitor->lastWaitPoint); - - pMonitor->output->state->setExplicitInFence(inFence); - - for (auto& e : explicitPresented) { - Debug::log(TRACE, "Explicit sync presented releasePoint {}", e->syncobj && e->syncobj->releaseTimeline ? e->syncobj->releasePoint : -1); - if (!e->syncobj || !e->syncobj->releaseTimeline) - continue; - e->syncobj->releaseTimeline->timeline->transfer(pMonitor->outTimeline, pMonitor->commitSeq, e->syncobj->releasePoint); - } - - explicitPresented.clear(); - auto outFence = pMonitor->outTimeline->exportAsSyncFileFD(pMonitor->commitSeq); - if (outFence < 0) - Debug::log(ERR, "Export commitSeq {} as sync explicitOutFence failed", pMonitor->commitSeq); - - pMonitor->output->state->setExplicitOutFence(outFence); - Debug::log(TRACE, "Explicit sync presented end"); + if (pMonitor->ctmUpdated) { + pMonitor->ctmUpdated = false; + pMonitor->output->state->setCTM(pMonitor->ctm); } - pMonitor->lastWaitPoint = 0; - bool ok = pMonitor->state.commit(); if (!ok) { - Debug::log(TRACE, "Monitor state commit failed"); - // rollback the buffer to avoid writing to the front buffer that is being - // displayed - pMonitor->output->swapchain->rollback(); - pMonitor->damage.damageEntire(); + if (inFD >= 0) { + Debug::log(TRACE, "Monitor state commit failed, retrying without a fence"); + pMonitor->output->state->resetExplicitFences(); + ok = pMonitor->state.commit(); + } + + if (!ok) { + Debug::log(TRACE, "Monitor state commit failed"); + // rollback the buffer to avoid writing to the front buffer that is being + // displayed + pMonitor->output->swapchain->rollback(); + pMonitor->damage.damageEntire(); + } } - if (!*PENABLEEXPLICIT) + auto explicitOptions = getExplicitSyncSettings(); + + if (!explicitOptions.explicitEnabled) return ok; - if (pMonitor->output->state->state().explicitInFence >= 0) - close(pMonitor->output->state->state().explicitInFence); + if (inFD >= 0) + close(inFD); if (pMonitor->output->state->state().explicitOutFence >= 0) { - if (ok) - pMonitor->outTimeline->importFromSyncFileFD(pMonitor->commitSeq, pMonitor->output->state->state().explicitOutFence); + Debug::log(TRACE, "Aquamarine returned an explicit out fence at {}", pMonitor->output->state->state().explicitOutFence); close(pMonitor->output->state->state().explicitOutFence); + } else + Debug::log(TRACE, "Aquamarine did not return an explicit out fence"); + + Debug::log(TRACE, "Explicit: {} presented", explicitPresented.size()); + auto sync = g_pHyprOpenGL->createEGLSync(-1); + + if (!sync) + Debug::log(TRACE, "Explicit: can't add sync, EGLSync failed"); + else { + for (auto const& e : explicitPresented) { + if (!e->current.buffer || !e->current.buffer->releaser) + continue; + + e->current.buffer->releaser->addReleaseSync(sync); + } } + explicitPresented.clear(); + + pMonitor->output->state->resetExplicitFences(); + return ok; } @@ -1477,7 +1583,7 @@ void CHyprRenderer::renderWorkspace(CMonitor* pMonitor, PHLWORKSPACE pWorkspace, } void CHyprRenderer::sendFrameEventsToWorkspace(CMonitor* pMonitor, PHLWORKSPACE pWorkspace, timespec* now) { - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (w->isHidden() || !w->m_bIsMapped || w->m_bFadingOut || !w->m_pWLSurface->resource()) continue; @@ -1487,8 +1593,8 @@ void CHyprRenderer::sendFrameEventsToWorkspace(CMonitor* pMonitor, PHLWORKSPACE w->m_pWLSurface->resource()->breadthfirst([now](SP r, const Vector2D& offset, void* d) { r->frame(now); }, nullptr); } - for (auto& lsl : pMonitor->m_aLayerSurfaceLayers) { - for (auto& ls : lsl) { + for (auto const& lsl : pMonitor->m_aLayerSurfaceLayers) { + for (auto const& ls : lsl) { if (ls->fadingOut || !ls->surface->resource()) continue; @@ -1567,7 +1673,7 @@ static void applyExclusive(CBox& usableArea, uint32_t anchor, int32_t exclusive, void CHyprRenderer::arrangeLayerArray(CMonitor* pMonitor, const std::vector& layerSurfaces, bool exclusiveZone, CBox* usableArea) { CBox full_area = {pMonitor->vecPosition.x, pMonitor->vecPosition.y, pMonitor->vecSize.x, pMonitor->vecSize.y}; - for (auto& ls : layerSurfaces) { + for (auto const& ls : layerSurfaces) { if (!ls || ls->fadingOut || ls->readyToDelete || !ls->layerSurface || ls->noProcess) continue; @@ -1649,8 +1755,10 @@ void CHyprRenderer::arrangeLayerArray(CMonitor* pMonitor, const std::vectorgetMonitorFromID(monitor); +void CHyprRenderer::arrangeLayersForMonitor(const MONITORID& monitor) { + const auto PMONITOR = g_pCompositor->getMonitorFromID(monitor); + + static auto BAR_POSITION = CConfigValue("debug:error_position"); if (!PMONITOR) return; @@ -1661,10 +1769,26 @@ void CHyprRenderer::arrangeLayersForMonitor(const int& monitor) { CBox usableArea = {PMONITOR->vecPosition.x, PMONITOR->vecPosition.y, PMONITOR->vecSize.x, PMONITOR->vecSize.y}; - for (auto& la : PMONITOR->m_aLayerSurfaceLayers) + if (g_pHyprError->active() && g_pCompositor->m_pLastMonitor == PMONITOR->self) { + const auto HEIGHT = g_pHyprError->height(); + if (*BAR_POSITION == 0) { + PMONITOR->vecReservedTopLeft.y = HEIGHT; + usableArea.y += HEIGHT; + usableArea.h -= HEIGHT; + } else { + PMONITOR->vecReservedBottomRight.y = HEIGHT; + usableArea.h -= HEIGHT; + } + } + + for (auto& la : PMONITOR->m_aLayerSurfaceLayers) { + std::stable_sort(la.begin(), la.end(), [](const PHLLSREF& a, const PHLLSREF& b) { return a->order > b->order; }); + } + + for (auto const& la : PMONITOR->m_aLayerSurfaceLayers) arrangeLayerArray(PMONITOR, la, true, &usableArea); - for (auto& la : PMONITOR->m_aLayerSurfaceLayers) + for (auto const& la : PMONITOR->m_aLayerSurfaceLayers) arrangeLayerArray(PMONITOR, la, false, &usableArea); PMONITOR->vecReservedTopLeft = Vector2D(usableArea.x, usableArea.y) - PMONITOR->vecPosition; @@ -1713,7 +1837,7 @@ void CHyprRenderer::damageSurface(SP pSurface, double x, dou CRegion damageBoxForEach; - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { if (!m->output) continue; @@ -1740,7 +1864,7 @@ void CHyprRenderer::damageWindow(PHLWINDOW pWindow, bool forceFull) { windowBox.translate(PWINDOWWORKSPACE->m_vRenderOffset.value()); windowBox.translate(pWindow->m_vFloatingOffset); - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { if (forceFull || g_pHyprRenderer->shouldRenderWindow(pWindow, m.get())) { // only damage if window is rendered on monitor CBox fixedDamageBox = {windowBox.x - m->vecPosition.x, windowBox.y - m->vecPosition.y, windowBox.width, windowBox.height}; fixedDamageBox.scale(m->scale); @@ -1748,7 +1872,7 @@ void CHyprRenderer::damageWindow(PHLWINDOW pWindow, bool forceFull) { } } - for (auto& wd : pWindow->m_dWindowDecorations) + for (auto const& wd : pWindow->m_dWindowDecorations) wd->damageEntire(); static auto PLOGDAMAGE = CConfigValue("debug:log_damage"); @@ -1774,7 +1898,7 @@ void CHyprRenderer::damageBox(CBox* pBox, bool skipFrameSchedule) { if (g_pCompositor->m_bUnsafeState) return; - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { if (m->isMirror()) continue; // don't damage mirrors traditionally @@ -1796,13 +1920,13 @@ void CHyprRenderer::damageBox(const int& x, const int& y, const int& w, const in } void CHyprRenderer::damageRegion(const CRegion& rg) { - for (auto& RECT : rg.getRects()) { + for (auto const& RECT : rg.getRects()) { damageBox(RECT.x1, RECT.y1, RECT.x2 - RECT.x1, RECT.y2 - RECT.y1); } } void CHyprRenderer::damageMirrorsWith(CMonitor* pMonitor, const CRegion& pRegion) { - for (auto& mirror : pMonitor->mirrors) { + for (auto const& mirror : pMonitor->mirrors) { // transform the damage here, so it won't get clipped by the monitor damage ring auto monitor = mirror; @@ -1898,6 +2022,9 @@ bool CHyprRenderer::applyMonitorRule(CMonitor* pMonitor, SMonitorRule* pMonitorR pMonitor->currentMode = nullptr; pMonitor->output->state->setFormat(DRM_FORMAT_XRGB8888); + pMonitor->prevDrmFormat = pMonitor->drmFormat; + pMonitor->drmFormat = DRM_FORMAT_XRGB8888; + pMonitor->output->state->resetExplicitFences(); bool autoScale = false; @@ -1919,7 +2046,7 @@ bool CHyprRenderer::applyMonitorRule(CMonitor* pMonitor, SMonitorRule* pMonitorR if (!pMonitor->output->modes.empty() && RULE->drmMode.type != DRM_MODE_TYPE_USERDEF) { bool found = false; - for (auto& mode : pMonitor->output->modes) { + for (auto const& mode : pMonitor->output->modes) { // if delta of refresh rate, w and h chosen and mode is < 1 we accept it if (DELTALESSTHAN(mode->pixelSize.x, RULE->resolution.x, 1) && DELTALESSTHAN(mode->pixelSize.y, RULE->resolution.y, 1) && DELTALESSTHAN(mode->refreshRate / 1000.f, RULE->refreshRate, 1)) { @@ -1981,17 +2108,9 @@ bool CHyprRenderer::applyMonitorRule(CMonitor* pMonitor, SMonitorRule* pMonitorR if (pMonitor->output->getBackend()->type() != Aquamarine::eBackendType::AQ_BACKEND_DRM) { Debug::log(ERR, "Tried to set custom modeline on non-DRM output"); fail = true; - } else { - // FIXME: - // auto* mode = wlr_drm_connector_add_mode(pMonitor->output, &RULE->drmMode); - // if (mode) { - // wlr_output_state_set_mode(pMonitor->state.wlr(), mode); - // pMonitor->customDrmMode = RULE->drmMode; - // } else { - // Debug::log(ERR, "wlr_drm_connector_add_mode failed"); - // fail = true; - // } - } + } else + pMonitor->output->state->setCustomMode(makeShared( + Aquamarine::SOutputMode{.pixelSize = {RULE->drmMode.hdisplay, RULE->drmMode.vdisplay}, .refreshRate = RULE->drmMode.vrefresh, .modeInfo = RULE->drmMode})); } else pMonitor->output->state->setCustomMode(makeShared(Aquamarine::SOutputMode{.pixelSize = RULE->resolution, .refreshRate = WLRREFRESHRATE})); @@ -2030,7 +2149,7 @@ bool CHyprRenderer::applyMonitorRule(CMonitor* pMonitor, SMonitorRule* pMonitorR //(-1,-1) indicates a preference to refreshrate over resolution, (-1,-2) preference to resolution if (RULE->resolution == Vector2D(-1, -1)) { - for (auto& mode : pMonitor->output->modes) { + for (auto const& mode : pMonitor->output->modes) { if ((mode->pixelSize.x >= currentWidth && mode->pixelSize.y >= currentHeight && mode->refreshRate >= (currentRefresh - 1000.f)) || mode->refreshRate > (currentRefresh + 3000.f)) { pMonitor->output->state->setMode(mode); @@ -2043,7 +2162,7 @@ bool CHyprRenderer::applyMonitorRule(CMonitor* pMonitor, SMonitorRule* pMonitorR } } } else { - for (auto& mode : pMonitor->output->modes) { + for (auto const& mode : pMonitor->output->modes) { if ((mode->pixelSize.x >= currentWidth && mode->pixelSize.y >= currentHeight && mode->refreshRate >= (currentRefresh - 1000.f)) || (mode->pixelSize.x > currentWidth && mode->pixelSize.y > currentHeight)) { pMonitor->output->state->setMode(mode); @@ -2094,7 +2213,7 @@ bool CHyprRenderer::applyMonitorRule(CMonitor* pMonitor, SMonitorRule* pMonitorR Debug::log(ERR, "Monitor {} has NO PREFERRED MODE", pMonitor->output->name); if (!pMonitor->output->modes.empty()) { - for (auto& mode : pMonitor->output->modes) { + for (auto const& mode : pMonitor->output->modes) { pMonitor->output->state->setMode(mode); if (!pMonitor->state.test()) { @@ -2143,8 +2262,10 @@ bool CHyprRenderer::applyMonitorRule(CMonitor* pMonitor, SMonitorRule* pMonitorR bool set10bit = false; - for (auto& fmt : formats[(int)!RULE->enable10bit]) { + for (auto const& fmt : formats[(int)!RULE->enable10bit]) { pMonitor->output->state->setFormat(fmt.second); + pMonitor->prevDrmFormat = pMonitor->drmFormat; + pMonitor->drmFormat = fmt.second; if (!pMonitor->state.test()) { Debug::log(ERR, "output {} failed basic test on format {}", pMonitor->szName, fmt.first); @@ -2239,7 +2360,7 @@ bool CHyprRenderer::applyMonitorRule(CMonitor* pMonitor, SMonitorRule* pMonitorR // Set scale for all surfaces on this monitor, needed for some clients // but not on unsafe state to avoid crashes if (!g_pCompositor->m_bUnsafeState) { - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { w->updateSurfaceScaleTransformDetails(); } } @@ -2289,7 +2410,7 @@ void CHyprRenderer::setCursorFromName(const std::string& name, bool force) { } void CHyprRenderer::ensureCursorRenderingMode() { - static auto PCURSORTIMEOUT = CConfigValue("cursor:inactive_timeout"); + static auto PCURSORTIMEOUT = CConfigValue("cursor:inactive_timeout"); static auto PHIDEONTOUCH = CConfigValue("cursor:hide_on_touch"); static auto PHIDEONKEY = CConfigValue("cursor:hide_on_key_press"); @@ -2311,7 +2432,7 @@ void CHyprRenderer::ensureCursorRenderingMode() { if (HIDE) { Debug::log(LOG, "Hiding the cursor (hl-mandated)"); - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { if (!g_pPointerManager->softwareLockedFor(m)) continue; @@ -2323,7 +2444,7 @@ void CHyprRenderer::ensureCursorRenderingMode() { } else { Debug::log(LOG, "Showing the cursor (hl-mandated)"); - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { if (!g_pPointerManager->softwareLockedFor(m)) continue; @@ -2364,7 +2485,7 @@ std::tuple CHyprRenderer::getRenderTimes(CMonitor* pMonitor float avgRenderTime = 0; float maxRenderTime = 0; float minRenderTime = 9999; - for (auto& rt : POVERLAY->m_dLastRenderTimes) { + for (auto const& rt : POVERLAY->m_dLastRenderTimes) { if (rt > maxRenderTime) maxRenderTime = rt; if (rt < minRenderTime) @@ -2415,7 +2536,7 @@ void CHyprRenderer::setOccludedForMainWorkspace(CRegion& region, PHLWORKSPACE pW if (!PMONITOR->activeSpecialWorkspace) return; - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (!w->m_bIsMapped || w->isHidden() || w->m_pWorkspace != PMONITOR->activeSpecialWorkspace) continue; @@ -2446,7 +2567,7 @@ void CHyprRenderer::setOccludedForBackLayers(CRegion& region, PHLWORKSPACE pWork static auto PBLURPASSES = CConfigValue("decoration:blur:passes"); const auto BLURRADIUS = *PBLUR ? (*PBLURPASSES > 10 ? pow(2, 15) : std::clamp(*PBLURSIZE, (int64_t)1, (int64_t)40) * pow(2, *PBLURPASSES)) : 0; - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (!w->m_bIsMapped || w->isHidden() || w->m_pWorkspace != pWorkspace) continue; @@ -2470,7 +2591,7 @@ void CHyprRenderer::setOccludedForBackLayers(CRegion& region, PHLWORKSPACE pWork } bool CHyprRenderer::canSkipBackBufferClear(CMonitor* pMonitor) { - for (auto& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]) { + for (auto const& ls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]) { if (!ls->layerSurface) continue; @@ -2522,12 +2643,12 @@ void CHyprRenderer::recheckSolitaryForMonitor(CMonitor* pMonitor) { if (!pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY].empty()) return; - for (auto& topls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) { + for (auto const& topls : pMonitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) { if (topls->alpha.value() != 0.f) return; } - for (auto& w : g_pCompositor->m_vWindows) { + for (auto const& w : g_pCompositor->m_vWindows) { if (w == PCANDIDATE || (!w->m_bIsMapped && !w->m_bFadingOut) || w->isHidden()) continue; @@ -2643,10 +2764,16 @@ bool CHyprRenderer::beginRender(CMonitor* pMonitor, CRegion& damage, eRenderMode void CHyprRenderer::endRender() { const auto PMONITOR = g_pHyprOpenGL->m_RenderData.pMonitor; static auto PNVIDIAANTIFLICKER = CConfigValue("opengl:nvidia_anti_flicker"); - static auto PENABLEEXPLICIT = CConfigValue("experimental:explicit_sync"); PMONITOR->commitSeq++; + auto cleanup = CScopeGuard([this]() { + if (m_pCurrentRenderbuffer) + m_pCurrentRenderbuffer->unbind(); + m_pCurrentRenderbuffer = nullptr; + m_pCurrentBuffer = nullptr; + }); + if (m_eRenderMode != RENDER_MODE_TO_BUFFER_READ_ONLY) g_pHyprOpenGL->end(); else { @@ -2661,35 +2788,28 @@ void CHyprRenderer::endRender() { if (m_eRenderMode == RENDER_MODE_NORMAL) { PMONITOR->output->state->setBuffer(m_pCurrentBuffer); - if (PMONITOR->inTimeline && *PENABLEEXPLICIT) { + auto explicitOptions = getExplicitSyncSettings(); + + if (PMONITOR->inTimeline && explicitOptions.explicitEnabled && explicitOptions.explicitKMSEnabled) { auto sync = g_pHyprOpenGL->createEGLSync(-1); if (!sync) { - m_pCurrentRenderbuffer->unbind(); - m_pCurrentRenderbuffer = nullptr; - m_pCurrentBuffer = nullptr; Debug::log(ERR, "renderer: couldn't create an EGLSync for out in endRender"); return; } - auto dupedfd = sync->dupFenceFD(); - sync.reset(); - if (dupedfd < 0) { - m_pCurrentRenderbuffer->unbind(); - m_pCurrentRenderbuffer = nullptr; - m_pCurrentBuffer = nullptr; - Debug::log(ERR, "renderer: couldn't dup an EGLSync fence for out in endRender"); - return; - } - - bool ok = PMONITOR->inTimeline->importFromSyncFileFD(PMONITOR->commitSeq, dupedfd); - close(dupedfd); + bool ok = PMONITOR->inTimeline->importFromSyncFileFD(PMONITOR->commitSeq, sync->fd()); if (!ok) { - m_pCurrentRenderbuffer->unbind(); - m_pCurrentRenderbuffer = nullptr; - m_pCurrentBuffer = nullptr; Debug::log(ERR, "renderer: couldn't import from sync file fd in endRender"); return; } + + auto fd = PMONITOR->inTimeline->exportAsSyncFileFD(PMONITOR->commitSeq); + if (fd <= 0) { + Debug::log(ERR, "renderer: couldn't export from sync timeline in endRender"); + return; + } + + PMONITOR->output->state->setExplicitInFence(fd); } else { if (isNvidia() && *PNVIDIAANTIFLICKER) glFinish(); @@ -2697,11 +2817,6 @@ void CHyprRenderer::endRender() { glFlush(); } } - - m_pCurrentRenderbuffer->unbind(); - - m_pCurrentRenderbuffer = nullptr; - m_pCurrentBuffer = nullptr; } void CHyprRenderer::onRenderbufferDestroy(CRenderbuffer* rb) { @@ -2715,3 +2830,69 @@ SP CHyprRenderer::getCurrentRBO() { bool CHyprRenderer::isNvidia() { return m_bNvidia; } + +SExplicitSyncSettings CHyprRenderer::getExplicitSyncSettings() { + static auto PENABLEEXPLICIT = CConfigValue("render:explicit_sync"); + static auto PENABLEEXPLICITKMS = CConfigValue("render:explicit_sync_kms"); + + SExplicitSyncSettings settings; + settings.explicitEnabled = *PENABLEEXPLICIT; + settings.explicitKMSEnabled = *PENABLEEXPLICITKMS; + + if (*PENABLEEXPLICIT == 2 /* auto */) + settings.explicitEnabled = true; + if (*PENABLEEXPLICITKMS == 2 /* auto */) { + if (!m_bNvidia) + settings.explicitKMSEnabled = true; + else { + + // check nvidia version. Explicit KMS is supported in >=560 + // in the case of an error, driverMajor will stay 0 and explicit KMS will be disabled + static int driverMajor = 0; + + static bool once = true; + if (once) { + once = false; + + Debug::log(LOG, "Renderer: checking for explicit KMS support for nvidia"); + + if (std::filesystem::exists("/sys/module/nvidia_drm/version")) { + Debug::log(LOG, "Renderer: Nvidia version file exists"); + + std::ifstream ifs("/sys/module/nvidia_drm/version"); + if (ifs.good()) { + try { + std::string driverInfo((std::istreambuf_iterator(ifs)), (std::istreambuf_iterator())); + + Debug::log(LOG, "Renderer: Read nvidia version {}", driverInfo); + + CVarList ver(driverInfo, 0, '.', true); + driverMajor = std::stoi(ver[0]); + + Debug::log(LOG, "Renderer: Parsed nvidia major version: {}", driverMajor); + + } catch (std::exception& e) { settings.explicitKMSEnabled = false; } + + ifs.close(); + } + } + } + + settings.explicitKMSEnabled = driverMajor >= 560; + } + } + + return settings; +} + +void CHyprRenderer::addWindowToRenderUnfocused(PHLWINDOW window) { + static auto PFPS = CConfigValue("misc:render_unfocused_fps"); + + if (std::find(m_vRenderUnfocused.begin(), m_vRenderUnfocused.end(), window) != m_vRenderUnfocused.end()) + return; + + m_vRenderUnfocused.emplace_back(window); + + if (!m_tRenderUnfocusedTimer->armed()) + m_tRenderUnfocusedTimer->updateTimeout(std::chrono::milliseconds(1000 / *PFPS)); +} diff --git a/src/render/Renderer.hpp b/src/render/Renderer.hpp index a9397cac..7d1ae4b1 100644 --- a/src/render/Renderer.hpp +++ b/src/render/Renderer.hpp @@ -13,6 +13,7 @@ class CWorkspace; class CWindow; class CInputPopup; class IHLBuffer; +class CEventLoopTimer; // TODO: add fuller damage tracking for updating only parts of a window enum DAMAGETRACKINGMODES { @@ -39,27 +40,32 @@ class CToplevelExportProtocolManager; class CInputManager; struct SSessionLockSurface; +struct SExplicitSyncSettings { + bool explicitEnabled = false, explicitKMSEnabled = false; +}; + class CHyprRenderer { public: CHyprRenderer(); ~CHyprRenderer(); - void renderMonitor(CMonitor* pMonitor); - void arrangeLayersForMonitor(const int&); - void damageSurface(SP, double, double, double scale = 1.0); - void damageWindow(PHLWINDOW, bool forceFull = false); - void damageBox(CBox*, bool skipFrameSchedule = false); - void damageBox(const int& x, const int& y, const int& w, const int& h); - void damageRegion(const CRegion&); - void damageMonitor(CMonitor*); - void damageMirrorsWith(CMonitor*, const CRegion&); - bool applyMonitorRule(CMonitor*, SMonitorRule*, bool force = false); - bool shouldRenderWindow(PHLWINDOW, CMonitor*); - bool shouldRenderWindow(PHLWINDOW); - void ensureCursorRenderingMode(); - bool shouldRenderCursor(); - void setCursorHidden(bool hide); - void calculateUVForSurface(PHLWINDOW, SP, bool main = false, const Vector2D& projSize = {}, bool fixMisalignedFSV1 = false); + void renderMonitor(CMonitor* pMonitor); + void arrangeLayersForMonitor(const MONITORID&); + void damageSurface(SP, double, double, double scale = 1.0); + void damageWindow(PHLWINDOW, bool forceFull = false); + void damageBox(CBox*, bool skipFrameSchedule = false); + void damageBox(const int& x, const int& y, const int& w, const int& h); + void damageRegion(const CRegion&); + void damageMonitor(CMonitor*); + void damageMirrorsWith(CMonitor*, const CRegion&); + bool applyMonitorRule(CMonitor*, SMonitorRule*, bool force = false); + bool shouldRenderWindow(PHLWINDOW, CMonitor*); + bool shouldRenderWindow(PHLWINDOW); + void ensureCursorRenderingMode(); + bool shouldRenderCursor(); + void setCursorHidden(bool hide); + void calculateUVForSurface(PHLWINDOW, SP, SP pMonitor, bool main = false, const Vector2D& projSize = {}, const Vector2D& projSizeUnscaled = {}, + bool fixMisalignedFSV1 = false); std::tuple getRenderTimes(CMonitor* pMonitor); // avg max min void renderLockscreen(CMonitor* pMonitor, timespec* now, const CBox& geometry); void setOccludedForBackLayers(CRegion& region, PHLWORKSPACE pWorkspace); @@ -73,6 +79,8 @@ class CHyprRenderer { bool isNvidia(); void makeEGLCurrent(); void unsetEGL(); + SExplicitSyncSettings getExplicitSyncSettings(); + void addWindowToRenderUnfocused(PHLWINDOW window); // if RENDER_MODE_NORMAL, provided damage will be written to. // otherwise, it will be the one used. @@ -118,6 +126,7 @@ class CHyprRenderer { void renderWorkspace(CMonitor* pMonitor, PHLWORKSPACE pWorkspace, timespec* now, const CBox& geometry); void sendFrameEventsToWorkspace(CMonitor* pMonitor, PHLWORKSPACE pWorkspace, timespec* now); // sends frame displayed events but doesn't actually render anything void renderAllClientsForWorkspace(CMonitor* pMonitor, PHLWORKSPACE pWorkspace, timespec* now, const Vector2D& translate = {0, 0}, const float& scale = 1.f); + void renderSessionLockMissing(CMonitor* pMonitor); bool commitPendingAndDoExplicitSync(CMonitor* pMonitor); @@ -137,11 +146,14 @@ class CHyprRenderer { SP getOrCreateRenderbuffer(SP buffer, uint32_t fmt); std::vector> m_vRenderbuffers; + std::vector m_vRenderUnfocused; + SP m_tRenderUnfocusedTimer; friend class CHyprOpenGLImpl; friend class CToplevelExportFrame; friend class CInputManager; friend class CPointerManager; + friend class CMonitor; }; inline std::unique_ptr g_pHyprRenderer; diff --git a/src/render/Texture.cpp b/src/render/Texture.cpp index 94d00184..91e70afa 100644 --- a/src/render/Texture.cpp +++ b/src/render/Texture.cpp @@ -120,7 +120,7 @@ void CTexture::update(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, cons } #endif - for (auto& rect : rects) { + for (auto const& rect : rects) { GLCALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / format->bytesPerBlock)); GLCALL(glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, rect.x1)); GLCALL(glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, rect.y1)); diff --git a/src/render/decorations/CHyprBorderDecoration.cpp b/src/render/decorations/CHyprBorderDecoration.cpp index f5e6e945..1eaaa0af 100644 --- a/src/render/decorations/CHyprBorderDecoration.cpp +++ b/src/render/decorations/CHyprBorderDecoration.cpp @@ -118,7 +118,7 @@ void CHyprBorderDecoration::damageEntire() { CRegion borderRegion(surfaceBoxExpandedBorder); borderRegion.subtract(surfaceBoxShrunkRounding); - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { if (!g_pHyprRenderer->shouldRenderWindow(m_pWindow.lock(), m.get())) { const CRegion monitorRegion({m->vecPosition, m->vecSize}); borderRegion.subtract(monitorRegion); diff --git a/src/render/decorations/CHyprDropShadowDecoration.cpp b/src/render/decorations/CHyprDropShadowDecoration.cpp index 423256d7..628a579a 100644 --- a/src/render/decorations/CHyprDropShadowDecoration.cpp +++ b/src/render/decorations/CHyprDropShadowDecoration.cpp @@ -66,7 +66,7 @@ void CHyprDropShadowDecoration::damageEntire() { shadowRegion.subtract(CRegion(surfaceBox)); } - for (auto& m : g_pCompositor->m_vMonitors) { + for (auto const& m : g_pCompositor->m_vMonitors) { if (!g_pHyprRenderer->shouldRenderWindow(PWINDOW, m.get())) { const CRegion monitorRegion({m->vecPosition, m->vecSize}); shadowRegion.subtract(monitorRegion); diff --git a/src/render/decorations/CHyprGroupBarDecoration.cpp b/src/render/decorations/CHyprGroupBarDecoration.cpp index 8163a8c1..74a1e2e9 100644 --- a/src/render/decorations/CHyprGroupBarDecoration.cpp +++ b/src/render/decorations/CHyprGroupBarDecoration.cpp @@ -192,7 +192,7 @@ void CHyprGroupBarDecoration::draw(CMonitor* pMonitor, float a) { } CTitleTex* CHyprGroupBarDecoration::textureFromTitle(const std::string& title) { - for (auto& tex : m_sTitleTexs.titleTexs) { + for (auto const& tex : m_sTitleTexs.titleTexs) { if (tex->szContent == title) return tex.get(); } @@ -402,8 +402,15 @@ bool CHyprGroupBarDecoration::onBeginWindowDragOnDeco(const Vector2D& pos) { } bool CHyprGroupBarDecoration::onEndWindowDragOnDeco(const Vector2D& pos, PHLWINDOW pDraggedWindow) { - static auto PSTACKED = CConfigValue("group:groupbar:stacked"); - if (!pDraggedWindow->canBeGroupedInto(m_pWindow.lock())) + static auto PSTACKED = CConfigValue("group:groupbar:stacked"); + static auto PDRAGINTOGROUP = CConfigValue("group:drag_into_group"); + static auto PMERGEFLOATEDINTOTILEDONGROUPBAR = CConfigValue("group:merge_floated_into_tiled_on_groupbar"); + + bool denied = false; + if (!m_pWindow->m_bIsFloating && !pDraggedWindow->m_bDraggingTiled && !*PMERGEFLOATEDINTOTILEDONGROUPBAR) + denied = true; + + if (!pDraggedWindow->canBeGroupedInto(m_pWindow.lock()) || (*PDRAGINTOGROUP != 1 && *PDRAGINTOGROUP != 2) || denied) return false; const float BARRELATIVE = *PSTACKED ? pos.y - assignedBoxGlobal().y - (m_fBarHeight + BAR_PADDING_OUTER_VERT) / 2 : pos.x - assignedBoxGlobal().x - m_fBarWidth / 2; @@ -435,6 +442,9 @@ bool CHyprGroupBarDecoration::onEndWindowDragOnDeco(const Vector2D& pos, PHLWIND // restores the group for (auto it = members.begin(); it != members.end(); ++it) { + (*it)->m_bIsFloating = pWindowInsertAfter->m_bIsFloating; // match the floating state of group members + if (pWindowInsertAfter->m_bIsFloating) + (*it)->m_vRealSize = pWindowInsertAfter->m_vRealSize.goal(); // match the size of group members if (std::next(it) != members.end()) (*it)->m_sGroupData.pNextWindow = *std::next(it); else @@ -442,9 +452,13 @@ bool CHyprGroupBarDecoration::onEndWindowDragOnDeco(const Vector2D& pos, PHLWIND } members[0]->m_sGroupData.head = true; members[0]->m_sGroupData.locked = WASLOCKED; - } else { + } else g_pLayoutManager->getCurrentLayout()->onWindowRemoved(pDraggedWindow); - } + + pDraggedWindow->m_bIsFloating = pWindowInsertAfter->m_bIsFloating; // match the floating state of the window + + if (pWindowInsertAfter->m_bIsFloating) + g_pXWaylandManager->setWindowSize(pDraggedWindow, pWindowInsertAfter->m_vRealSize.goal()); // match the size of the window pWindowInsertAfter->insertWindowToGroup(pDraggedWindow); @@ -452,7 +466,6 @@ bool CHyprGroupBarDecoration::onEndWindowDragOnDeco(const Vector2D& pos, PHLWIND std::swap(pDraggedHead->m_sGroupData.head, pWindowInsertEnd->m_sGroupData.head); m_pWindow->setGroupCurrent(pDraggedWindow); - pDraggedWindow->applyGroupRules(); pDraggedWindow->updateWindowDecos(); g_pLayoutManager->getCurrentLayout()->recalculateWindow(pDraggedWindow); diff --git a/src/render/decorations/DecorationPositioner.cpp b/src/render/decorations/DecorationPositioner.cpp index d66a5760..4666a59e 100644 --- a/src/render/decorations/DecorationPositioner.cpp +++ b/src/render/decorations/DecorationPositioner.cpp @@ -125,7 +125,7 @@ void CDecorationPositioner::onWindowUpdate(PHLWINDOW pWindow) { // std::vector datas; - for (auto& wd : pWindow->m_dWindowDecorations) { + for (auto const& wd : pWindow->m_dWindowDecorations) { datas.push_back(getDataFor(wd.get(), pWindow)); } @@ -296,7 +296,7 @@ SBoxExtents CDecorationPositioner::getWindowDecorationReserved(PHLWINDOW pWindow SBoxExtents CDecorationPositioner::getWindowDecorationExtents(PHLWINDOW pWindow, bool inputOnly) { CBox accum = pWindow->getWindowMainSurfaceBox(); - for (auto& data : m_vWindowPositioningDatas) { + for (auto const& data : m_vWindowPositioningDatas) { if (data->pWindow.lock() != pWindow) continue; @@ -337,7 +337,7 @@ SBoxExtents CDecorationPositioner::getWindowDecorationExtents(PHLWINDOW pWindow, CBox CDecorationPositioner::getBoxWithIncludedDecos(PHLWINDOW pWindow) { CBox accum = pWindow->getWindowMainSurfaceBox(); - for (auto& data : m_vWindowPositioningDatas) { + for (auto const& data : m_vWindowPositioningDatas) { if (data->pWindow.lock() != pWindow) continue; diff --git a/src/signal-safe.cpp b/src/signal-safe.cpp index 05ca9c65..44d23f9b 100644 --- a/src/signal-safe.cpp +++ b/src/signal-safe.cpp @@ -10,7 +10,7 @@ extern char** environ; char const* sig_getenv(char const* name) { - int len = strlen(name); + size_t len = strlen(name); for (char** var = environ; *var != NULL; var++) { if (strncmp(*var, name, len) == 0 && (*var)[len] == '=') { return (*var) + len + 1; diff --git a/src/signal-safe.hpp b/src/signal-safe.hpp index 3a38f043..ef643097 100644 --- a/src/signal-safe.hpp +++ b/src/signal-safe.hpp @@ -139,7 +139,7 @@ class BufFileWriter { abort(); } else { close(pipefd[1]); - int len; + long len; char readbuf[256]; while ((len = read(pipefd[0], readbuf, 256)) > 0) { write(readbuf, len); @@ -155,7 +155,7 @@ class BufFileWriter { void flush() { size_t i = 0; while (i < m_writeBufPos) { - int written = ::write(m_fd, m_writeBuf + i, m_writeBufPos - i); + auto written = ::write(m_fd, m_writeBuf + i, m_writeBufPos - i); if (written <= 0) { return; } diff --git a/src/xwayland/Server.cpp b/src/xwayland/Server.cpp index cec582f6..5ad9ff23 100644 --- a/src/xwayland/Server.cpp +++ b/src/xwayland/Server.cpp @@ -1,104 +1,115 @@ +#include #ifndef NO_XWAYLAND -#include "Server.hpp" -#include "../defines.hpp" -#include "../Compositor.hpp" -#include "../managers/CursorManager.hpp" -#include "XWayland.hpp" - +#include +#include #include #include +#include +#include #include #include #include -#include #include -#include -#include -#include #include #include +#include #include +#include +#include +#include -// TODO: cleanup -static bool set_cloexec(int fd, bool cloexec) { +#include "Server.hpp" +#include "XWayland.hpp" +#include "debug/Log.hpp" +#include "../defines.hpp" +#include "../Compositor.hpp" +#include "../managers/CursorManager.hpp" + +// Constants +constexpr int SOCKET_DIR_PERMISSIONS = 0755; +constexpr int SOCKET_BACKLOG = 1; +constexpr int MAX_SOCKET_RETRIES = 32; +constexpr int LOCK_FILE_MODE = 044; + +static bool setCloseOnExec(int fd, bool cloexec) { int flags = fcntl(fd, F_GETFD); if (flags == -1) { Debug::log(ERR, "fcntl failed"); return false; } - if (cloexec) { + + if (cloexec) flags = flags | FD_CLOEXEC; - } else { + else flags = flags & ~FD_CLOEXEC; - } + if (fcntl(fd, F_SETFD, flags) == -1) { Debug::log(ERR, "fcntl failed"); return false; } + return true; } -static int openSocket(struct sockaddr_un* addr, size_t path_size) { - int fd, rc; - socklen_t size = offsetof(struct sockaddr_un, sun_path) + path_size + 1; +void cleanUpSocket(int fd, const char* path) { + close(fd); + if (path[0]) + unlink(path); +} - fd = socket(AF_UNIX, SOCK_STREAM, 0); +static int createSocket(struct sockaddr_un* addr, size_t path_size) { + socklen_t size = offsetof(struct sockaddr_un, sun_path) + path_size + 1; + int fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) { - Debug::log(ERR, "failed to create socket {}{}", addr->sun_path[0] ? addr->sun_path[0] : '@', addr->sun_path + 1); + Debug::log(ERR, "Failed to create socket {}{}", addr->sun_path[0] ? addr->sun_path[0] : '@', addr->sun_path + 1); return -1; } - if (!set_cloexec(fd, true)) { + + if (!setCloseOnExec(fd, true)) { close(fd); return -1; } - if (addr->sun_path[0]) { + if (addr->sun_path[0]) unlink(addr->sun_path); - } + if (bind(fd, (struct sockaddr*)addr, size) < 0) { - rc = errno; - Debug::log(ERR, "failed to bind socket {}{}", addr->sun_path[0] ? addr->sun_path[0] : '@', addr->sun_path + 1); - goto cleanup; + Debug::log(ERR, "Failed to bind socket {}{}", addr->sun_path[0] ? addr->sun_path[0] : '@', addr->sun_path + 1); + cleanUpSocket(fd, addr->sun_path); + return -1; } - if (listen(fd, 1) < 0) { - rc = errno; - Debug::log(ERR, "failed to listen to socket {}{}", addr->sun_path[0] ? addr->sun_path[0] : '@', addr->sun_path + 1); - goto cleanup; + + if (listen(fd, SOCKET_BACKLOG) < 0) { + Debug::log(ERR, "Failed to listen to socket {}{}", addr->sun_path[0] ? addr->sun_path[0] : '@', addr->sun_path + 1); + cleanUpSocket(fd, addr->sun_path); + return -1; } return fd; - -cleanup: - close(fd); - if (addr->sun_path[0]) { - unlink(addr->sun_path); - } - errno = rc; - return -1; } static bool checkPermissionsForSocketDir(void) { struct stat buf; if (lstat("/tmp/.X11-unix", &buf)) { - Debug::log(ERR, "Failed statting X11 socket dir"); + Debug::log(ERR, "Failed to stat X11 socket dir"); return false; } if (!(buf.st_mode & S_IFDIR)) { - Debug::log(ERR, "X11 socket dir is not a dir"); + Debug::log(ERR, "X11 socket dir is not a directory"); return false; } if (!((buf.st_uid == 0) || (buf.st_uid == getuid()))) { - Debug::log(ERR, "X11 socket dir is not ours"); + Debug::log(ERR, "X11 socket dir is not owned by root or current user"); return false; } if (!(buf.st_mode & S_ISVTX)) { if ((buf.st_mode & (S_IWGRP | S_IWOTH))) { - Debug::log(ERR, "X11 socket dir is sticky by others"); + Debug::log(ERR, "X11 socket dir is writable by others"); return false; } } @@ -106,38 +117,51 @@ static bool checkPermissionsForSocketDir(void) { return true; } -static bool openSockets(std::array& sockets, int display) { - auto ret = mkdir("/tmp/.X11-unix", 755); - - if (ret != 0) { - if (errno == EEXIST) { - if (!checkPermissionsForSocketDir()) - return false; - } else { - Debug::log(ERR, "XWayland: couldn't create socket dir"); +static bool ensureSocketDirExists() { + if (mkdir("/tmp/.X11-unix", SOCKET_DIR_PERMISSIONS) != 0) { + if (errno == EEXIST) + return checkPermissionsForSocketDir(); + else { + Debug::log(ERR, "XWayland: Couldn't create socket dir"); return false; } } - std::string path; + return true; +} + +static std::string getSocketPath(int display, bool isLinux) { + if (isLinux) + return std::format("/tmp/.X11-unix/X{}", display); + + return std::format("/tmp/.X11-unix/X{}_", display); +} + +static bool openSockets(std::array& sockets, int display) { + if (!ensureSocketDirExists()) + return false; + sockaddr_un addr = {.sun_family = AF_UNIX}; + std::string path; #ifdef __linux__ // cursed... addr.sun_path[0] = 0; - path = std::format("/tmp/.X11-unix/X{}", display); + path = getSocketPath(display, true); strncpy(addr.sun_path + 1, path.c_str(), path.length() + 1); #else - path = std::format("/tmp/.X11-unix/X{}_", display); + path = getSocketPath(display, false); strncpy(addr.sun_path, path.c_str(), path.length() + 1); #endif - sockets[0] = openSocket(&addr, path.length()); + + sockets[0] = createSocket(&addr, path.length()); if (sockets[0] < 0) return false; - path = std::format("/tmp/.X11-unix/X{}", display); + path = getSocketPath(display, true); strncpy(addr.sun_path, path.c_str(), path.length() + 1); - sockets[1] = openSocket(&addr, path.length()); + + sockets[1] = createSocket(&addr, path.length()); if (sockets[1] < 0) { close(sockets[0]); sockets[0] = -1; @@ -159,39 +183,37 @@ static int xwaylandReady(int fd, uint32_t mask, void* data) { static bool safeRemove(const std::string& path) { try { return std::filesystem::remove(path); - } catch (std::exception& e) { Debug::log(ERR, "[XWayland] failed to remove {}", path); } - + } catch (const std::exception& e) { Debug::log(ERR, "[XWayland] Failed to remove {}", path); } return false; } bool CXWaylandServer::tryOpenSockets() { - for (size_t i = 0; i <= 32; ++i) { - auto LOCK = std::format("/tmp/.X{}-lock", i); + for (size_t i = 0; i <= MAX_SOCKET_RETRIES; ++i) { + std::string lockPath = std::format("/tmp/.X{}-lock", i); - if (int fd = open(LOCK.c_str(), O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0444); fd >= 0) { + int fd = open(lockPath.c_str(), O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, LOCK_FILE_MODE); + if (fd >= 0) { // we managed to open the lock if (!openSockets(xFDs, i)) { - safeRemove(LOCK); + safeRemove(lockPath); close(fd); continue; } - const auto PIDSTR = std::format("{}", getpid()); - - if (write(fd, PIDSTR.c_str(), PIDSTR.length()) != (long)PIDSTR.length()) { - safeRemove(LOCK); + const std::string pidStr = std::to_string(getpid()); + if (write(fd, pidStr.c_str(), pidStr.length()) != (long)pidStr.length()) { + safeRemove(lockPath); close(fd); continue; } close(fd); - display = i; displayName = std::format(":{}", display); break; } - int fd = open(LOCK.c_str(), O_RDONLY | O_CLOEXEC); + fd = open(lockPath.c_str(), O_RDONLY | O_CLOEXEC); if (fd < 0) continue; @@ -200,21 +222,20 @@ bool CXWaylandServer::tryOpenSockets() { read(fd, pidstr, sizeof(pidstr) - 1); close(fd); - uint64_t pid = 0; + int32_t pid = 0; try { pid = std::stoi(std::string{pidstr, 11}); } catch (...) { continue; } if (kill(pid, 0) != 0 && errno == ESRCH) { - if (!safeRemove(LOCK)) + if (!safeRemove(lockPath)) continue; - i--; } } if (display < 0) { - Debug::log(ERR, "Failed to find a suitable socket for xwayland"); + Debug::log(ERR, "Failed to find a suitable socket for XWayland"); return false; } @@ -231,19 +252,17 @@ CXWaylandServer::~CXWaylandServer() { if (display < 0) return; - if (xFDs[0]) - close(xFDs[0]); - if (xFDs[1]) - close(xFDs[1]); + close(xFDs[0]); + close(xFDs[1]); - auto LOCK = std::format("/tmp/.X{}-lock", display); - safeRemove(LOCK); + std::string lockPath = std::format("/tmp/.X{}-lock", display); + safeRemove(lockPath); std::string path; #ifdef __linux__ - path = std::format("/tmp/.X11-unix/X{}", display); + path = getSocketPath(display, true); #else - path = std::format("/tmp/.X11-unix/X{}_", display); + path = getSocketPath(display, false); #endif safeRemove(path); } @@ -255,7 +274,6 @@ void CXWaylandServer::die() { if (xFDReadEvents[0]) { wl_event_source_remove(xFDReadEvents[0]); wl_event_source_remove(xFDReadEvents[1]); - xFDReadEvents = {nullptr, nullptr}; } @@ -297,7 +315,7 @@ bool CXWaylandServer::create() { } void CXWaylandServer::runXWayland(int notifyFD) { - if (!set_cloexec(xFDs[0], false) || !set_cloexec(xFDs[1], false) || !set_cloexec(waylandFDs[1], false) || !set_cloexec(xwmFDs[1], false)) { + if (!setCloseOnExec(xFDs[0], false) || !setCloseOnExec(xFDs[1], false) || !setCloseOnExec(waylandFDs[1], false) || !setCloseOnExec(xwmFDs[1], false)) { Debug::log(ERR, "Failed to unset cloexec on fds"); _exit(EXIT_FAILURE); } @@ -324,7 +342,7 @@ bool CXWaylandServer::start() { return false; } - if (!set_cloexec(waylandFDs[0], true) || !set_cloexec(waylandFDs[1], true)) { + if (!setCloseOnExec(waylandFDs[0], true) || !setCloseOnExec(waylandFDs[1], true)) { Debug::log(ERR, "set_cloexec failed (1)"); die(); return false; @@ -336,7 +354,7 @@ bool CXWaylandServer::start() { return false; } - if (!set_cloexec(xwmFDs[0], true) || !set_cloexec(xwmFDs[1], true)) { + if (!setCloseOnExec(xwmFDs[0], true) || !setCloseOnExec(xwmFDs[1], true)) { Debug::log(ERR, "set_cloexec failed (2)"); die(); return false; @@ -358,7 +376,7 @@ bool CXWaylandServer::start() { return false; } - if (!set_cloexec(notify[0], true)) { + if (!setCloseOnExec(notify[0], true)) { Debug::log(ERR, "set_cloexec failed (3)"); close(notify[0]); close(notify[1]); @@ -381,9 +399,8 @@ bool CXWaylandServer::start() { if (pid < 0) { Debug::log(ERR, "second fork failed"); _exit(1); - } else if (pid == 0) { + } else if (pid == 0) runXWayland(notify[1]); - } _exit(0); } @@ -431,7 +448,8 @@ int CXWaylandServer::ready(int fd, uint32_t mask) { pipeSource = nullptr; // start the wm - g_pXWayland->pWM = std::make_unique(); + if (!g_pXWayland->pWM) + g_pXWayland->pWM = std::make_unique(); g_pCursorManager->setXWaylandCursor(); diff --git a/src/xwayland/XDataSource.cpp b/src/xwayland/XDataSource.cpp index f4059ee1..c6495435 100644 --- a/src/xwayland/XDataSource.cpp +++ b/src/xwayland/XDataSource.cpp @@ -1,8 +1,8 @@ #ifndef NO_XWAYLAND -#include "XDataSource.hpp" #include "XWayland.hpp" #include "../defines.hpp" +#include "XDataSource.hpp" #include diff --git a/src/xwayland/XSurface.cpp b/src/xwayland/XSurface.cpp index 30ffbc68..e734c153 100644 --- a/src/xwayland/XSurface.cpp +++ b/src/xwayland/XSurface.cpp @@ -5,8 +5,8 @@ #ifndef NO_XWAYLAND -#include "../Compositor.hpp" #include +#include "../Compositor.hpp" CXWaylandSurface::CXWaylandSurface(uint32_t xID_, CBox geometry_, bool OR) : xID(xID_), geometry(geometry_), overrideRedirect(OR) { xcb_res_query_client_ids_cookie_t client_id_cookie = {0}; @@ -151,8 +151,8 @@ bool CXWaylandSurface::wantsFocus() { HYPRATOMS["_NET_WM_WINDOW_TYPE_UTILITY"], }; - for (auto& searched : search) { - for (auto& a : atoms) { + for (auto const& searched : search) { + for (auto const& a : atoms) { if (a == searched) return false; } @@ -196,12 +196,11 @@ void CXWaylandSurface::restackToTop() { xcb_configure_window(g_pXWayland->pWM->connection, xID, XCB_CONFIG_WINDOW_STACK_MODE, values); - for (auto it = g_pXWayland->pWM->mappedSurfacesStacking.begin(); it != g_pXWayland->pWM->mappedSurfacesStacking.end(); ++it) { - if (*it == self) { - std::rotate(it, it + 1, g_pXWayland->pWM->mappedSurfacesStacking.end()); - break; - } - } + auto& stack = g_pXWayland->pWM->mappedSurfacesStacking; + auto it = std::find(stack.begin(), stack.end(), self); + + if (it != stack.end()) + std::rotate(it, it + 1, stack.end()); g_pXWayland->pWM->updateClientList(); diff --git a/src/xwayland/XSurface.hpp b/src/xwayland/XSurface.hpp index 61eee984..7584354e 100644 --- a/src/xwayland/XSurface.hpp +++ b/src/xwayland/XSurface.hpp @@ -1,6 +1,5 @@ #pragma once -#include "../helpers/WLListener.hpp" #include "../helpers/signal/Signal.hpp" #include "../helpers/memory/Memory.hpp" #include "../helpers/math/Math.hpp" diff --git a/src/xwayland/XWM.cpp b/src/xwayland/XWM.cpp index f6b6864c..248813bf 100644 --- a/src/xwayland/XWM.cpp +++ b/src/xwayland/XWM.cpp @@ -1,20 +1,21 @@ #include "helpers/math/Math.hpp" +#include #ifndef NO_XWAYLAND +#include +#include +#include +#include +#include +#include + #include "XWayland.hpp" #include "../defines.hpp" -#include #include "../Compositor.hpp" +#include "../protocols/core/Seat.hpp" +#include "../managers/SeatManager.hpp" #include "../protocols/XWaylandShell.hpp" #include "../protocols/core/Compositor.hpp" -#include "../managers/SeatManager.hpp" -#include "../protocols/core/Seat.hpp" -#include -#include -#include -#include - -#include #define XCB_EVENT_RESPONSE_TYPE_MASK 0x7f #define INCR_CHUNK_SIZE (64 * 1024) @@ -24,7 +25,7 @@ static int onX11Event(int fd, uint32_t mask, void* data) { } SP CXWM::windowForXID(xcb_window_t wid) { - for (auto& s : surfaces) { + for (auto const& s : surfaces) { if (s->xID == wid) return s; } @@ -81,6 +82,7 @@ void CXWM::handleConfigureNotify(xcb_configure_notify_event_t* e) { return; XSURF->geometry = {e->x, e->y, e->width, e->height}; + updateOverrideRedirect(XSURF, e->override_redirect); XSURF->events.setGeometry.emit(); } @@ -115,7 +117,12 @@ void CXWM::handleMapRequest(xcb_map_request_event_t* e) { void CXWM::handleMapNotify(xcb_map_notify_event_t* e) { const auto XSURF = windowForXID(e->window); - if (!XSURF || XSURF->overrideRedirect) + if (!XSURF) + return; + + updateOverrideRedirect(XSURF, e->override_redirect); + + if (XSURF->overrideRedirect) return; XSURF->setWithdrawn(false); @@ -143,27 +150,48 @@ void CXWM::handleUnmapNotify(xcb_unmap_notify_event_t* e) { } static bool lookupParentExists(SP XSURF, SP prospectiveChild) { + std::vector> visited; + while (XSURF->parent) { if (XSURF->parent == prospectiveChild) return true; + visited.emplace_back(XSURF); + XSURF = XSURF->parent.lock(); + + if (std::find(visited.begin(), visited.end(), XSURF) != visited.end()) + return false; } return false; } +std::string CXWM::getAtomName(uint32_t atom) { + for (auto const& ha : HYPRATOMS) { + if (ha.second != atom) + continue; + + return ha.first; + } + + // Get the name of the atom + auto const atom_name_cookie = xcb_get_atom_name(connection, atom); + auto* atom_name_reply = xcb_get_atom_name_reply(connection, atom_name_cookie, NULL); + + if (!atom_name_reply) + return "Unknown"; + + auto const name_len = xcb_get_atom_name_name_length(atom_name_reply); + auto* name = xcb_get_atom_name_name(atom_name_reply); + free(atom_name_reply); + + return {name, name_len}; +} + void CXWM::readProp(SP XSURF, uint32_t atom, xcb_get_property_reply_t* reply) { std::string propName; - if (Debug::trace) { - propName = std::format("{}?", atom); - for (auto& ha : HYPRATOMS) { - if (ha.second != atom) - continue; - - propName = ha.first; - break; - } - } + if (Debug::trace) + propName = getAtomName(atom); if (atom == XCB_ATOM_WM_CLASS) { size_t len = xcb_get_property_value_length(reply); @@ -285,7 +313,7 @@ void CXWM::handleClientMessage(xcb_client_message_event_t* e) { return; std::string propName = "?"; - for (auto& ha : HYPRATOMS) { + for (auto const& ha : HYPRATOMS) { if (ha.second != e->type) continue; @@ -317,7 +345,7 @@ void CXWM::handleClientMessage(xcb_client_message_event_t* e) { Debug::log(LOG, "[xwm] surface {:x} requests serial {:x}", (uintptr_t)XSURF.get(), XSURF->wlSerial); - for (auto& res : shellResources) { + for (auto const& res : shellResources) { if (!res) continue; @@ -417,7 +445,7 @@ void CXWM::focusWindow(SP surf) { // send state to all toplevel surfaces, sometimes we might lose some // that could still stick with the focused atom - for (auto& s : mappedSurfaces) { + for (auto const& s : mappedSurfaces) { if (!s || s->overrideRedirect) continue; @@ -539,7 +567,7 @@ bool CXWM::handleSelectionPropertyNotify(xcb_property_notify_event_t* e) { // Debug::log(ERR, "[xwm] FIXME: CXWM::handleSelectionPropertyNotify stub"); - return true; + return false; } void CXWM::handleSelectionRequest(xcb_selection_request_event_t* e) { @@ -579,7 +607,7 @@ void CXWM::handleSelectionRequest(xcb_selection_request_event_t* e) { atoms.push_back(HYPRATOMS["TIMESTAMP"]); atoms.push_back(HYPRATOMS["TARGETS"]); - for (auto& m : mimes) { + for (auto const& m : mimes) { atoms.push_back(mimeToAtom(m)); } @@ -810,15 +838,15 @@ void CXWM::getRenderFormat() { free(reply); } -CXWM::CXWM() { - connection = xcb_connect_to_fd(g_pXWayland->pServer->xwmFDs[0], nullptr); +CXWM::CXWM() : connection(g_pXWayland->pServer->xwmFDs[0]) { - if (int ret = xcb_connection_has_error(connection); ret) { - Debug::log(ERR, "[xwm] Couldn't start, error {}", ret); + if (connection.hasError()) { + Debug::log(ERR, "[xwm] Couldn't start, error {}", connection.hasError()); return; } - if (xcb_errors_context_new(connection, &errors)) { + CXCBErrorContext xcbErrCtx(connection); + if (!xcbErrCtx.isValid()) { Debug::log(ERR, "[xwm] Couldn't allocate errors context"); return; } @@ -847,10 +875,7 @@ CXWM::CXWM() { }; xcb_change_property(connection, XCB_PROP_MODE_REPLACE, screen->root, HYPRATOMS["_NET_SUPPORTED"], XCB_ATOM_ATOM, 32, sizeof(supported) / sizeof(*supported), supported); - xcb_flush(connection); - setActiveWindow(XCB_WINDOW_NONE); - initSelection(); listeners.newWLSurface = PROTO::compositor->events.newSurface.registerListener([this](std::any d) { onNewSurface(std::any_cast>(d)); }); @@ -862,14 +887,13 @@ CXWM::CXWM() { } CXWM::~CXWM() { - if (errors) - xcb_errors_context_free(errors); - - if (connection) - xcb_disconnect(connection); if (eventSource) wl_event_source_remove(eventSource); + + for (auto const& sr : surfaces) { + sr->events.destroy.emit(); + } } void CXWM::setActiveWindow(xcb_window_t window) { @@ -897,7 +921,7 @@ void CXWM::activateSurface(SP surf, bool activate) { if ((surf == focusedSurface && activate) || (surf && surf->overrideRedirect)) return; - if (!surf) { + if (!surf || (!activate && g_pCompositor->m_pLastWindow && !g_pCompositor->m_pLastWindow->m_bIsX11)) { setActiveWindow((uint32_t)XCB_WINDOW_NONE); focusWindow(nullptr); } else { @@ -944,7 +968,7 @@ void CXWM::onNewSurface(SP surf) { const auto WLID = surf->id(); - for (auto& sr : surfaces) { + for (auto const& sr : surfaces) { if (sr->surface || sr->wlID != WLID) continue; @@ -961,7 +985,7 @@ void CXWM::onNewResource(SP resource) { std::erase_if(shellResources, [](const auto& e) { return e.expired(); }); shellResources.push_back(resource); - for (auto& surf : surfaces) { + for (auto const& surf : surfaces) { if (surf->resource || surf->wlSerial != resource->serial) continue; @@ -1025,7 +1049,7 @@ void CXWM::updateClientList() { std::erase_if(mappedSurfacesStacking, [](const auto& e) { return e.expired() || !e->mapped; }); std::vector windows; - for (auto& m : mappedSurfaces) { + for (auto const& m : mappedSurfaces) { windows.push_back(m->xID); } @@ -1033,7 +1057,7 @@ void CXWM::updateClientList() { windows.clear(); - for (auto& m : mappedSurfacesStacking) { + for (auto const& m : mappedSurfacesStacking) { windows.push_back(m->xID); } @@ -1044,6 +1068,13 @@ bool CXWM::isWMWindow(xcb_window_t w) { return w == wmWindow || w == clipboard.window; } +void CXWM::updateOverrideRedirect(SP surf, bool overrideRedirect) { + if (!surf || surf->overrideRedirect == overrideRedirect) + return; + + surf->overrideRedirect = overrideRedirect; +} + void CXWM::initSelection() { clipboard.window = xcb_generate_id(connection); uint32_t mask[1] = {XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE}; diff --git a/src/xwayland/XWM.hpp b/src/xwayland/XWM.hpp index 4f6f3f7c..37c61416 100644 --- a/src/xwayland/XWM.hpp +++ b/src/xwayland/XWM.hpp @@ -1,17 +1,15 @@ #pragma once -#include "../helpers/signal/Signal.hpp" -#include "../helpers/memory/Memory.hpp" -#include "../helpers/WLListener.hpp" #include "../macros.hpp" - #include "XDataSource.hpp" +#include "../helpers/memory/Memory.hpp" +#include "../helpers/signal/Signal.hpp" #include -#include -#include -#include #include +#include +#include +#include struct wl_event_source; class CXWaylandSurfaceResource; @@ -58,6 +56,49 @@ struct SXSelection { std::unique_ptr transfer; }; +class CXCBConnection { + public: + CXCBConnection(int fd) { + connection = xcb_connect_to_fd(fd, nullptr); + } + + ~CXCBConnection() { + if (connection) + xcb_disconnect(connection); + } + + bool hasError() const { + return xcb_connection_has_error(connection); + } + + operator xcb_connection_t*() const { + return connection; + } + + private: + xcb_connection_t* connection = nullptr; +}; + +class CXCBErrorContext { + public: + explicit CXCBErrorContext(xcb_connection_t* connection) { + if (xcb_errors_context_new(connection, &errors) != 0) + errors = nullptr; + } + + ~CXCBErrorContext() { + if (errors) + xcb_errors_context_free(errors); + } + + bool isValid() const { + return errors != nullptr; + } + + private: + xcb_errors_context_t* errors = nullptr; +}; + class CXWM { public: CXWM(); @@ -82,6 +123,7 @@ class CXWM { void focusWindow(SP surf); void activateSurface(SP surf, bool activate); bool isWMWindow(xcb_window_t w); + void updateOverrideRedirect(SP surf, bool overrideRedirect); void sendWMMessage(SP surf, xcb_client_message_data_t* data, uint32_t mask); @@ -118,12 +160,13 @@ class CXWM { std::string mimeFromAtom(xcb_atom_t atom); void setClipboardToWayland(SXSelection& sel); void getTransferData(SXSelection& sel); + std::string getAtomName(uint32_t atom); void readProp(SP XSURF, uint32_t atom, xcb_get_property_reply_t* reply); // - xcb_connection_t* connection = nullptr; - xcb_errors_context_t* errors = nullptr; - xcb_screen_t* screen = nullptr; + CXCBConnection connection; + xcb_errors_context_t* errors = nullptr; + xcb_screen_t* screen = nullptr; xcb_window_t wmWindow; diff --git a/src/xwayland/XWayland.cpp b/src/xwayland/XWayland.cpp index 8d45fa63..8cdb1fca 100644 --- a/src/xwayland/XWayland.cpp +++ b/src/xwayland/XWayland.cpp @@ -1,12 +1,17 @@ #include "XWayland.hpp" #include "../debug/Log.hpp" -CXWayland::CXWayland() { +CXWayland::CXWayland(const bool enabled) { #ifndef NO_XWAYLAND Debug::log(LOG, "Starting up the XWayland server"); pServer = std::make_unique(); + if (!enabled) { + unsetenv("DISPLAY"); + return; + } + if (!pServer->create()) { Debug::log(ERR, "XWayland failed to start: it will not work."); return; @@ -25,4 +30,4 @@ void CXWayland::setCursor(unsigned char* pixData, uint32_t stride, const Vector2 pWM->setCursor(pixData, stride, size, hotspot); #endif -} \ No newline at end of file +} diff --git a/src/xwayland/XWayland.hpp b/src/xwayland/XWayland.hpp index d1cc4421..96253d19 100644 --- a/src/xwayland/XWayland.hpp +++ b/src/xwayland/XWayland.hpp @@ -3,6 +3,7 @@ #include #include "../helpers/signal/Signal.hpp" #include "../helpers/memory/Memory.hpp" +#include "../macros.hpp" #include "XSurface.hpp" @@ -16,7 +17,7 @@ class CXWM; class CXWayland { public: - CXWayland(); + CXWayland(const bool enabled); #ifndef NO_XWAYLAND std::unique_ptr pServer; @@ -125,4 +126,4 @@ inline std::unordered_map HYPRATOMS = { HYPRATOM("DELETE"), HYPRATOM("TEXT"), HYPRATOM("INCR"), -}; \ No newline at end of file +}; diff --git a/subprojects/hyprland-protocols b/subprojects/hyprland-protocols index e06482e0..c7c3f4cd 160000 --- a/subprojects/hyprland-protocols +++ b/subprojects/hyprland-protocols @@ -1 +1 @@ -Subproject commit e06482e0e611130cd1929f75e8c1cf679e57d161 +Subproject commit c7c3f4cd0faed21fc90ba6bd06fe4f3e0e057ea8 diff --git a/subprojects/tracy.wrap b/subprojects/tracy.wrap new file mode 100644 index 00000000..11b21787 --- /dev/null +++ b/subprojects/tracy.wrap @@ -0,0 +1 @@ +[wrap-file] diff --git a/subprojects/udis86.wrap b/subprojects/udis86.wrap new file mode 100644 index 00000000..dfb63984 --- /dev/null +++ b/subprojects/udis86.wrap @@ -0,0 +1,5 @@ +[wrap-file] +method = cmake + +[provide] +udis86 = libudis86_dep