diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index fda97f57..75b4b7c5 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,6 +1,8 @@ diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d14ac02e..2ec558ed 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -46,11 +46,38 @@ jobs: if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork name: "Code Style" runs-on: ubuntu-latest + container: + image: archlinux steps: - name: Checkout repository uses: actions/checkout@v4 + # - name: clang-format check + # uses: jidicula/clang-format-action@v4.16.0 + # with: + # exclude-regex: ^subprojects$ + + - name: Install clang-format + run: | + pacman --noconfirm --noprogressbar -Syyu + pacman --noconfirm --noprogressbar -Sy clang + - name: clang-format check - uses: jidicula/clang-format-action@v4.16.0 - with: - exclude-regex: ^subprojects$ + run: .github/workflows/clang-format-check.sh "." "llvm" "^subprojects$" "" + + - name: Save PR head commit SHA + if: failure() && github.event_name == 'pull_request' + shell: bash + run: | + SHA="${{ github.event.pull_request.head.sha }}" + echo "SHA=$SHA" >> $GITHUB_ENV + - name: Save latest commit SHA if not PR + if: failure() && github.event_name != 'pull_request' + shell: bash + run: echo "SHA=${{ github.sha }}" >> $GITHUB_ENV + + - name: Report failure in job summary + if: failure() + run: | + DEEPLINK="${{ github.server_url }}/${{ github.repository }}/commit/${{ env.SHA }}" + echo -e "Format check failed on commit [${GITHUB_SHA:0:8}]($DEEPLINK) with files:\n$(<$GITHUB_WORKSPACE/failing-files.txt)" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/clang-format-check.sh b/.github/workflows/clang-format-check.sh new file mode 100755 index 00000000..41237aa7 --- /dev/null +++ b/.github/workflows/clang-format-check.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash +# +# Adapted from https://github.com/jidicula/clang-format-action + +############################################################################### +# check.sh # +############################################################################### +# USAGE: ./entrypoint.sh [] [] +# +# Checks all C/C++/Protobuf/CUDA files (.h, .H, .hpp, .hh, .h++, .hxx and .c, +# .C, .cpp, .cc, .c++, .cxx, .proto, .cu) in the provided GitHub repository path +# (arg1) for conforming to clang-format. If no path is provided or provided path +# is not a directory, all C/C++/Protobuf/CUDA files are checked. If any files +# are incorrectly formatted, the script lists them and exits with 1. +# +# Define your own formatting rules in a .clang-format file at your repository +# root. Otherwise, the provided style guide (arg2) is used as a fallback. + +# format_diff function +# Accepts a filepath argument. The filepath passed to this function must point +# to a C/C++/Protobuf/CUDA file. +format_diff() { + local filepath="$1" + + # Invoke clang-format with dry run and formatting error output + local_format="$(clang-format \ + --dry-run \ + --Werror \ + --style=file \ + --fallback-style="$FALLBACK_STYLE" \ + "${filepath}")" + + local format_status="$?" + if [[ ${format_status} -ne 0 ]]; then + # Append Markdown-bulleted monospaced filepath of failing file to + # summary file. + echo "* \`$filepath\`" >>failing-files.txt + + echo "Failed on file: $filepath" >&2 + echo "$local_format" >&2 + exit_code=1 # flip the global exit code + return "${format_status}" + fi + return 0 +} + +CHECK_PATH="$1" +FALLBACK_STYLE="$2" +EXCLUDE_REGEX="$3" +INCLUDE_REGEX="$4" + +# Set the regex to an empty string regex if nothing was provided +if [[ -z $EXCLUDE_REGEX ]]; then + EXCLUDE_REGEX="^$" +fi + +# Set the filetype regex if nothing was provided. +# Find all C/C++/Protobuf/CUDA files: +# h, H, hpp, hh, h++, hxx +# c, C, cpp, cc, c++, cxx +# ino, pde +# proto +# cu +if [[ -z $INCLUDE_REGEX ]]; then + INCLUDE_REGEX='^.*\.((((c|C)(c|pp|xx|\+\+)?$)|((h|H)h?(pp|xx|\+\+)?$))|(ino|pde|proto|cu))$' +fi + +cd "$GITHUB_WORKSPACE" || exit 2 + +if [[ ! -d $CHECK_PATH ]]; then + echo "Not a directory in the workspace, fallback to all files." >&2 + CHECK_PATH="." +fi + +# initialize exit code +exit_code=0 + +# All files improperly formatted will be printed to the output. +src_files=$(find "$CHECK_PATH" -name .git -prune -o -regextype posix-egrep -regex "$INCLUDE_REGEX" -print) + +# check formatting in each source file +IFS=$'\n' # Loop below should separate on new lines, not spaces. +for file in $src_files; do + # Only check formatting if the path doesn't match the regex + if ! [[ ${file} =~ $EXCLUDE_REGEX ]]; then + format_diff "${file}" + fi +done + +# global exit code is flipped to nonzero if any invocation of `format_diff` has +# a formatting difference. +exit "$exit_code" diff --git a/CMakeLists.txt b/CMakeLists.txt index db1fcfe4..87574b82 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -110,6 +110,7 @@ add_compile_options( -Wno-narrowing -Wno-pointer-arith -Wno-clobbered + -frtti -fmacro-prefix-map=${CMAKE_SOURCE_DIR}/=) # disable lto as it may break plugins @@ -124,6 +125,7 @@ find_package(Threads REQUIRED) set(GLES_VERSION "GLES3") find_package(OpenGL REQUIRED COMPONENTS ${GLES_VERSION}) +find_package(glslang CONFIG REQUIRED) set(AQUAMARINE_MINIMUM_VERSION 0.9.3) set(HYPRLANG_MINIMUM_VERSION 0.6.7) @@ -243,7 +245,7 @@ configure_file( set_source_files_properties(${CMAKE_SOURCE_DIR}/src/version.h PROPERTIES GENERATED TRUE) set(XKBCOMMON_MINIMUM_VERSION 1.11.0) -set(WAYLAND_SERVER_MINIMUM_VERSION 1.22.90) +set(WAYLAND_SERVER_MINIMUM_VERSION 1.22.91) set(WAYLAND_SERVER_PROTOCOLS_MINIMUM_VERSION 1.45) set(LIBINPUT_MINIMUM_VERSION 1.28) @@ -265,7 +267,8 @@ pkg_check_modules( gbm gio-2.0 re2 - muparser) + muparser + lcms2) find_package(hyprwayland-scanner 0.3.10 REQUIRED) @@ -298,21 +301,6 @@ if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) target_compile_options(hyprland_lib PUBLIC -fsanitize=address) endif() - if(USE_TRACY) - message(STATUS "Tracy is turned on") - - option(TRACY_ENABLE "" ON) - option(TRACY_ON_DEMAND "" ON) - add_subdirectory(subprojects/tracy) - - target_link_libraries(hyprland_lib PUBLIC Tracy::TracyClient) - - if(USE_TRACY_GPU) - message(STATUS "Tracy GPU Profiling is turned on") - add_compile_definitions(USE_TRACY_GPU) - endif() - endif() - add_compile_options(-fno-pie -fno-builtin) add_link_options(-no-pie -fno-builtin) if(USE_GPROF) @@ -321,6 +309,23 @@ if(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG) endif() endif() +if(USE_TRACY) + message(STATUS "Tracy is turned on") + + option(TRACY_ENABLE "" ON) + option(TRACY_ON_DEMAND "" ON) + add_subdirectory(subprojects/tracy) + + add_compile_options(-fno-omit-frame-pointer) + + target_link_libraries(hyprland_lib PUBLIC Tracy::TracyClient) + + if(USE_TRACY_GPU) + message(STATUS "Tracy GPU Profiling is turned on") + add_compile_definitions(USE_TRACY_GPU) + endif() +endif() + if(BUILT_WITH_NIX) add_compile_definitions(BUILT_WITH_NIX) endif() @@ -474,7 +479,11 @@ function(protocolWayland) set(PROTOCOL_SOURCES "${PROTOCOL_SOURCES}" PARENT_SCOPE) endfunction() -target_link_libraries(hyprland_lib PUBLIC OpenGL::EGL OpenGL::GL Threads::Threads) +if(TARGET OpenGL::GL) + target_link_libraries(hyprland_lib PUBLIC OpenGL::EGL OpenGL::GL glslang::glslang glslang::glslang-default-resource-limits glslang::SPIRV Threads::Threads) +else() + target_link_libraries(hyprland_lib PUBLIC OpenGL::EGL OpenGL::GLES3 glslang::glslang glslang::glslang-default-resource-limits glslang::SPIRV Threads::Threads) +endif() pkg_check_modules(hyprland_protocols_dep hyprland-protocols>=0.6.4) if(hyprland_protocols_dep_FOUND) @@ -549,6 +558,8 @@ protocolnew("staging/ext-data-control" "ext-data-control-v1" false) protocolnew("staging/pointer-warp" "pointer-warp-v1" false) protocolnew("staging/fifo" "fifo-v1" false) protocolnew("staging/commit-timing" "commit-timing-v1" false) +protocolnew("staging/ext-image-capture-source" "ext-image-capture-source-v1" false) +protocolnew("staging/ext-image-copy-capture" "ext-image-copy-capture-v1" false) protocolwayland() diff --git a/LICENSE b/LICENSE index e881cf92..efdec21a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2022-2025, vaxerski +Copyright (c) 2022-2026, vaxerski All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/Makefile b/Makefile index 282258ed..852fcddf 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,7 @@ nopch: clear: rm -rf build rm -f ./protocols/*.h ./protocols/*.c ./protocols/*.cpp ./protocols/*.hpp + rm -f ./hyprctl/hw-protocols/*.cpp ./hyprctl/hw-protocols/*.hpp all: $(MAKE) clear @@ -87,7 +88,7 @@ asan: @echo "Wayland done" patch -p1 < ./scripts/hyprlandStaticAsan.diff - cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DWITH_ASAN:STRING=True -DUSE_TRACY:STRING=False -DUSE_TRACY_GPU:STRING=False -S . -B ./build -G Ninja + cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -DWITH_ASAN:STRING=True -DUSE_TRACY:STRING=False -DUSE_TRACY_GPU:STRING=False -S . -B ./build cmake --build ./build --config Debug --target all @echo "Hyprland done" diff --git a/VERSION b/VERSION index 7f422a16..524456c7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.53.0 +0.54.0 diff --git a/example/hyprland.conf b/example/hyprland.conf index 98ac0996..15b0e4f7 100644 --- a/example/hyprland.conf +++ b/example/hyprland.conf @@ -242,7 +242,7 @@ bind = $mainMod, E, exec, $fileManager bind = $mainMod, V, togglefloating, bind = $mainMod, R, exec, $menu bind = $mainMod, P, pseudo, # dwindle -bind = $mainMod, J, togglesplit, # dwindle +bind = $mainMod, J, layoutmsg, togglesplit # dwindle # Move focus with mainMod + arrow keys bind = $mainMod, left, movefocus, l diff --git a/flake.lock b/flake.lock index cffa6fac..4a89c0fc 100644 --- a/flake.lock +++ b/flake.lock @@ -16,11 +16,11 @@ ] }, "locked": { - "lastModified": 1765900596, - "narHash": "sha256-+hn8v9jkkLP9m+o0Nm5SiEq10W0iWDSotH2XfjU45fA=", + "lastModified": 1772292445, + "narHash": "sha256-4F1Q7U313TKUDDovCC96m/Za4wZcJ3yqtu4eSrj8lk8=", "owner": "hyprwm", "repo": "aquamarine", - "rev": "d83c97f8f5c0aae553c1489c7d9eff3eadcadace", + "rev": "1dbbba659c1cef0b0202ce92cadfe13bae550e8f", "type": "github" }, "original": { @@ -32,15 +32,15 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1761588595, - "narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=", - "owner": "edolstra", + "lastModified": 1767039857, + "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", + "owner": "NixOS", "repo": "flake-compat", - "rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", "type": "github" }, "original": { - "owner": "edolstra", + "owner": "NixOS", "repo": "flake-compat", "type": "github" } @@ -105,11 +105,11 @@ ] }, "locked": { - "lastModified": 1763733840, - "narHash": "sha256-JnET78yl5RvpGuDQy3rCycOCkiKoLr5DN1fPhRNNMco=", + "lastModified": 1770511807, + "narHash": "sha256-suKmSbSk34uPOJDTg/GbPrKEJutzK08vj0VoTvAFBCA=", "owner": "hyprwm", "repo": "hyprgraphics", - "rev": "8f1bec691b2d198c60cccabca7a94add2df4ed1a", + "rev": "7c75487edd43a71b61adb01cae8326d277aab683", "type": "github" }, "original": { @@ -144,11 +144,11 @@ ] }, "locked": { - "lastModified": 1765643131, - "narHash": "sha256-CCGohW5EBIRy4B7vTyBMqPgsNcaNenVad/wszfddET0=", + "lastModified": 1767023960, + "narHash": "sha256-R2HgtVS1G3KSIKAQ77aOZ+Q0HituOmPgXW9nBNkpp3Q=", "owner": "hyprwm", "repo": "hyprland-guiutils", - "rev": "e50ae912813bdfa8372d62daf454f48d6df02297", + "rev": "c2e906261142f5dd1ee0bfc44abba23e2754c660", "type": "github" }, "original": { @@ -193,11 +193,11 @@ ] }, "locked": { - "lastModified": 1764612430, - "narHash": "sha256-54ltTSbI6W+qYGMchAgCR6QnC1kOdKXN6X6pJhOWxFg=", + "lastModified": 1771866172, + "narHash": "sha256-fYFoXhQLrm1rD8vSFKQBOEX4OGCuJdLt1amKfHd5GAw=", "owner": "hyprwm", "repo": "hyprlang", - "rev": "0d00dc118981531aa731150b6ea551ef037acddd", + "rev": "0b219224910e7642eb0ed49f0db5ec3d008e3e41", "type": "github" }, "original": { @@ -261,11 +261,11 @@ ] }, "locked": { - "lastModified": 1766160771, - "narHash": "sha256-roINUGikWRqqgKrD4iotKbGj3ZKJl3hjMz5l/SyKrHw=", + "lastModified": 1771271487, + "narHash": "sha256-41gEiUS0Pyw3L/ge1l8MXn61cK14VAhgWB/JV8s/oNI=", "owner": "hyprwm", "repo": "hyprutils", - "rev": "5ac060bfcf2f12b3a6381156ebbc13826a05b09f", + "rev": "340a792e3b3d482c4ae5f66d27a9096bdee6d76d", "type": "github" }, "original": { @@ -284,11 +284,11 @@ ] }, "locked": { - "lastModified": 1763640274, - "narHash": "sha256-Uan1Nl9i4TF/kyFoHnTq1bd/rsWh4GAK/9/jDqLbY5A=", + "lastModified": 1770501770, + "narHash": "sha256-NWRM6+YxTRv+bT9yvlhhJ2iLae1B1pNH3mAL5wi2rlQ=", "owner": "hyprwm", "repo": "hyprwayland-scanner", - "rev": "f6cf414ca0e16a4d30198fd670ec86df3c89f671", + "rev": "0bd8b6cde9ec27d48aad9e5b4deefb3746909d40", "type": "github" }, "original": { @@ -310,11 +310,11 @@ ] }, "locked": { - "lastModified": 1766253200, - "narHash": "sha256-26qPwrd3od+xoYVywSB7hC2cz9ivN46VPLlrsXyGxvE=", + "lastModified": 1771606233, + "narHash": "sha256-F3PLUqQ/TwgR70U+UeOqJnihJZ2EuunzojYC4g5xHr0=", "owner": "hyprwm", "repo": "hyprwire", - "rev": "1079777525b30a947c8d657fac158e00ae85de9d", + "rev": "06c7f1f8c4194786c8400653c4efc49dc14c0f3a", "type": "github" }, "original": { @@ -325,11 +325,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1766070988, - "narHash": "sha256-G/WVghka6c4bAzMhTwT2vjLccg/awmHkdKSd2JrycLc=", + "lastModified": 1772198003, + "narHash": "sha256-I45esRSssFtJ8p/gLHUZ1OUaaTaVLluNkABkk6arQwE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "c6245e83d836d0433170a16eb185cefe0572f8b8", + "rev": "dd9b079222d43e1943b6ebd802f04fd959dc8e61", "type": "github" }, "original": { @@ -348,11 +348,11 @@ ] }, "locked": { - "lastModified": 1765911976, - "narHash": "sha256-t3T/xm8zstHRLx+pIHxVpQTiySbKqcQbK+r+01XVKc0=", + "lastModified": 1772024342, + "narHash": "sha256-+eXlIc4/7dE6EcPs9a2DaSY3fTA9AE526hGqkNID3Wg=", "owner": "cachix", "repo": "git-hooks.nix", - "rev": "b68b780b69702a090c8bb1b973bab13756cc7a27", + "rev": "6e34e97ed9788b17796ee43ccdbaf871a5c2b476", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 21561cc5..6d695bfb 100644 --- a/flake.nix +++ b/flake.nix @@ -88,108 +88,122 @@ }; }; - outputs = inputs @ { - self, - nixpkgs, - systems, - ... - }: let - inherit (nixpkgs) lib; - eachSystem = lib.genAttrs (import systems); - pkgsFor = eachSystem (system: - import nixpkgs { - localSystem = system; - overlays = with self.overlays; [ - hyprland-packages - hyprland-extras - ]; - }); - pkgsCrossFor = eachSystem (system: crossSystem: - import nixpkgs { - localSystem = system; - inherit crossSystem; - overlays = with self.overlays; [ - hyprland-packages - hyprland-extras - ]; - }); - pkgsDebugFor = eachSystem (system: - import nixpkgs { - localSystem = system; - overlays = with self.overlays; [ - hyprland-debug - ]; - }); - pkgsDebugCrossFor = eachSystem (system: crossSystem: - import nixpkgs { - localSystem = system; - inherit crossSystem; - overlays = with self.overlays; [ - hyprland-debug - ]; - }); - in { - overlays = import ./nix/overlays.nix {inherit self lib inputs;}; + outputs = + inputs@{ + self, + nixpkgs, + systems, + ... + }: + let + inherit (nixpkgs) lib; + eachSystem = lib.genAttrs (import systems); + pkgsFor = eachSystem ( + system: + import nixpkgs { + localSystem = system; + overlays = with self.overlays; [ + hyprland-packages + hyprland-extras + ]; + } + ); + pkgsCrossFor = eachSystem ( + system: crossSystem: + import nixpkgs { + localSystem = system; + inherit crossSystem; + overlays = with self.overlays; [ + hyprland-packages + hyprland-extras + ]; + } + ); + pkgsDebugFor = eachSystem ( + system: + import nixpkgs { + localSystem = system; + overlays = with self.overlays; [ + hyprland-debug + ]; + } + ); + pkgsDebugCrossFor = eachSystem ( + system: crossSystem: + import nixpkgs { + localSystem = system; + inherit crossSystem; + overlays = with self.overlays; [ + hyprland-debug + ]; + } + ); + in + { + overlays = import ./nix/overlays.nix { inherit self lib inputs; }; - checks = eachSystem (system: - (lib.filterAttrs - (n: _: (lib.hasPrefix "hyprland" n) && !(lib.hasSuffix "debug" n)) - 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; + checks = eachSystem ( + system: + (lib.filterAttrs ( + n: _: (lib.hasPrefix "hyprland" n) && !(lib.hasSuffix "debug" n) + ) 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; + }; }; }; - }; - } - // (import ./nix/tests inputs pkgsFor.${system})); + } + // (import ./nix/tests inputs pkgsFor.${system}) + ); - packages = eachSystem (system: { - default = self.packages.${system}.hyprland; - inherit - (pkgsFor.${system}) - # hyprland-packages - hyprland - hyprland-unwrapped - hyprland-with-tests - # hyprland-extras - xdg-desktop-portal-hyprland - ; - inherit (pkgsDebugFor.${system}) hyprland-debug; - hyprland-cross = (pkgsCrossFor.${system} "aarch64-linux").hyprland; - hyprland-debug-cross = (pkgsDebugCrossFor.${system} "aarch64-linux").hyprland-debug; - }); + packages = eachSystem (system: { + default = self.packages.${system}.hyprland; + inherit (pkgsFor.${system}) + # hyprland-packages + hyprland + hyprland-unwrapped + hyprland-with-tests + # hyprland-extras + xdg-desktop-portal-hyprland + ; + inherit (pkgsDebugFor.${system}) hyprland-debug; + hyprland-cross = (pkgsCrossFor.${system} "aarch64-linux").hyprland; + hyprland-debug-cross = (pkgsDebugCrossFor.${system} "aarch64-linux").hyprland-debug; + }); - devShells = eachSystem (system: { - default = - pkgsFor.${system}.mkShell.override { - inherit (self.packages.${system}.default) stdenv; - } { - name = "hyprland-shell"; - hardeningDisable = ["fortify"]; - inputsFrom = [pkgsFor.${system}.hyprland]; - packages = [pkgsFor.${system}.clang-tools]; - inherit (self.checks.${system}.pre-commit-check) shellHook; - }; - }); + devShells = eachSystem (system: { + default = + pkgsFor.${system}.mkShell.override + { + inherit (self.packages.${system}.default) stdenv; + } + { + name = "hyprland-shell"; + hardeningDisable = [ "fortify" ]; + inputsFrom = [ pkgsFor.${system}.hyprland ]; + packages = [ pkgsFor.${system}.clang-tools ]; + inherit (self.checks.${system}.pre-commit-check) shellHook; + }; + }); - formatter = eachSystem (system: pkgsFor.${system}.callPackage ./nix/formatter.nix {}); + formatter = eachSystem (system: pkgsFor.${system}.callPackage ./nix/formatter.nix { }); - nixosModules.default = import ./nix/module.nix inputs; - homeManagerModules.default = import ./nix/hm-module.nix self; + nixosModules.default = import ./nix/module.nix inputs; + homeManagerModules.default = import ./nix/hm-module.nix self; - # Hydra build jobs - # Recent versions of Hydra can aggregate jobsets from 'hydraJobs' instead of a release.nix - # or similar. Remember to filter large or incompatible attributes here. More eval jobs can - # be added by merging, e.g., self.packages // self.devShells. - hydraJobs = self.packages; - }; + # Hydra build jobs + # Recent versions of Hydra can aggregate jobsets from 'hydraJobs' instead of a release.nix + # or similar. Remember to filter large or incompatible attributes here. More eval jobs can + # be added by merging, e.g., self.packages // self.devShells. + hydraJobs = self.packages; + }; } diff --git a/hyprctl/hw-protocols/hyprpaper_core.xml b/hyprctl/hw-protocols/hyprpaper_core.xml index fa2edc0a..3d26a102 100644 --- a/hyprctl/hw-protocols/hyprpaper_core.xml +++ b/hyprctl/hw-protocols/hyprpaper_core.xml @@ -1,5 +1,5 @@ - + BSD 3-Clause License @@ -31,7 +31,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + This is the core manager object for hyprpaper operations @@ -62,6 +62,13 @@ Destroys this object. Children remain alive until destroyed. + + + + Creates a status object + + + @@ -141,4 +148,25 @@ + + + + This is an object which will emit various status updates. + + + + + Sends the active wallpaper for a given monitor. This will be emitted + immediately after binding, and then every time the path changes. + + + + + + + + Destroys this object. + + + diff --git a/hyprctl/src/hyprpaper/Hyprpaper.cpp b/hyprctl/src/hyprpaper/Hyprpaper.cpp index afa7f653..93f4182a 100644 --- a/hyprctl/src/hyprpaper/Hyprpaper.cpp +++ b/hyprctl/src/hyprpaper/Hyprpaper.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -15,7 +16,7 @@ using namespace std::string_literals; constexpr const char* SOCKET_NAME = ".hyprpaper.sock"; static SP g_coreImpl; -constexpr const uint32_t PROTOCOL_VERSION_SUPPORTED = 1; +constexpr const uint32_t PROTOCOL_VERSION_SUPPORTED = 2; // static hyprpaperCoreWallpaperFitMode fitFromString(const std::string_view& sv) { @@ -53,21 +54,7 @@ static std::expected getFullPath(const std::string_vie return resolvePath(sv); } -std::expected Hyprpaper::makeHyprpaperRequest(const std::string_view& rq) { - if (!rq.contains(' ')) - return std::unexpected("Invalid request"); - - if (!rq.starts_with("/hyprpaper ")) - return std::unexpected("Invalid request"); - - std::string_view LHS, RHS; - auto spacePos = rq.find(' ', 12); - LHS = rq.substr(11, spacePos - 11); - RHS = rq.substr(spacePos + 1); - - if (LHS != "wallpaper") - return std::unexpected("Unknown hyprpaper request"); - +static std::expected doWallpaper(const std::string_view& RHS) { CVarList2 args(std::string{RHS}, 0, ','); const std::string MONITOR = std::string{args[0]}; @@ -99,7 +86,7 @@ std::expected Hyprpaper::makeHyprpaperRequest(const std::stri if (!socket) return std::unexpected("can't send: failed to connect to hyprpaper (is it running?)"); - g_coreImpl = makeShared(1); + g_coreImpl = makeShared(PROTOCOL_VERSION_SUPPORTED); socket->addImplementation(g_coreImpl); @@ -111,7 +98,7 @@ std::expected Hyprpaper::makeHyprpaperRequest(const std::stri if (!spec) return std::unexpected("can't send: hyprpaper doesn't have the spec?!"); - auto manager = makeShared(socket->bindProtocol(g_coreImpl->protocol(), PROTOCOL_VERSION_SUPPORTED)); + auto manager = makeShared(socket->bindProtocol(g_coreImpl->protocol(), std::min(PROTOCOL_VERSION_SUPPORTED, spec->specVer()))); if (!manager) return std::unexpected("wire error: couldn't create manager"); @@ -126,7 +113,11 @@ std::expected Hyprpaper::makeHyprpaperRequest(const std::stri wallpaper->setFailed([&canExit, &err](uint32_t code) { canExit = true; - err = std::format("failed to set wallpaper, code {}", code); + switch (code) { + case HYPRPAPER_CORE_APPLYING_ERROR_INVALID_PATH: err = std::format("failed to set wallpaper: Invalid path", code); break; + case HYPRPAPER_CORE_APPLYING_ERROR_INVALID_MONITOR: err = std::format("failed to set wallpaper: Invalid monitor", code); break; + default: err = std::format("failed to set wallpaper: unknown error, code {}", code); break; + } }); wallpaper->setSuccess([&canExit]() { canExit = true; }); @@ -145,4 +136,73 @@ std::expected Hyprpaper::makeHyprpaperRequest(const std::stri return std::unexpected(*err); return {}; -} \ No newline at end of file +} + +static std::expected doListActive() { + const auto RTDIR = getenv("XDG_RUNTIME_DIR"); + + if (!RTDIR || RTDIR[0] == '\0') + return std::unexpected("can't send: no XDG_RUNTIME_DIR"); + + const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE"); + + if (!HIS || HIS[0] == '\0') + return std::unexpected("can't send: no HYPRLAND_INSTANCE_SIGNATURE (not running under hyprland)"); + + auto socketPath = RTDIR + "/hypr/"s + HIS + "/"s + SOCKET_NAME; + + auto socket = Hyprwire::IClientSocket::open(socketPath); + + if (!socket) + return std::unexpected("can't send: failed to connect to hyprpaper (is it running?)"); + + g_coreImpl = makeShared(PROTOCOL_VERSION_SUPPORTED); + + socket->addImplementation(g_coreImpl); + + if (!socket->waitForHandshake()) + return std::unexpected("can't send: wire handshake failed"); + + auto spec = socket->getSpec(g_coreImpl->protocol()->specName()); + + if (!spec) + return std::unexpected("can't send: hyprpaper doesn't have the spec?!"); + + if (spec->specVer() < 2) + return std::unexpected("can't send: hyprpaper protocol version too low (hyprpaper too old)"); + + auto manager = makeShared(socket->bindProtocol(g_coreImpl->protocol(), std::min(PROTOCOL_VERSION_SUPPORTED, spec->specVer()))); + + if (!manager) + return std::unexpected("wire error: couldn't create manager"); + + auto status = makeShared(manager->sendGetStatusObject()); + + status->setActiveWallpaper([](const char* mon, const char* wp) { std::println("{}: {}", mon, wp); }); + + socket->roundtrip(); + + return {}; +} + +std::expected Hyprpaper::makeHyprpaperRequest(const std::string_view& rq) { + if (!rq.contains(' ')) + return std::unexpected("Invalid request"); + + if (!rq.starts_with("/hyprpaper ")) + return std::unexpected("Invalid request"); + + std::string_view LHS, RHS; + auto spacePos = rq.find(' ', 12); + LHS = rq.substr(11, spacePos - 11); + RHS = rq.substr(spacePos + 1); + + if (LHS == "wallpaper") + return doWallpaper(RHS); + else if (LHS == "listactive") + return doListActive(); + else + return std::unexpected("invalid hyprpaper request"); + + return {}; +} diff --git a/hyprctl/src/main.cpp b/hyprctl/src/main.cpp index 7146c635..0a33f3ed 100644 --- a/hyprctl/src/main.cpp +++ b/hyprctl/src/main.cpp @@ -228,23 +228,23 @@ int request(std::string_view arg, int minArgs = 0, bool needRoll = false) { constexpr size_t BUFFER_SIZE = 8192; char buffer[BUFFER_SIZE] = {0}; - sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE); - - if (sizeWritten < 0) { - if (errno == EWOULDBLOCK) - log("Hyprland IPC didn't respond in time\n"); - log("Couldn't read (6)"); - return 6; - } - - reply += std::string(buffer, sizeWritten); - - while (sizeWritten == BUFFER_SIZE) { + // read all data until server closes the connection + // this handles partial writes on the server side under high load + while (true) { sizeWritten = read(SERVERSOCKET, buffer, BUFFER_SIZE); + if (sizeWritten < 0) { + if (errno == EWOULDBLOCK) + log("Hyprland IPC didn't respond in time\n"); log("Couldn't read (6)"); return 6; } + + if (sizeWritten == 0) { + // server closed connection, we're done + break; + } + reply += std::string(buffer, sizeWritten); } diff --git a/hyprpm/CMakeLists.txt b/hyprpm/CMakeLists.txt index ee738104..7d5b8eda 100644 --- a/hyprpm/CMakeLists.txt +++ b/hyprpm/CMakeLists.txt @@ -11,9 +11,9 @@ set(CMAKE_CXX_STANDARD 23) pkg_check_modules(hyprpm_deps REQUIRED IMPORTED_TARGET tomlplusplus hyprutils>=0.7.0) -find_package(glaze 6.0.0 QUIET) +find_package(glaze 6.0.1 QUIET) if (NOT glaze_FOUND) - set(GLAZE_VERSION v6.1.0) + set(GLAZE_VERSION v6.0.1) message(STATUS "glaze dependency not found, retrieving ${GLAZE_VERSION} with FetchContent") include(FetchContent) FetchContent_Declare( @@ -21,6 +21,7 @@ if (NOT glaze_FOUND) GIT_REPOSITORY https://github.com/stephenberry/glaze.git GIT_TAG ${GLAZE_VERSION} GIT_SHALLOW TRUE + EXCLUDE_FROM_ALL ) FetchContent_MakeAvailable(glaze) endif() diff --git a/hyprpm/src/core/PluginManager.cpp b/hyprpm/src/core/PluginManager.cpp index 0d35b4ae..6621a49f 100644 --- a/hyprpm/src/core/PluginManager.cpp +++ b/hyprpm/src/core/PluginManager.cpp @@ -94,15 +94,18 @@ SHyprlandVersion CPluginManager::getHyprlandVersion(bool running) { auto hldate = (*jsonQuery)["commit_date"].get_string(); auto hlcommits = (*jsonQuery)["commits"].get_string(); + auto flags = (*jsonQuery)["flags"].get_array(); + bool isNix = std::ranges::any_of(flags, [](const auto& f) { return f.is_string() && f.get_string() == std::string_view{"nix"}; }); + size_t commits = 0; try { commits = std::stoull(hlcommits); } catch (...) { ; } if (m_bVerbose) - std::println("{}", verboseString("parsed commit {} at branch {} on {}, commits {}", hlcommit, hlbranch, hldate, commits)); + std::println("{}", verboseString("parsed commit {} at branch {} on {}, commits {}, nix: {}", hlcommit, hlbranch, hldate, commits, isNix)); - auto ver = SHyprlandVersion{hlbranch, hlcommit, hldate, abiHash, commits}; + auto ver = SHyprlandVersion{hlbranch, hlcommit, hldate, abiHash, commits, isNix}; if (running) verRunning = ver; @@ -128,12 +131,20 @@ bool CPluginManager::createSafeDirectory(const std::string& path) { return true; } +bool CPluginManager::validArg(const std::string& s) { + return !s.contains("'") && !s.ends_with("\\") && !s.starts_with("\\"); +} + bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& rev) { const auto HLVER = getHyprlandVersion(); + if (!validArg(url) || !validArg(rev)) { + std::println(stderr, "\n{}", failureString("url or rev invalid")); + return false; + } + if (!hasDeps()) { - std::println(stderr, "\n{}", - failureString("Could not clone the plugin repository. Dependencies not satisfied. Hyprpm requires: cmake, meson, cpio, pkg-config, git, g++, gcc")); + std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. Dependencies not satisfied. Hyprpm requires: cmake, cpio, pkg-config, git, g++, gcc")); return false; } @@ -196,7 +207,7 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& progress.printMessageAbove(infoString("Cloning {}", url)); - std::string ret = execAndGet(std::format("cd {} && git clone --recursive {} {}", getTempRoot(), url, USERNAME)); + std::string ret = execAndGet(std::format("cd {} && git clone --recursive '{}' {}", getTempRoot(), url, USERNAME)); if (!std::filesystem::exists(m_szWorkingPluginDirectory + "/.git")) { std::println(stderr, "\n{}", failureString("Could not clone the plugin repository. shell returned:\n{}", ret)); @@ -303,8 +314,14 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& progress.printMessageAbove(infoString("Building {}", p.name)); 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"; + const auto CMD_RAW = nixDevelopIfNeeded(std::format("cd {} && PKG_CONFIG_PATH=\"{}\" {}", m_szWorkingPluginDirectory, getPkgConfigPath(), bs), HLVER); + + if (!CMD_RAW) { + progress.printMessageAbove(failureString("Failed to build {}: {}", p.name, CMD_RAW.error())); + break; + } + + out += " -> " + *CMD_RAW + "\n" + execAndGet(*CMD_RAW) + "\n"; } if (m_bVerbose) @@ -389,7 +406,7 @@ eHeadersErrors CPluginManager::headersValid() { return HEADERS_MISSING; // find headers commit - const std::string& cmd = std::format("PKG_CONFIG_PATH=\"{}/share/pkgconfig\" pkgconf --cflags --keep-system-cflags hyprland", DataState::getHeadersPath()); + const std::string& cmd = std::format("PKG_CONFIG_PATH=\"{}\" pkgconf --cflags --keep-system-cflags hyprland", getPkgConfigPath()); auto headers = execAndGet(cmd); if (!headers.contains("-I/")) @@ -453,7 +470,7 @@ bool CPluginManager::updateHeaders(bool force) { const auto HLVER = getHyprlandVersion(false); if (!hasDeps()) { - std::println("\n{}", failureString("Could not update. Dependencies not satisfied. Hyprpm requires: cmake, meson, cpio, pkg-config, git, g++, gcc")); + std::println("\n{}", failureString("Could not update. Dependencies not satisfied. Hyprpm requires: cmake, cpio, pkg-config, git, g++, gcc")); return false; } @@ -495,11 +512,11 @@ bool CPluginManager::updateHeaders(bool force) { progress.printMessageAbove(verboseString("will shallow since: {}", SHALLOW_DATE)); std::string ret = - execAndGet(std::format("cd {} && git clone --recursive {} hyprland-{}{}", getTempRoot(), HL_URL, USERNAME, (bShallow ? " --shallow-since='" + SHALLOW_DATE + "'" : ""))); + execAndGet(std::format("cd {} && git clone --recursive '{}' hyprland-{}{}", getTempRoot(), HL_URL, USERNAME, (bShallow ? " --shallow-since='" + SHALLOW_DATE + "'" : ""))); if (!std::filesystem::exists(WORKINGDIR)) { progress.printMessageAbove(failureString("Clone failed. Retrying without shallow.")); - ret = execAndGet(std::format("cd {} && git clone --recursive {} hyprland-{}", getTempRoot(), HL_URL, USERNAME)); + ret = execAndGet(std::format("cd {} && git clone --recursive '{}' hyprland-{}", getTempRoot(), HL_URL, USERNAME)); } if (!std::filesystem::exists(WORKINGDIR + "/.git")) { @@ -542,8 +559,17 @@ bool CPluginManager::updateHeaders(bool force) { if (m_bVerbose) 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())); + const auto CONFIGURE_CMD = + nixDevelopIfNeeded(std::format("cd {} && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:STRING=\"{}\" -S . -B ./build", WORKINGDIR, + DataState::getHeadersPath()), + HLVER); + + if (!CONFIGURE_CMD) { + std::println(stderr, "\n{}", failureString("Could not configure hyprland: {}", CONFIGURE_CMD.error())); + return false; + } + + ret = execAndGet(*CONFIGURE_CMD); if (m_bVerbose) progress.printMessageAbove(verboseString("cmake returned: {}", ret)); @@ -631,7 +657,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) { const auto HLVER = getHyprlandVersion(false); CProgressBar progress; - progress.m_iMaxSteps = REPOS.size() * 2 + 2; + progress.m_iMaxSteps = (REPOS.size() * 2) + 2; progress.m_iSteps = 0; progress.m_szCurrentMessage = "Updating repositories"; progress.print(); @@ -652,7 +678,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) { progress.printMessageAbove(infoString("Cloning {}", repo.url)); - std::string ret = execAndGet(std::format("cd {} && git clone --recursive {} {}", getTempRoot(), repo.url, USERNAME)); + std::string ret = execAndGet(std::format("cd {} && git clone --recursive '{}' {}", getTempRoot(), repo.url, USERNAME)); if (!std::filesystem::exists(m_szWorkingPluginDirectory + "/.git")) { std::println("{}", failureString("could not clone repo: shell returned: {}", ret)); @@ -662,7 +688,7 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) { if (!repo.rev.empty()) { progress.printMessageAbove(infoString("Plugin has revision set, resetting: {}", repo.rev)); - std::string ret = execAndGet("git -C " + m_szWorkingPluginDirectory + " reset --hard --recurse-submodules " + repo.rev); + std::string ret = execAndGet("git -C " + m_szWorkingPluginDirectory + " reset --hard --recurse-submodules \'" + repo.rev + "\'"); if (ret.compare(0, 6, "fatal:") == 0) { std::println(stderr, "\n{}", failureString("could not check out revision {}: shell returned:\n{}", repo.rev, ret)); @@ -741,8 +767,14 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) { progress.printMessageAbove(infoString("Building {}", p.name)); 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"; + const auto CMD_RAW = nixDevelopIfNeeded(std::format("cd {} && PKG_CONFIG_PATH=\"{}\" {}", m_szWorkingPluginDirectory, getPkgConfigPath(), bs), HLVER); + + if (!CMD_RAW) { + progress.printMessageAbove(failureString("Failed to build {}: {}", p.name, CMD_RAW.error())); + break; + } + + out += " -> " + *CMD_RAW + "\n" + execAndGet(*CMD_RAW) + "\n"; } if (m_bVerbose) @@ -772,8 +804,8 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) { repohash.pop_back(); newrepo.hash = repohash; for (auto const& p : pManifest->m_plugins) { - 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}); + const auto OLDPLUGINIT = std::ranges::find_if(repo.plugins, [&](const auto& other) { return other.name == p.name; }); + newrepo.plugins.emplace_back(SPlugin{p.name, m_szWorkingPluginDirectory + "/" + p.output, OLDPLUGINIT != repo.plugins.end() ? OLDPLUGINIT->enabled : false}); } DataState::removePluginRepo(SPluginRepoIdentifier::fromName(newrepo.name)); DataState::addNewPluginRepo(newrepo); @@ -899,7 +931,7 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState(bool forceReload) if (!p.enabled) continue; - if (!forceReload && std::find_if(loadedPlugins.begin(), loadedPlugins.end(), [&](const auto& other) { return other == p.name; }) != loadedPlugins.end()) + if (!forceReload && std::ranges::find_if(loadedPlugins, [&](const auto& other) { return other == p.name; }) != loadedPlugins.end()) continue; if (!loadUnloadPlugin(HYPRPMPATH / repoForName(p.name) / p.filename, true)) { @@ -987,8 +1019,11 @@ std::string CPluginManager::headerErrorShort(const eHeadersErrors err) { } bool CPluginManager::hasDeps() { + if (!m_bNoNix && getHyprlandVersion().isNix) + return true; // dep check not needed if we are on nix + bool hasAllDeps = true; - std::vector deps = {"meson", "cpio", "cmake", "pkg-config", "g++", "gcc", "git"}; + std::vector deps = {"cpio", "cmake", "pkg-config", "g++", "gcc", "git"}; for (auto const& d : deps) { if (!execAndGet("command -v " + d).contains("/")) { @@ -999,3 +1034,92 @@ bool CPluginManager::hasDeps() { return hasAllDeps; } + +const std::string& CPluginManager::getPkgConfigPath() { + static const auto str = std::format("{}/share/pkgconfig:$PKG_CONFIG_PATH", DataState::getHeadersPath()); + return str; +} + +static std::expected getNixDevelopFromPath(const std::string& argv0) { + std::string fullStorePath; + + if (argv0.starts_with("/")) { + // we can use this directly + fullStorePath = argv0; + } else { + // use hyprpm, find in path + auto exe = NSys::findInPath("hyprpm"); + if (!exe) + return std::unexpected("hyprpm not found in PATH"); + + fullStorePath = *exe; + } + + if (fullStorePath.empty() || !fullStorePath.ends_with("/bin/hyprpm")) + return std::unexpected("couldn't get a real path for hyprpm (1)"); + + // canonicalize to get the real nix-store path + std::error_code ec; + fullStorePath = std::filesystem::canonical(fullStorePath, ec); + + if (ec || fullStorePath.empty() || !fullStorePath.starts_with("/nix")) + return std::unexpected("couldn't get a real path for hyprpm"); + + fullStorePath = fullStorePath.substr(0, fullStorePath.length() - std::string_view{"/bin/hyprpm"}.length()); + + auto deriver = trim(execAndGet(std::format("echo \"$(nix-store --query --deriver '{}')\"", fullStorePath))); + + if (deriver.starts_with("unknown")) + return std::unexpected("couldn't nix deriver"); + + return deriver; +} + +static std::expected getNixDevelopFromProfile() { + const auto NIX_PROFILE_STR = execAndGet("nix profile list --json"); + + auto rawJson = glz::read_json(NIX_PROFILE_STR); + + if (!rawJson) + return std::unexpected("failed to parse nix profile list --json"); + + auto& json = *rawJson; + + if (!json.contains("elements") || !json["elements"].is_object()) + return std::unexpected("nix profile list --json returned a wonky json"); + + if (!json["elements"].contains("hyprland") && !json["elements"].contains("Hyprland")) + return std::unexpected("nix profile list --json doesn't contain Hyprland (did you uninstall?)"); + + auto& hyprlandJson = json["elements"].contains("hyprland") ? json["elements"]["hyprland"] : json["elements"]["Hyprland"]; + + if (!hyprlandJson.contains("originalUrl")) + return std::unexpected("nix profile list --json's hyprland doesn't contain originalUrl?"); + + return hyprlandJson["originalUrl"].get_string(); +} + +std::expected CPluginManager::nixDevelopIfNeeded(const std::string& cmd, const SHyprlandVersion& ver) { + if (m_bNoNix || !ver.isNix) + return cmd; + + // Escape single quotes + std::string newCmd = cmd; + replaceInString(newCmd, "'", "\\'"); + + auto NIX_DEVELOP = getNixDevelopFromPath(m_szArgv0); + + if (NIX_DEVELOP) + return std::format("nix develop '{}' --command bash -c $'{}'", *NIX_DEVELOP, newCmd); + else if (m_bVerbose) + std::println("{}", verboseString("Failed nix from path: {}", NIX_DEVELOP.error())); + + NIX_DEVELOP = getNixDevelopFromProfile(); + + if (NIX_DEVELOP) + return std::format("nix develop '{}' --command bash -c $'{}'", *NIX_DEVELOP, newCmd); + else if (m_bVerbose) + std::println("{}", verboseString("Failed nix from profile: {}", NIX_DEVELOP.error())); + + return std::unexpected("hyprland is nix, but hyprpm failed to obtain a nix develop shell for build cmd"); +} diff --git a/hyprpm/src/core/PluginManager.hpp b/hyprpm/src/core/PluginManager.hpp index 10a71469..25878f54 100644 --- a/hyprpm/src/core/PluginManager.hpp +++ b/hyprpm/src/core/PluginManager.hpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include "Plugin.hpp" enum eHeadersErrors { @@ -41,6 +41,7 @@ struct SHyprlandVersion { std::string date; std::string abiHash; int commits = 0; + bool isNix = false; }; class CPluginManager { @@ -65,20 +66,26 @@ class CPluginManager { void notify(const eNotifyIcons icon, uint32_t color, int durationMs, const std::string& message); + const std::string& getPkgConfigPath(); + bool hasDeps(); bool m_bVerbose = false; bool m_bNoShallow = false; - std::string m_szCustomHlUrl, m_szUsername; + bool m_bNoNix = false; + std::string m_szCustomHlUrl, m_szUsername, m_szArgv0; // will delete recursively if exists!! bool createSafeDirectory(const std::string& path); private: - std::string headerError(const eHeadersErrors err); - std::string headerErrorShort(const eHeadersErrors err); + std::string headerError(const eHeadersErrors err); + std::string headerErrorShort(const eHeadersErrors err); + bool validArg(const std::string& s); - std::string m_szWorkingPluginDirectory; + std::expected nixDevelopIfNeeded(const std::string& cmd, const SHyprlandVersion& ver); + + std::string m_szWorkingPluginDirectory; }; inline std::unique_ptr g_pPluginManager; diff --git a/hyprpm/src/helpers/Sys.cpp b/hyprpm/src/helpers/Sys.cpp index c18d4748..e9dd4c85 100644 --- a/hyprpm/src/helpers/Sys.cpp +++ b/hyprpm/src/helpers/Sys.cpp @@ -35,9 +35,13 @@ static std::string validSubinsAsStr() { } static bool executableExistsInPath(const std::string& exe) { + return NSys::findInPath(exe).has_value(); +} + +std::optional NSys::findInPath(const std::string& exe) { const char* PATHENV = std::getenv("PATH"); if (!PATHENV) - return false; + return std::nullopt; CVarList paths(PATHENV, 0, ':', true); std::error_code ec; @@ -52,10 +56,10 @@ static bool executableExistsInPath(const std::string& exe) { if (ec) continue; if ((perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none) - return true; + return candidate.string(); } - return false; + return std::nullopt; } static std::string subin() { diff --git a/hyprpm/src/helpers/Sys.hpp b/hyprpm/src/helpers/Sys.hpp index b44eb758..03d5a0de 100644 --- a/hyprpm/src/helpers/Sys.hpp +++ b/hyprpm/src/helpers/Sys.hpp @@ -1,11 +1,13 @@ #pragma once #include +#include namespace NSys { - bool isSuperuser(); - int getUID(); - int getEUID(); + bool isSuperuser(); + int getUID(); + int getEUID(); + std::optional findInPath(const std::string& exe); // NOLINTNEXTLINE namespace root { @@ -20,4 +22,4 @@ namespace NSys { // Do not use this unless absolutely necessary! std::string runAsSuperuserUnsafe(const std::string& cmd); }; -}; \ No newline at end of file +}; diff --git a/hyprpm/src/main.cpp b/hyprpm/src/main.cpp index dced58e7..f5e14bbb 100644 --- a/hyprpm/src/main.cpp +++ b/hyprpm/src/main.cpp @@ -25,6 +25,7 @@ constexpr std::string_view HELP = R"#(┏ hyprpm, a Hyprland Plugin Manager ┃ ┣ Flags: ┃ +┣ --no-nix | → Disable `nix develop` for build commands, even if Hyprland is nix. ┣ --notify | -n → Send a hyprland notification confirming successful plugin load. ┃ Warnings/Errors trigger notifications regardless of this flag. ┣ --help | -h → Show this menu. @@ -47,7 +48,7 @@ int main(int argc, char** argv, char** envp) { } std::vector command; - bool notify = false, verbose = false, force = false, noShallow = false; + bool notify = false, verbose = false, force = false, noShallow = false, noNix = false; std::string customHlUrl; for (int i = 1; i < argc; ++i) { @@ -63,6 +64,8 @@ int main(int argc, char** argv, char** envp) { g_pPluginManager->notify(ICON_INFO, 0, 10000, "[hyprpm] -n flag is deprecated, see hyprpm --help."); } else if (ARGS[i] == "--verbose" || ARGS[i] == "-v") { verbose = true; + } else if (ARGS[i] == "--no-nix") { + noNix = true; } else if (ARGS[i] == "--no-shallow" || ARGS[i] == "-s") { noShallow = true; } else if (ARGS[i] == "--hl-url") { @@ -91,7 +94,9 @@ int main(int argc, char** argv, char** envp) { g_pPluginManager = std::make_unique(); g_pPluginManager->m_bVerbose = verbose; g_pPluginManager->m_bNoShallow = noShallow; + g_pPluginManager->m_bNoNix = noNix; g_pPluginManager->m_szCustomHlUrl = customHlUrl; + g_pPluginManager->m_szArgv0 = argv[0]; if (command[0] == "add") { if (command.size() < 2) { diff --git a/hyprtester/CMakeLists.txt b/hyprtester/CMakeLists.txt index d771c658..f17f73b1 100644 --- a/hyprtester/CMakeLists.txt +++ b/hyprtester/CMakeLists.txt @@ -96,7 +96,9 @@ endfunction() protocolnew("staging/pointer-warp" "pointer-warp-v1" false) protocolnew("stable/xdg-shell" "xdg-shell" false) +protocolnew("unstable/keyboard-shortcuts-inhibit" "keyboard-shortcuts-inhibit-unstable-v1" false) clientNew("pointer-warp" PROTOS "pointer-warp-v1" "xdg-shell") clientNew("pointer-scroll" PROTOS "xdg-shell") -clientNew("child-window" PROTOS "xdg-shell") \ No newline at end of file +clientNew("child-window" PROTOS "xdg-shell") +clientNew("shortcut-inhibitor" PROTOS "xdg-shell" "keyboard-shortcuts-inhibit-unstable-v1") diff --git a/hyprtester/clients/child-window.cpp b/hyprtester/clients/child-window.cpp index 30bc3fe1..5f66be6b 100644 --- a/hyprtester/clients/child-window.cpp +++ b/hyprtester/clients/child-window.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -332,4 +333,4 @@ int main(int argc, char** argv) { wl_display_disconnect(display); return 0; -} \ No newline at end of file +} diff --git a/hyprtester/clients/pointer-scroll.cpp b/hyprtester/clients/pointer-scroll.cpp index 140e4700..59120961 100644 --- a/hyprtester/clients/pointer-scroll.cpp +++ b/hyprtester/clients/pointer-scroll.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include diff --git a/hyprtester/clients/pointer-warp.cpp b/hyprtester/clients/pointer-warp.cpp index 2d3624d5..a57f99ae 100644 --- a/hyprtester/clients/pointer-warp.cpp +++ b/hyprtester/clients/pointer-warp.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include diff --git a/hyprtester/clients/shortcut-inhibitor.cpp b/hyprtester/clients/shortcut-inhibitor.cpp new file mode 100644 index 00000000..0c6b4341 --- /dev/null +++ b/hyprtester/clients/shortcut-inhibitor.cpp @@ -0,0 +1,297 @@ +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +using Hyprutils::Math::Vector2D; +using namespace Hyprutils::Memory; + +struct SWlState { + wl_display* display; + CSharedPointer registry; + + // protocols + CSharedPointer wlCompositor; + CSharedPointer wlSeat; + CSharedPointer wlShm; + CSharedPointer xdgShell; + CSharedPointer inhibitManager; + + // shm/buffer stuff + CSharedPointer shmPool; + CSharedPointer shmBuf; + int shmFd; + size_t shmBufSize; + bool xrgb8888_support = false; + + // surface/toplevel stuff + CSharedPointer surf; + CSharedPointer xdgSurf; + CSharedPointer xdgToplevel; + Vector2D geom; + + // pointer + CSharedPointer pointer; + uint32_t enterSerial; + + // shortcut inhibiting + CSharedPointer inhibitor; +}; + +static bool debug, started, shouldExit; + +template +//NOLINTNEXTLINE +static void clientLog(std::format_string fmt, Args&&... args) { + std::string text = std::vformat(fmt.get(), std::make_format_args(args...)); + std::println("{}", text); + std::fflush(stdout); +} + +template +//NOLINTNEXTLINE +static void debugLog(std::format_string fmt, Args&&... args) { + std::string text = std::vformat(fmt.get(), std::make_format_args(args...)); + if (!debug) + return; + std::println("{}", text); + std::fflush(stdout); +} + +static bool bindRegistry(SWlState& state) { + state.registry = makeShared((wl_proxy*)wl_display_get_registry(state.display)); + + state.registry->setGlobal([&](CCWlRegistry* r, uint32_t id, const char* name, uint32_t version) { + const std::string NAME = name; + if (NAME == "wl_compositor") { + debugLog(" > binding to global: {} (version {}) with id {}", name, version, id); + state.wlCompositor = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_compositor_interface, 6)); + } else if (NAME == "wl_shm") { + debugLog(" > binding to global: {} (version {}) with id {}", name, version, id); + state.wlShm = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_shm_interface, 1)); + } else if (NAME == "wl_seat") { + debugLog(" > binding to global: {} (version {}) with id {}", name, version, id); + state.wlSeat = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &wl_seat_interface, 9)); + } else if (NAME == "xdg_wm_base") { + debugLog(" > binding to global: {} (version {}) with id {}", name, version, id); + state.xdgShell = makeShared((wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &xdg_wm_base_interface, 1)); + } else if (NAME == "zwp_keyboard_shortcuts_inhibit_manager_v1") { + debugLog(" > binding to global: {} (version {}) with id {}", name, version, id); + state.inhibitManager = makeShared( + (wl_proxy*)wl_registry_bind((wl_registry*)state.registry->resource(), id, &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1)); + } + }); + state.registry->setGlobalRemove([](CCWlRegistry* r, uint32_t id) { debugLog("Global {} removed", id); }); + + wl_display_roundtrip(state.display); + + if (!state.wlCompositor || !state.wlShm || !state.wlSeat || !state.xdgShell || !state.inhibitManager) { + clientLog("Failed to get protocols from Hyprland"); + return false; + } + + return true; +} + +static bool createShm(SWlState& state, Vector2D geom) { + if (!state.xrgb8888_support) + return false; + + size_t stride = geom.x * 4; + size_t size = geom.y * stride; + if (!state.shmPool) { + const char* name = "/wl-shm-shortcut-inhibitor"; + state.shmFd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + if (state.shmFd < 0) + return false; + + if (shm_unlink(name) < 0 || ftruncate(state.shmFd, size) < 0) { + close(state.shmFd); + return false; + } + + state.shmPool = makeShared(state.wlShm->sendCreatePool(state.shmFd, size)); + if (!state.shmPool->resource()) { + close(state.shmFd); + state.shmFd = -1; + state.shmPool.reset(); + return false; + } + state.shmBufSize = size; + } else if (size > state.shmBufSize) { + if (ftruncate(state.shmFd, size) < 0) { + close(state.shmFd); + state.shmFd = -1; + state.shmPool.reset(); + return false; + } + + state.shmPool->sendResize(size); + state.shmBufSize = size; + } + + auto buf = makeShared(state.shmPool->sendCreateBuffer(0, geom.x, geom.y, stride, WL_SHM_FORMAT_XRGB8888)); + if (!buf->resource()) + return false; + + if (state.shmBuf) { + state.shmBuf->sendDestroy(); + state.shmBuf.reset(); + } + + state.shmBuf = buf; + + return true; +} + +static bool setupToplevel(SWlState& state) { + state.wlShm->setFormat([&](CCWlShm* p, uint32_t format) { + if (format == WL_SHM_FORMAT_XRGB8888) + state.xrgb8888_support = true; + }); + + state.xdgShell->setPing([&](CCXdgWmBase* p, uint32_t serial) { state.xdgShell->sendPong(serial); }); + + state.surf = makeShared(state.wlCompositor->sendCreateSurface()); + if (!state.surf->resource()) + return false; + + state.xdgSurf = makeShared(state.xdgShell->sendGetXdgSurface(state.surf->resource())); + if (!state.xdgSurf->resource()) + return false; + + state.xdgToplevel = makeShared(state.xdgSurf->sendGetToplevel()); + if (!state.xdgToplevel->resource()) + return false; + + state.xdgToplevel->setClose([&](CCXdgToplevel* p) { exit(0); }); + + state.xdgToplevel->setConfigure([&](CCXdgToplevel* p, int32_t w, int32_t h, wl_array* arr) { + state.geom = {1280, 720}; + + if (!createShm(state, state.geom)) + exit(-1); + }); + + state.xdgSurf->setConfigure([&](CCXdgSurface* p, uint32_t serial) { + if (!state.shmBuf) + debugLog("xdgSurf configure but no buf made yet?"); + + state.xdgSurf->sendSetWindowGeometry(0, 0, state.geom.x, state.geom.y); + state.surf->sendAttach(state.shmBuf.get(), 0, 0); + state.surf->sendCommit(); + + state.xdgSurf->sendAckConfigure(serial); + + if (!started) { + started = true; + clientLog("started"); + } + }); + + state.xdgToplevel->sendSetTitle("shortcut-inhibitor test client"); + state.xdgToplevel->sendSetAppId("shortcut-inhibitor"); + + state.surf->sendAttach(nullptr, 0, 0); + state.surf->sendCommit(); + + return true; +} + +static bool setupSeat(SWlState& state) { + state.pointer = makeShared(state.wlSeat->sendGetPointer()); + if (!state.pointer->resource()) + return false; + + state.pointer->setEnter([&](CCWlPointer* p, uint32_t serial, wl_proxy* surf, wl_fixed_t x, wl_fixed_t y) { + debugLog("Got pointer enter event, serial {}, x {}, y {}", serial, x, y); + state.enterSerial = serial; + }); + + state.pointer->setLeave([&](CCWlPointer* p, uint32_t serial, wl_proxy* surf) { debugLog("Got pointer leave event, serial {}", serial); }); + + state.pointer->setMotion([&](CCWlPointer* p, uint32_t serial, wl_fixed_t x, wl_fixed_t y) { debugLog("Got pointer motion event, serial {}, x {}, y {}", serial, x, y); }); + + return true; +} + +static void parseRequest(SWlState& state, std::string req) { + if (req.starts_with("on")) { + state.inhibitor = makeShared(state.inhibitManager->sendInhibitShortcuts(state.surf->resource(), state.wlSeat->resource())); + + state.inhibitor->setActive([&](CCZwpKeyboardShortcutsInhibitorV1* inhibitorV1) { clientLog("inhibiting"); }); + state.inhibitor->setInactive([&](CCZwpKeyboardShortcutsInhibitorV1* inhibitorV1) { clientLog("inhibit disabled by compositor"); }); + } else if (req.starts_with("off")) { + state.inhibitor->sendDestroy(); + state.inhibitor.reset(); + shouldExit = true; + clientLog("inhibit disabled by request"); + } +} + +int main(int argc, char** argv) { + if (argc != 1 && argc != 2) + clientLog("Only the \"--debug\" switch is allowed, it turns on debug logs."); + + if (argc == 2 && std::string{argv[1]} == "--debug") + debug = true; + + SWlState state; + + // WAYLAND_DISPLAY env should be set to the correct one + state.display = wl_display_connect(nullptr); + if (!state.display) { + clientLog("Failed to connect to wayland display"); + return -1; + } + + if (!bindRegistry(state) || !setupSeat(state) || !setupToplevel(state)) + return -1; + + std::array readBuf; + readBuf.fill(0); + + wl_display_flush(state.display); + + struct pollfd fds[2] = {{.fd = wl_display_get_fd(state.display), .events = POLLIN | POLLOUT}, {.fd = STDIN_FILENO, .events = POLLIN}}; + while (!shouldExit && poll(fds, 2, 0) != -1) { + if (fds[0].revents & POLLIN) { + wl_display_flush(state.display); + + if (wl_display_prepare_read(state.display) == 0) { + wl_display_read_events(state.display); + wl_display_dispatch_pending(state.display); + } else + wl_display_dispatch(state.display); + + int ret = 0; + do { + ret = wl_display_dispatch_pending(state.display); + wl_display_flush(state.display); + } while (ret > 0); + } + + if (fds[1].revents & POLLIN) { + ssize_t bytesRead = read(fds[1].fd, readBuf.data(), 1023); + if (bytesRead == -1) + continue; + readBuf[bytesRead] = 0; + + parseRequest(state, std::string{readBuf.data()}); + } + } + + wl_display* display = state.display; + state = {}; + + wl_display_disconnect(display); + return 0; +} diff --git a/hyprtester/plugin/src/main.cpp b/hyprtester/plugin/src/main.cpp index d8f3c971..ce83c5b4 100644 --- a/hyprtester/plugin/src/main.cpp +++ b/hyprtester/plugin/src/main.cpp @@ -6,15 +6,16 @@ #define private public #include #include -#include -#include #include #include #include #include +#include #include +#include #include #include +#include #undef private #include @@ -53,8 +54,9 @@ static SDispatchResult snapMove(std::string in) { Vector2D pos = PLASTWINDOW->m_realPosition->goal(); Vector2D size = PLASTWINDOW->m_realSize->goal(); - g_pLayoutManager->getCurrentLayout()->performSnap(pos, size, PLASTWINDOW, MBIND_MOVE, -1, size); - *PLASTWINDOW->m_realPosition = pos.round(); + g_layoutManager->performSnap(pos, size, PLASTWINDOW->layoutTarget(), MBIND_MOVE, -1, size); + + PLASTWINDOW->layoutTarget()->setPositionGlobal(CBox{pos, size}); return {}; } @@ -270,32 +272,85 @@ static SDispatchResult keybind(std::string in) { return {}; } -static Desktop::Rule::CWindowRuleEffectContainer::storageType ruleIDX = 0; +static Desktop::Rule::CWindowRuleEffectContainer::storageType windowRuleIDX = 0; // -static SDispatchResult addRule(std::string in) { - ruleIDX = Desktop::Rule::windowEffects()->registerEffect("plugin_rule"); +static SDispatchResult addWindowRule(std::string in) { + windowRuleIDX = Desktop::Rule::windowEffects()->registerEffect("plugin_rule"); - if (Desktop::Rule::windowEffects()->registerEffect("plugin_rule") != ruleIDX) + if (Desktop::Rule::windowEffects()->registerEffect("plugin_rule") != windowRuleIDX) return {.success = false, .error = "re-registering returned a different id?"}; return {}; } -static SDispatchResult checkRule(std::string in) { +static SDispatchResult checkWindowRule(std::string in) { const auto PLASTWINDOW = Desktop::focusState()->window(); if (!PLASTWINDOW) return {.success = false, .error = "No window"}; - if (!PLASTWINDOW->m_ruleApplicator->m_otherProps.props.contains(ruleIDX)) + if (!PLASTWINDOW->m_ruleApplicator->m_otherProps.props.contains(windowRuleIDX)) return {.success = false, .error = "No rule"}; - if (PLASTWINDOW->m_ruleApplicator->m_otherProps.props[ruleIDX]->effect != "effect") + if (PLASTWINDOW->m_ruleApplicator->m_otherProps.props[windowRuleIDX]->effect != "effect") return {.success = false, .error = "Effect isn't \"effect\""}; return {}; } +static Desktop::Rule::CLayerRuleEffectContainer::storageType layerRuleIDX = 0; + +static SDispatchResult addLayerRule(std::string in) { + layerRuleIDX = Desktop::Rule::layerEffects()->registerEffect("plugin_rule"); + + if (Desktop::Rule::layerEffects()->registerEffect("plugin_rule") != layerRuleIDX) + return {.success = false, .error = "re-registering returned a different id?"}; + return {}; +} + +static SDispatchResult checkLayerRule(std::string in) { + if (g_pCompositor->m_layers.size() != 3) + return {.success = false, .error = "Layers under test not here"}; + + for (const auto& layer : g_pCompositor->m_layers) { + if (layer->m_namespace == "rule-layer") { + + if (!layer->m_ruleApplicator->m_otherProps.props.contains(layerRuleIDX)) + return {.success = false, .error = "No rule"}; + + if (layer->m_ruleApplicator->m_otherProps.props[layerRuleIDX]->effect != "effect") + return {.success = false, .error = "Effect isn't \"effect\""}; + + } else if (layer->m_namespace == "norule-layer") { + + if (layer->m_ruleApplicator->m_otherProps.props.contains(layerRuleIDX)) + return {.success = false, .error = "Rule even though it shouldn't"}; + + } else + return {.success = false, .error = "Unrecognized layer"}; + } + + return {}; +} + +static SDispatchResult floatingFocusOnFullscreen(std::string in) { + const auto PLASTWINDOW = Desktop::focusState()->window(); + + if (!PLASTWINDOW) + return {.success = false, .error = "No window"}; + + if (!PLASTWINDOW->m_isFloating) + return {.success = false, .error = "Window must be floating"}; + + if (PLASTWINDOW->m_alpha != 1.f) + return {.success = false, .error = "floating window doesnt restore it opacity when focused on fullscreen workspace"}; + + if (!PLASTWINDOW->m_createdOverFullscreen) + return {.success = false, .error = "floating window doesnt get flagged as createdOverFullscreen"}; + + return {}; +} + APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { PHANDLE = handle; @@ -307,8 +362,11 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:scroll", ::scroll); HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:click", ::click); HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:keybind", ::keybind); - HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_rule", ::addRule); - HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_rule", ::checkRule); + HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_window_rule", ::addWindowRule); + HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_window_rule", ::checkWindowRule); + HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_layer_rule", ::addLayerRule); + HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_layer_rule", ::checkLayerRule); + HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:floating_focus_on_fullscreen", ::floatingFocusOnFullscreen); // init mouse g_mouse = CTestMouse::create(false); diff --git a/hyprtester/src/shared.hpp b/hyprtester/src/shared.hpp index 1090aa9a..941788fd 100644 --- a/hyprtester/src/shared.hpp +++ b/hyprtester/src/shared.hpp @@ -39,6 +39,16 @@ namespace Colors { TESTS_PASSED++; \ } +#define EXPECT_NOT(expr, val) \ + if (const auto RESULT = expr; RESULT == (val)) { \ + NLog::log("{}Failed: {}{}, expected not {}, got {}. Source: {}@{}.", Colors::RED, Colors::RESET, #expr, val, RESULT, __FILE__, __LINE__); \ + ret = 1; \ + TESTS_FAILED++; \ + } else { \ + NLog::log("{}Passed: {}{}. Got {}", Colors::GREEN, Colors::RESET, #expr, val); \ + TESTS_PASSED++; \ + } + #define EXPECT_VECTOR2D(expr, val) \ do { \ const auto& RESULT = expr; \ diff --git a/hyprtester/src/tests/clients/child-window.cpp b/hyprtester/src/tests/clients/child-window.cpp index 1740b029..a5680d4f 100644 --- a/hyprtester/src/tests/clients/child-window.cpp +++ b/hyprtester/src/tests/clients/child-window.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -117,7 +118,34 @@ static bool test() { Tests::killAllWindows(); EXPECT(Tests::windowCount(), 0); + // test that child windows (shouldBeFloated) are not auto-grouped + NLog::log("{}Test child windows are not auto-grouped", Colors::GREEN); + auto kitty = Tests::spawnKitty(); + if (!kitty) { + NLog::log("{}Error: kitty did not spawn", Colors::RED); + return false; + } + + // create group and enable auto-grouping + OK(getFromSocket("/dispatch togglegroup")); + OK(getFromSocket("/keyword group:auto_group true")); + + SClient client2; + if (!startClient(client2)) + return false; + + EXPECT(Tests::windowCount(), 2); + createChild(client2); + EXPECT(Tests::windowCount(), 3); + + // child has set_parent so shouldBeFloated returns true, it should not be auto-grouped + EXPECT_COUNT_STRING(getFromSocket("/clients"), "grouped: 0", 1); + + stopClient(client2); + Tests::killAllWindows(); + EXPECT(Tests::windowCount(), 0); + return !ret; } -REGISTER_CLIENT_TEST_FN(test); \ No newline at end of file +REGISTER_CLIENT_TEST_FN(test); diff --git a/hyprtester/src/tests/clients/pointer-scroll.cpp b/hyprtester/src/tests/clients/pointer-scroll.cpp index 2ea93a14..b5fb68fb 100644 --- a/hyprtester/src/tests/clients/pointer-scroll.cpp +++ b/hyprtester/src/tests/clients/pointer-scroll.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -42,6 +43,7 @@ static bool startClient(SClient& client) { client.readFd = CFileDescriptor(pipeFds2[0]); client.proc->setStdoutFD(pipeFds2[1]); + const int COUNT_BEFORE = Tests::windowCount(); client.proc->runAsync(); close(pipeFds1[0]); @@ -62,7 +64,16 @@ static bool startClient(SClient& client) { } // wait for window to appear - std::this_thread::sleep_for(std::chrono::milliseconds(5000)); + int counter = 0; + while (Tests::processAlive(client.proc->pid()) && Tests::windowCount() == COUNT_BEFORE) { + counter++; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + if (counter > 50) { + NLog::log("{}pointer-scroll client took too long to open", Colors::RED); + return false; + } + } if (getFromSocket(std::format("/dispatch setprop pid:{} no_anim 1", client.proc->pid())) != "ok") { NLog::log("{}Failed to disable animations for client window", Colors::RED, ret); diff --git a/hyprtester/src/tests/clients/pointer-warp.cpp b/hyprtester/src/tests/clients/pointer-warp.cpp index bb03afd2..be992566 100644 --- a/hyprtester/src/tests/clients/pointer-warp.cpp +++ b/hyprtester/src/tests/clients/pointer-warp.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -42,6 +43,7 @@ static bool startClient(SClient& client) { client.readFd = CFileDescriptor(pipeFds2[0]); client.proc->setStdoutFD(pipeFds2[1]); + const int COUNT_BEFORE = Tests::windowCount(); client.proc->runAsync(); close(pipeFds1[0]); @@ -62,7 +64,16 @@ static bool startClient(SClient& client) { } // wait for window to appear - std::this_thread::sleep_for(std::chrono::milliseconds(5000)); + int counter = 0; + while (Tests::processAlive(client.proc->pid()) && Tests::windowCount() == COUNT_BEFORE) { + counter++; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + if (counter > 50) { + NLog::log("{}pointer-warp client took too long to open", Colors::RED); + return false; + } + } if (getFromSocket(std::format("/dispatch setprop pid:{} no_anim 1", client.proc->pid())) != "ok") { NLog::log("{}Failed to disable animations for client window", Colors::RED, ret); diff --git a/hyprtester/src/tests/clients/shortcut-inhibitor.cpp b/hyprtester/src/tests/clients/shortcut-inhibitor.cpp new file mode 100644 index 00000000..91c3376c --- /dev/null +++ b/hyprtester/src/tests/clients/shortcut-inhibitor.cpp @@ -0,0 +1,180 @@ +#include "../../shared.hpp" +#include "../../hyprctlCompat.hpp" +#include "../shared.hpp" +#include "tests.hpp" +#include "build.hpp" + +#include +#include + +#include +#include +#include +#include + +using namespace Hyprutils::OS; +using namespace Hyprutils::Memory; + +#define SP CSharedPointer + +struct SClient { + SP proc; + std::array readBuf; + CFileDescriptor readFd, writeFd; + struct pollfd fds; +}; + +static int ret = 0; + +static bool startClient(SClient& client) { + Tests::killAllWindows(); + client.proc = makeShared(binaryDir + "/shortcut-inhibitor", std::vector{}); + + client.proc->addEnv("WAYLAND_DISPLAY", WLDISPLAY); + + int pipeFds1[2], pipeFds2[2]; + if (pipe(pipeFds1) != 0 || pipe(pipeFds2) != 0) { + NLog::log("{}Unable to open pipe to client", Colors::RED); + return false; + } + + client.writeFd = CFileDescriptor(pipeFds1[1]); + client.proc->setStdinFD(pipeFds1[0]); + + client.readFd = CFileDescriptor(pipeFds2[0]); + client.proc->setStdoutFD(pipeFds2[1]); + + const int COUNT_BEFORE = Tests::windowCount(); + client.proc->runAsync(); + + close(pipeFds1[0]); + close(pipeFds2[1]); + + client.fds = {.fd = client.readFd.get(), .events = POLLIN}; + if (poll(&client.fds, 1, 1000) != 1 || !(client.fds.revents & POLLIN)) { + NLog::log("{}shortcut-inhibitor client failed poll", Colors::RED); + return false; + } + + client.readBuf.fill(0); + if (read(client.readFd.get(), client.readBuf.data(), client.readBuf.size() - 1) == -1) { + NLog::log("{}shortcut-inhibitor client read failed", Colors::RED); + return false; + } + + std::string ret = std::string{client.readBuf.data()}; + if (ret.find("started") == std::string::npos) { + NLog::log("{}Failed to start shortcut-inhibitor client, read {}", Colors::RED, ret); + return false; + } + + // wait for window to appear + int counter = 0; + while (Tests::processAlive(client.proc->pid()) && Tests::windowCount() == COUNT_BEFORE) { + counter++; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + if (counter > 50) { + NLog::log("{}shortcut-inhibitor client took too long to open", Colors::RED); + return false; + } + } + + if (!Tests::processAlive(client.proc->pid())) { + NLog::log("{}shortcut-inhibitor client not alive", Colors::RED); + return false; + } + + if (getFromSocket(std::format("/dispatch focuswindow pid:{}", client.proc->pid())) != "ok") { + NLog::log("{}Failed to focus shortcut-inhibitor client", Colors::RED, ret); + return false; + } + + std::string command = "on\n"; + if (write(client.writeFd.get(), command.c_str(), command.length()) == -1) { + NLog::log("{}shortcut-inhibitor client write failed", Colors::RED); + return false; + } + + client.readBuf.fill(0); + if (read(client.readFd.get(), client.readBuf.data(), client.readBuf.size() - 1) == -1) + return false; + + ret = std::string{client.readBuf.data()}; + if (ret.find("inhibiting") == std::string::npos) { + NLog::log("{}shortcut-inhibitor client didn't return inhibiting", Colors::RED); + return false; + } + + NLog::log("{}Started shortcut-inhibitor client", Colors::YELLOW); + + return true; +} + +static void stopClient(SClient& client) { + std::string cmd = "off\n"; + write(client.writeFd.get(), cmd.c_str(), cmd.length()); + + kill(client.proc->pid(), SIGKILL); + client.proc.reset(); +} + +static std::string flagFile = "/tmp/hyprtester-keybinds.txt"; + +static bool checkFlag() { + bool exists = std::filesystem::exists(flagFile); + std::filesystem::remove(flagFile); + return exists; +} + +static bool attemptCheckFlag(int attempts, int intervalMs) { + for (int i = 0; i < attempts; i++) { + if (checkFlag()) + return true; + + std::this_thread::sleep_for(std::chrono::milliseconds(intervalMs)); + } + + return false; +} + +static bool test() { + SClient client; + if (!startClient(client)) + return false; + + NLog::log("{}Testing keybinds", Colors::GREEN); + //basic keybind test + EXPECT(checkFlag(), false); + EXPECT(getFromSocket("/keyword bind SUPER,Y,exec,touch " + flagFile), "ok"); + OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29")); + EXPECT(attemptCheckFlag(20, 50), false); + OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29")); + EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok"); + + //keybind bypass flag test + EXPECT(checkFlag(), false); + EXPECT(getFromSocket("/keyword bindp SUPER,Y,exec,touch " + flagFile), "ok"); + OK(getFromSocket("/dispatch plugin:test:keybind 1,7,29")); + EXPECT(attemptCheckFlag(20, 50), true); + OK(getFromSocket("/dispatch plugin:test:keybind 0,0,29")); + EXPECT(getFromSocket("/keyword unbind SUPER,Y"), "ok"); + + NLog::log("{}Testing gestures", Colors::GREEN); + //basic gesture test + OK(getFromSocket("/dispatch plugin:test:gesture right,3")); + EXPECT_NOT_CONTAINS(getFromSocket("/activewindow"), "floating: 1"); + + //gesture bypass flag test + OK(getFromSocket("/dispatch plugin:test:gesture right,2")); + EXPECT_CONTAINS(getFromSocket("/activewindow"), "floating: 1"); + + stopClient(client); + + NLog::log("{}Reloading the config", Colors::YELLOW); + OK(getFromSocket("/reload")); + + return !ret; +} + +REGISTER_CLIENT_TEST_FN(test); diff --git a/hyprtester/src/tests/main/dwindle.cpp b/hyprtester/src/tests/main/dwindle.cpp index 8f17c815..234bfc33 100644 --- a/hyprtester/src/tests/main/dwindle.cpp +++ b/hyprtester/src/tests/main/dwindle.cpp @@ -34,6 +34,197 @@ static void testFloatClamp() { // clean up NLog::log("{}Killing all windows", Colors::YELLOW); Tests::killAllWindows(); + + OK(getFromSocket("/reload")); +} + +static void test13349() { + + // Test if dwindle properly uses a focal point to place a new window. + // exposed by #13349 as a regression from #12890 + + for (auto const& win : {"a", "b", "c"}) { + if (!Tests::spawnKitty(win)) { + NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); + ++TESTS_FAILED; + ret = 1; + return; + } + } + + OK(getFromSocket("/dispatch focuswindow class:c")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 967,547"); + EXPECT_CONTAINS(str, "size: 931,511"); + } + + OK(getFromSocket("/dispatch movewindow l")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 497,22"); + EXPECT_CONTAINS(str, "size: 456,1036"); + } + + OK(getFromSocket("/dispatch movewindow r")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 967,22"); + EXPECT_CONTAINS(str, "size: 456,1036"); + } + + // clean up + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); +} + +static void testSplit() { + // Test various split methods + + Tests::spawnKitty("a"); + + // these must not crash + EXPECT_NOT(getFromSocket("/dispatch layoutmsg swapsplit"), "ok"); + EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio 1 exact"), "ok"); + + Tests::spawnKitty("b"); + + OK(getFromSocket("/dispatch focuswindow class:a")); + OK(getFromSocket("/dispatch layoutmsg splitratio -0.2")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,22"); + EXPECT_CONTAINS(str, "size: 743,1036"); + } + + OK(getFromSocket("/dispatch layoutmsg splitratio 1.6 exact")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,22"); + EXPECT_CONTAINS(str, "size: 1495,1036"); + } + + EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio fhne exact"), "ok"); + EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio exact"), "ok"); + EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio -....9"), "ok"); + EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio ..9"), "ok"); + EXPECT_NOT(getFromSocket("/dispatch layoutmsg splitratio"), "ok"); + + OK(getFromSocket("/dispatch layoutmsg togglesplit")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,22"); + EXPECT_CONTAINS(str, "size: 1876,823"); + } + + OK(getFromSocket("/dispatch layoutmsg swapsplit")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,859"); + EXPECT_CONTAINS(str, "size: 1876,199"); + } + + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); +} + +static void testRotatesplit() { + OK(getFromSocket("r/keyword general:gaps_in 0")); + OK(getFromSocket("r/keyword general:gaps_out 0")); + OK(getFromSocket("r/keyword general:border_size 0")); + + for (auto const& win : {"a", "b"}) { + if (!Tests::spawnKitty(win)) { + NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); + ++TESTS_FAILED; + ret = 1; + return; + } + } + + { + auto str = getFromSocket("/clients"); + EXPECT_CONTAINS(str, "at: 0,0"); + EXPECT_CONTAINS(str, "size: 960,1080"); + } + + // test 4 repeated rotations by 90 degrees + OK(getFromSocket("/dispatch layoutmsg rotatesplit")); + { + auto str = getFromSocket("/clients"); + EXPECT_CONTAINS(str, "at: 0,0"); + EXPECT_CONTAINS(str, "size: 1920,540"); + } + + OK(getFromSocket("/dispatch layoutmsg rotatesplit")); + { + auto str = getFromSocket("/clients"); + EXPECT_CONTAINS(str, "at: 960,0"); + EXPECT_CONTAINS(str, "size: 960,1080"); + } + + OK(getFromSocket("/dispatch layoutmsg rotatesplit")); + { + auto str = getFromSocket("/clients"); + EXPECT_CONTAINS(str, "at: 0,540"); + EXPECT_CONTAINS(str, "size: 1920,540"); + } + + OK(getFromSocket("/dispatch layoutmsg rotatesplit")); + { + auto str = getFromSocket("/clients"); + EXPECT_CONTAINS(str, "at: 0,0"); + EXPECT_CONTAINS(str, "size: 960,1080"); + } + + // test different angles + OK(getFromSocket("/dispatch layoutmsg rotatesplit 180")); + { + auto str = getFromSocket("/clients"); + EXPECT_CONTAINS(str, "at: 960,0"); + EXPECT_CONTAINS(str, "size: 960,1080"); + } + + OK(getFromSocket("/dispatch layoutmsg rotatesplit 270")); + { + auto str = getFromSocket("/clients"); + EXPECT_CONTAINS(str, "at: 0,540"); + EXPECT_CONTAINS(str, "size: 1920,540"); + } + + OK(getFromSocket("/dispatch layoutmsg rotatesplit 360")); + { + auto str = getFromSocket("/clients"); + EXPECT_CONTAINS(str, "at: 0,0"); + EXPECT_CONTAINS(str, "size: 1920,540"); + } + + // test negative angles + OK(getFromSocket("/dispatch layoutmsg rotatesplit -90")); + { + auto str = getFromSocket("/clients"); + EXPECT_CONTAINS(str, "at: 0,0"); + EXPECT_CONTAINS(str, "size: 960,1080"); + } + + OK(getFromSocket("/dispatch layoutmsg rotatesplit -180")); + { + auto str = getFromSocket("/clients"); + EXPECT_CONTAINS(str, "at: 960,0"); + EXPECT_CONTAINS(str, "size: 960,1080"); + } + + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + + OK(getFromSocket("/reload")); } static bool test() { @@ -43,6 +234,15 @@ static bool test() { NLog::log("{}Testing float clamp", Colors::GREEN); testFloatClamp(); + NLog::log("{}Testing #13349", Colors::GREEN); + test13349(); + + NLog::log("{}Testing splits", Colors::GREEN); + testSplit(); + + NLog::log("{}Testing rotatesplit", Colors::GREEN); + testRotatesplit(); + // clean up NLog::log("Cleaning up", Colors::YELLOW); getFromSocket("/dispatch workspace 1"); diff --git a/hyprtester/src/tests/main/exec.cpp b/hyprtester/src/tests/main/exec.cpp index fd42cf06..a410494a 100644 --- a/hyprtester/src/tests/main/exec.cpp +++ b/hyprtester/src/tests/main/exec.cpp @@ -2,6 +2,7 @@ #include "../../shared.hpp" #include "../../hyprctlCompat.hpp" #include +#include #include #include #include @@ -15,40 +16,46 @@ using namespace Hyprutils::Memory; #define UP CUniquePointer #define SP CSharedPointer -static bool test() { +const static auto SLEEP_DURATIONS = std::array{1, 10}; + +static bool test() { NLog::log("{}Testing process spawning", Colors::GREEN); - // Note: POSIX sleep does not support fractional seconds, so - // can't sleep for less than 1 second. - OK(getFromSocket("/dispatch exec sleep 1")); + for (const auto duration : SLEEP_DURATIONS) { + // Note: POSIX sleep does not support fractional seconds, so + // can't sleep for less than 1 second. + OK(getFromSocket(std::format("/dispatch exec sleep {}", duration))); - // Ensure that sleep is our child - const std::string sleepPidS = Tests::execAndGet("pgrep sleep"); - pid_t sleepPid; - try { - sleepPid = std::stoull(sleepPidS); - } catch (...) { - NLog::log("{}Sleep was not spawned or several sleeps are running: pgrep returned '{}'", Colors::RED, sleepPidS); - return false; + // Ensure that sleep is our child + const std::string sleepPidS = Tests::execAndGet("pgrep sleep"); + pid_t sleepPid; + try { + sleepPid = std::stoull(sleepPidS); + } catch (...) { + NLog::log("{}Sleep was not spawned or several sleeps are running: pgrep returned '{}'", Colors::RED, sleepPidS); + continue; + } + + const std::string sleepParentComm = Tests::execAndGet("cat \"/proc/$(ps -o ppid:1= -p " + sleepPidS + ")/comm\""); + NLog::log("{}Expecting that sleep's parent is Hyprland", Colors::YELLOW); + EXPECT_CONTAINS(sleepParentComm, "Hyprland"); + + std::this_thread::sleep_for(std::chrono::seconds(duration)); + + // Ensure that sleep did not become a zombie + EXPECT(Tests::processAlive(sleepPid), false); + + // kill all + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + + NLog::log("{}Expecting 0 windows", Colors::YELLOW); + EXPECT(Tests::windowCount(), 0); + + return !ret; } - const std::string sleepParentComm = Tests::execAndGet("cat \"/proc/$(ps -o ppid:1= -p " + sleepPidS + ")/comm\""); - NLog::log("{}Expecting that sleep's parent is Hyprland", Colors::YELLOW); - EXPECT_CONTAINS(sleepParentComm, "Hyprland"); - - std::this_thread::sleep_for(std::chrono::seconds(1)); - - // Ensure that sleep did not become a zombie - EXPECT(Tests::processAlive(sleepPid), false); - - // kill all - NLog::log("{}Killing all windows", Colors::YELLOW); - Tests::killAllWindows(); - - NLog::log("{}Expecting 0 windows", Colors::YELLOW); - EXPECT(Tests::windowCount(), 0); - - return !ret; + return false; } REGISTER_TEST_FN(test) diff --git a/hyprtester/src/tests/main/groups.cpp b/hyprtester/src/tests/main/groups.cpp index 3cf15851..2f9c5062 100644 --- a/hyprtester/src/tests/main/groups.cpp +++ b/hyprtester/src/tests/main/groups.cpp @@ -127,6 +127,34 @@ static bool test() { ret = 1; } + // test movegroupwindow: focus should follow the moved window + NLog::log("{}Test movegroupwindow focus follows window", Colors::YELLOW); + try { + auto str = getFromSocket("/activewindow"); + auto activeBeforeMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16); + OK(getFromSocket("/dispatch movegroupwindow f")); + str = getFromSocket("/activewindow"); + auto activeAfterMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16); + EXPECT(activeAfterMove, activeBeforeMove); + } catch (...) { + NLog::log("{}Fail at getting prop", Colors::RED); + ret = 1; + } + + // and backwards + NLog::log("{}Test movegroupwindow backwards", Colors::YELLOW); + try { + auto str = getFromSocket("/activewindow"); + auto activeBeforeMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16); + OK(getFromSocket("/dispatch movegroupwindow b")); + str = getFromSocket("/activewindow"); + auto activeAfterMove = std::stoull(str.substr(7, str.find(" -> ") - 7), nullptr, 16); + EXPECT(activeAfterMove, activeBeforeMove); + } catch (...) { + NLog::log("{}Fail at getting prop", Colors::RED); + ret = 1; + } + NLog::log("{}Disable autogrouping", Colors::YELLOW); OK(getFromSocket("/keyword group:auto_group false")); @@ -173,6 +201,99 @@ static bool test() { NLog::log("{}Expecting 0 windows", Colors::YELLOW); EXPECT(Tests::windowCount(), 0); + // test movewindoworgroup: direction should be respected when extracting from group + NLog::log("{}Test movewindoworgroup respects direction out of group", Colors::YELLOW); + OK(getFromSocket("/keyword group:groupbar:enabled 0")); + { + auto kittyE = Tests::spawnKitty(); + if (!kittyE) { + NLog::log("{}Error: kitty did not spawn", Colors::RED); + return false; + } + + // group kitty, and new windows should be auto-grouped + OK(getFromSocket("/dispatch togglegroup")); + + auto kittyF = Tests::spawnKitty(); + if (!kittyF) { + NLog::log("{}Error: kitty did not spawn", Colors::RED); + return false; + } + EXPECT(Tests::windowCount(), 2); + + // both windows should be grouped at the same position + { + auto str = getFromSocket("/clients"); + EXPECT_COUNT_STRING(str, "at: 22,22", 2); + } + + // move active window out of group to the right + NLog::log("{}Test movewindoworgroup r", Colors::YELLOW); + OK(getFromSocket("/dispatch movewindoworgroup r")); + + // the group should stay at x=22, the extracted window should be to the right + { + auto str = getFromSocket("/clients"); + EXPECT_COUNT_STRING(str, "at: 22,22", 1); + } + + // move it back into the group + OK(getFromSocket("/dispatch moveintogroup l")); + + // move active window out of group downward + NLog::log("{}Test movewindoworgroup d", Colors::YELLOW); + OK(getFromSocket("/dispatch movewindoworgroup d")); + + // the group should stay at y=22, the extracted window should be below + { + auto str = getFromSocket("/clients"); + EXPECT_COUNT_STRING(str, "at: 22,22", 1); + } + + Tests::killAllWindows(); + EXPECT(Tests::windowCount(), 0); + } + + // test that we deny a floated window getting auto-grouped into a tiled group. + NLog::log("{}Test that we deny a floated window getting auto-grouped into a tiled group.", Colors::GREEN); + + OK(getFromSocket("/keyword windowrule[kitty-tiled]:match:class kitty_tiled")); + OK(getFromSocket("/keyword windowrule[kitty-tiled]:tile yes")); + auto kittyProcE = Tests::spawnKitty("kitty_tiled"); + if (!kittyProcE) { + NLog::log("{}Error: kitty did not spawn", Colors::RED); + return false; + } + OK(getFromSocket("/dispatch togglegroup")); + + OK(getFromSocket("/keyword windowrule[kitty-floated]:match:class kitty_floated")); + OK(getFromSocket("/keyword windowrule[kitty-floated]:float yes")); + auto kittyProcF = Tests::spawnKitty("kitty_floated"); + if (!kittyProcF) { + NLog::log("{}Error: kitty did not spawn", Colors::RED); + return false; + } + + EXPECT(Tests::windowCount(), 2); + + { + auto clients = getFromSocket("/clients"); + auto classPos = clients.find("class: kitty_floated"); + if (classPos == std::string::npos) { + NLog::log("{}Could not find kitty_floated in clients output", Colors::RED); + ret = 1; + } else { + auto entryStart = clients.rfind("Window ", classPos); + auto entryEnd = clients.find("\n\n", classPos); + auto windowEntry = clients.substr(entryStart, entryEnd - entryStart); + EXPECT_CONTAINS(windowEntry, "floating: 1"); + EXPECT_CONTAINS(windowEntry, "grouped: 0"); + } + } + + Tests::killAllWindows(); + EXPECT(Tests::windowCount(), 0); + return !ret; } diff --git a/hyprtester/src/tests/main/hyprctl.cpp b/hyprtester/src/tests/main/hyprctl.cpp index e8759d28..e5e6f1fc 100644 --- a/hyprtester/src/tests/main/hyprctl.cpp +++ b/hyprtester/src/tests/main/hyprctl.cpp @@ -77,6 +77,16 @@ static bool testGetprop() { EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size"), "100 50"); EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size -j"), R"({"min_size": [100,50]})"); + // expr-based min/max _size + getFromSocket("/dispatch setfloating class:kitty"); // need to set floating for tests below + getFromSocket("/dispatch setprop class:kitty max_size 90+10 25*2"); // set max to the same as min above, forcing window to 100*50 + EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size"), "100 50"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty max_size -j"), R"({"max_size": [100,50]})"); + getFromSocket("/dispatch setprop class:kitty min_size window_w*0.5 window_h-10"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size"), "50 40"); + EXPECT(getCommandStdOut("hyprctl getprop class:kitty min_size -j"), R"({"min_size": [50,40]})"); + getFromSocket("/dispatch settiled class:kitty"); // go back to tiled for consistency + // opacity EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity"), "1"); EXPECT(getCommandStdOut("hyprctl getprop class:kitty opacity -j"), R"({"opacity": 1})"); diff --git a/hyprtester/src/tests/main/layer.cpp b/hyprtester/src/tests/main/layer.cpp new file mode 100644 index 00000000..73e30ba6 --- /dev/null +++ b/hyprtester/src/tests/main/layer.cpp @@ -0,0 +1,53 @@ +#include "../../Log.hpp" +#include "../shared.hpp" +#include "tests.hpp" +#include "../../shared.hpp" +#include "../../hyprctlCompat.hpp" +#include +#include + +static int ret = 0; + +using namespace Hyprutils::OS; +using namespace Hyprutils::Memory; + +static bool spawnLayer(const std::string& namespace_) { + NLog::log("{}Spawning kitty layer {}", Colors::YELLOW, namespace_); + if (!Tests::spawnLayerKitty(namespace_)) { + NLog::log("{}Error: {} layer did not spawn", Colors::RED, namespace_); + return false; + } + return true; +} + +static bool test() { + NLog::log("{}Testing plugin layerrules", Colors::GREEN); + + if (!spawnLayer("rule-layer")) + return false; + + OK(getFromSocket("/dispatch plugin:test:add_layer_rule")); + OK(getFromSocket("/reload")); + + OK(getFromSocket("/keyword layerrule match:namespace rule-layer, plugin_rule effect")); + + if (!spawnLayer("rule-layer")) + return false; + + if (!spawnLayer("norule-layer")) + return false; + + OK(getFromSocket("/dispatch plugin:test:check_layer_rule")); + + OK(getFromSocket("/reload")); + + NLog::log("{}Killing all layers", Colors::YELLOW); + Tests::killAllLayers(); + + NLog::log("{}Expecting 0 layers", Colors::YELLOW); + EXPECT(Tests::layerCount(), 0); + + return !ret; +} + +REGISTER_TEST_FN(test) diff --git a/hyprtester/src/tests/main/layout.cpp b/hyprtester/src/tests/main/layout.cpp new file mode 100644 index 00000000..186d7034 --- /dev/null +++ b/hyprtester/src/tests/main/layout.cpp @@ -0,0 +1,127 @@ +#include "../shared.hpp" +#include "../../shared.hpp" +#include "../../hyprctlCompat.hpp" +#include "tests.hpp" + +static int ret = 0; + +static void swar() { + OK(getFromSocket("/keyword layout:single_window_aspect_ratio 1 1")); + + Tests::spawnKitty(); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 442,22"); + EXPECT_CONTAINS(str, "size: 1036,1036"); + } + + Tests::spawnKitty(); + + OK(getFromSocket("/dispatch killwindow activewindow")); + + Tests::waitUntilWindowsN(1); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 442,22"); + EXPECT_CONTAINS(str, "size: 1036,1036"); + } + + // don't use swar on maximized + OK(getFromSocket("/dispatch fullscreen 1")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,22"); + EXPECT_CONTAINS(str, "size: 1876,1036"); + } + + // clean up + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); +} + +// Don't crash when focus after global geometry changes +static void testCrashOnGeomUpdate() { + Tests::spawnKitty(); + Tests::spawnKitty(); + Tests::spawnKitty(); + + // move the layout + OK(getFromSocket("/keyword monitor HEADLESS-2,1920x1080@60,1000x0,1")); + + // shouldnt crash + OK(getFromSocket("/dispatch movefocus r")); + + OK(getFromSocket("/reload")); + + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); +} + +// Test if size + pos is preserved after fs cycle +static void testPosPreserve() { + Tests::spawnKitty(); + + OK(getFromSocket("/dispatch setfloating class:kitty")); + OK(getFromSocket("/dispatch resizewindowpixel exact 1337 69, class:kitty")); + OK(getFromSocket("/dispatch movewindowpixel exact 420 420, class:kitty")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 420,420"); + EXPECT_CONTAINS(str, "size: 1337,69"); + } + + OK(getFromSocket("/dispatch fullscreen")); + OK(getFromSocket("/dispatch fullscreen")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "size: 1337,69"); + } + + OK(getFromSocket("/dispatch movewindow r")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 581,420"); + EXPECT_CONTAINS(str, "size: 1337,69"); + } + + OK(getFromSocket("/dispatch fullscreen")); + OK(getFromSocket("/dispatch fullscreen")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 581,420"); + EXPECT_CONTAINS(str, "size: 1337,69"); + } + + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); +} + +static bool test() { + NLog::log("{}Testing layout generic", Colors::GREEN); + + // setup + OK(getFromSocket("/dispatch workspace 10")); + + // test + NLog::log("{}Testing `single_window_aspect_ratio`", Colors::GREEN); + swar(); + + testCrashOnGeomUpdate(); + testPosPreserve(); + + // clean up + NLog::log("Cleaning up", Colors::YELLOW); + OK(getFromSocket("/dispatch workspace 1")); + OK(getFromSocket("/reload")); + + return !ret; +} + +REGISTER_TEST_FN(test); diff --git a/hyprtester/src/tests/main/master.cpp b/hyprtester/src/tests/main/master.cpp index 9cd20e83..9aaa4cf0 100644 --- a/hyprtester/src/tests/main/master.cpp +++ b/hyprtester/src/tests/main/master.cpp @@ -3,7 +3,53 @@ #include "../../hyprctlCompat.hpp" #include "tests.hpp" -static int ret = 0; +static int ret = 0; + +// reqs 1 master 3 slaves +static void testOrientations() { + OK(getFromSocket("/keyword master:orientation top")); + + // top + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,22"); + EXPECT_CONTAINS(str, "size: 1876"); + } + + // cycle = top, right, bottom, center, left + + // right + OK(getFromSocket("/dispatch layoutmsg orientationnext")); + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 873,22"); + EXPECT_CONTAINS(str, "size: 1025,1036"); + } + + // bottom + OK(getFromSocket("/dispatch layoutmsg orientationnext")); + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,495"); + EXPECT_CONTAINS(str, "size: 1876"); + } + + // center + OK(getFromSocket("/dispatch layoutmsg orientationnext")); + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 450,22"); + EXPECT_CONTAINS(str, "size: 1020,1036"); + } + + // left + OK(getFromSocket("/dispatch layoutmsg orientationnext")); + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,22"); + EXPECT_CONTAINS(str, "size: 1025,1036"); + } +} static void focusMasterPrevious() { // setup @@ -44,11 +90,74 @@ static void focusMasterPrevious() { OK(getFromSocket("/dispatch layoutmsg focusmaster previous")); EXPECT_CONTAINS(getFromSocket("/activewindow"), "class: master"); + testOrientations(); + // clean up NLog::log("{}Killing all windows", Colors::YELLOW); Tests::killAllWindows(); } +static void testFsBehavior() { + // Master will re-send data to fullscreen / maximized windows, which can interfere with misc:on_focus_under_fullscreen + // check that it doesn't. + + for (auto const& win : {"master", "slave1", "slave2"}) { + if (!Tests::spawnKitty(win)) { + NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); + ++TESTS_FAILED; + ret = 1; + return; + } + } + + OK(getFromSocket("/dispatch focuswindow class:master")); + OK(getFromSocket("/dispatch fullscreen 1")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,22"); + EXPECT_CONTAINS(str, "size: 1876,1036"); + EXPECT_CONTAINS(str, "class: master"); + } + + OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 1")); + + Tests::spawnKitty("new_master"); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,22"); + EXPECT_CONTAINS(str, "size: 1876,1036"); + EXPECT_CONTAINS(str, "class: new_master"); + EXPECT_CONTAINS(str, "fullscreen: 1"); + } + + OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 0")); + + Tests::spawnKitty("ignored"); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "at: 22,22"); + EXPECT_CONTAINS(str, "size: 1876,1036"); + EXPECT_CONTAINS(str, "class: new_master"); + EXPECT_CONTAINS(str, "fullscreen: 1"); + } + + OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 2")); + + Tests::spawnKitty("vaxwashere"); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: vaxwashere"); + EXPECT_CONTAINS(str, "fullscreen: 0"); + } + + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); +} + static bool test() { NLog::log("{}Testing Master layout", Colors::GREEN); @@ -60,6 +169,9 @@ static bool test() { NLog::log("{}Testing `focusmaster previous` layoutmsg", Colors::GREEN); focusMasterPrevious(); + NLog::log("{}Testing fs behavior", Colors::GREEN); + testFsBehavior(); + // clean up NLog::log("Cleaning up", Colors::YELLOW); OK(getFromSocket("/dispatch workspace 1")); diff --git a/hyprtester/src/tests/main/misc.cpp b/hyprtester/src/tests/main/misc.cpp index 3187694b..471eef7a 100644 --- a/hyprtester/src/tests/main/misc.cpp +++ b/hyprtester/src/tests/main/misc.cpp @@ -18,6 +18,121 @@ using namespace Hyprutils::Memory; #define UP CUniquePointer #define SP CSharedPointer +// Uncomment once test vm can run hyprland-dialog +// static void testAnrDialogs() { +// NLog::log("{}Testing ANR dialogs", Colors::YELLOW); +// +// OK(getFromSocket("/keyword misc:enable_anr_dialog true")); +// OK(getFromSocket("/keyword misc:anr_missed_pings 1")); +// +// NLog::log("{}ANR dialog: regular workspaces", Colors::YELLOW); +// { +// OK(getFromSocket("/dispatch workspace 2")); +// +// auto kitty = Tests::spawnKitty("bad_kitty"); +// +// if (!kitty) { +// ret = 1; +// return; +// } +// +// { +// auto str = getFromSocket("/activewindow"); +// EXPECT_CONTAINS(str, "workspace: 2"); +// } +// +// OK(getFromSocket("/dispatch workspace 1")); +// +// ::kill(kitty->pid(), SIGSTOP); +// Tests::waitUntilWindowsN(2); +// +// { +// auto str = getFromSocket("/activeworkspace"); +// EXPECT_CONTAINS(str, "windows: 0"); +// } +// +// { +// OK(getFromSocket("/dispatch focuswindow class:hyprland-dialog")) +// auto str = getFromSocket("/activewindow"); +// EXPECT_CONTAINS(str, "workspace: 2"); +// } +// } +// +// Tests::killAllWindows(); +// +// NLog::log("{}ANR dialog: named workspaces", Colors::YELLOW); +// { +// OK(getFromSocket("/dispatch workspace name:yummy")); +// +// auto kitty = Tests::spawnKitty("bad_kitty"); +// +// if (!kitty) { +// ret = 1; +// return; +// } +// +// { +// auto str = getFromSocket("/activewindow"); +// EXPECT_CONTAINS(str, "yummy"); +// } +// +// OK(getFromSocket("/dispatch workspace 1")); +// +// ::kill(kitty->pid(), SIGSTOP); +// Tests::waitUntilWindowsN(2); +// +// { +// auto str = getFromSocket("/activeworkspace"); +// EXPECT_CONTAINS(str, "windows: 0"); +// } +// +// { +// OK(getFromSocket("/dispatch focuswindow class:hyprland-dialog")) +// auto str = getFromSocket("/activewindow"); +// EXPECT_CONTAINS(str, "yummy"); +// } +// } +// +// Tests::killAllWindows(); +// +// NLog::log("{}ANR dialog: special workspaces", Colors::YELLOW); +// { +// OK(getFromSocket("/dispatch workspace special:apple")); +// +// auto kitty = Tests::spawnKitty("bad_kitty"); +// +// if (!kitty) { +// ret = 1; +// return; +// } +// +// { +// auto str = getFromSocket("/activewindow"); +// EXPECT_CONTAINS(str, "special:apple"); +// } +// +// OK(getFromSocket("/dispatch togglespecialworkspace apple")); +// OK(getFromSocket("/dispatch workspace 1")); +// +// ::kill(kitty->pid(), SIGSTOP); +// Tests::waitUntilWindowsN(2); +// +// { +// auto str = getFromSocket("/activeworkspace"); +// EXPECT_CONTAINS(str, "windows: 0"); +// } +// +// { +// OK(getFromSocket("/dispatch focuswindow class:hyprland-dialog")) +// auto str = getFromSocket("/activewindow"); +// EXPECT_CONTAINS(str, "special:apple"); +// } +// } +// +// OK(getFromSocket("/reload")); +// Tests::killAllWindows(); +// } + static bool test() { NLog::log("{}Testing config: misc:", Colors::GREEN); diff --git a/hyprtester/src/tests/main/scroll.cpp b/hyprtester/src/tests/main/scroll.cpp new file mode 100644 index 00000000..8bb950dd --- /dev/null +++ b/hyprtester/src/tests/main/scroll.cpp @@ -0,0 +1,228 @@ +#include "../shared.hpp" +#include "../../shared.hpp" +#include "../../hyprctlCompat.hpp" +#include "tests.hpp" + +static int ret = 0; + +static void testFocusCycling() { + for (auto const& win : {"a", "b", "c", "d"}) { + if (!Tests::spawnKitty(win)) { + NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); + ++TESTS_FAILED; + ret = 1; + return; + } + } + + OK(getFromSocket("/dispatch focuswindow class:a")); + + OK(getFromSocket("/dispatch movefocus r")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: b"); + } + + OK(getFromSocket("/dispatch movefocus r")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: c"); + } + + OK(getFromSocket("/dispatch movefocus r")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: d"); + } + + OK(getFromSocket("/dispatch movewindow l")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: d"); + } + + OK(getFromSocket("/dispatch movefocus u")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: c"); + } + + // clean up + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); +} + +static void testFocusWrapping() { + for (auto const& win : {"a", "b", "c", "d"}) { + if (!Tests::spawnKitty(win)) { + NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); + ++TESTS_FAILED; + ret = 1; + return; + } + } + + // set wrap_focus to true + OK(getFromSocket("/keyword scrolling:wrap_focus true")); + + OK(getFromSocket("/dispatch focuswindow class:a")); + + OK(getFromSocket("/dispatch layoutmsg focus l")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: d"); + } + + OK(getFromSocket("/dispatch layoutmsg focus r")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: a"); + } + + // set wrap_focus to false + OK(getFromSocket("/keyword scrolling:wrap_focus false")); + + OK(getFromSocket("/dispatch focuswindow class:a")); + + OK(getFromSocket("/dispatch layoutmsg focus l")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: a"); + } + + OK(getFromSocket("/dispatch focuswindow class:d")); + + OK(getFromSocket("/dispatch layoutmsg focus r")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: d"); + } + + // clean up + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); +} + +static void testSwapcolWrapping() { + for (auto const& win : {"a", "b", "c", "d"}) { + if (!Tests::spawnKitty(win)) { + NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); + ++TESTS_FAILED; + ret = 1; + return; + } + } + + // set wrap_swapcol to true + OK(getFromSocket("/keyword scrolling:wrap_swapcol true")); + + OK(getFromSocket("/dispatch focuswindow class:a")); + + OK(getFromSocket("/dispatch layoutmsg swapcol l")); + OK(getFromSocket("/dispatch layoutmsg focus l")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: c"); + } + + // clean up + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + + for (auto const& win : {"a", "b", "c", "d"}) { + if (!Tests::spawnKitty(win)) { + NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); + ++TESTS_FAILED; + ret = 1; + return; + } + } + + OK(getFromSocket("/dispatch focuswindow class:d")); + OK(getFromSocket("/dispatch layoutmsg swapcol r")); + OK(getFromSocket("/dispatch layoutmsg focus r")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: b"); + } + + // clean up + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + + for (auto const& win : {"a", "b", "c", "d"}) { + if (!Tests::spawnKitty(win)) { + NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); + ++TESTS_FAILED; + ret = 1; + return; + } + } + + // set wrap_swapcol to false + OK(getFromSocket("/keyword scrolling:wrap_swapcol false")); + + OK(getFromSocket("/dispatch focuswindow class:a")); + + OK(getFromSocket("/dispatch layoutmsg swapcol l")); + OK(getFromSocket("/dispatch layoutmsg focus r")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: b"); + } + + OK(getFromSocket("/dispatch focuswindow class:d")); + + OK(getFromSocket("/dispatch layoutmsg swapcol r")); + OK(getFromSocket("/dispatch layoutmsg focus l")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: c"); + } + + // clean up + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); +} + +static bool test() { + NLog::log("{}Testing Scroll layout", Colors::GREEN); + + // setup + OK(getFromSocket("/dispatch workspace name:scroll")); + OK(getFromSocket("/keyword general:layout scrolling")); + + // test + NLog::log("{}Testing focus cycling", Colors::GREEN); + testFocusCycling(); + + // test + NLog::log("{}Testing focus wrap", Colors::GREEN); + testFocusWrapping(); + + // test + NLog::log("{}Testing swapcol wrap", Colors::GREEN); + testSwapcolWrapping(); + + // clean up + NLog::log("Cleaning up", Colors::YELLOW); + OK(getFromSocket("/dispatch workspace 1")); + OK(getFromSocket("/reload")); + + return !ret; +} + +REGISTER_TEST_FN(test); diff --git a/hyprtester/src/tests/main/window.cpp b/hyprtester/src/tests/main/window.cpp index 37442790..beac0298 100644 --- a/hyprtester/src/tests/main/window.cpp +++ b/hyprtester/src/tests/main/window.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -24,6 +25,28 @@ static bool spawnKitty(const std::string& class_, const std::vector return true; } +/// Spawns a kitty and creates a file and returns its name. The removal of the file triggers +/// activation of the spawned kitty window. +/// +/// On failure, returns an empty string, possibly leaving a temporary file. +static std::string spawnKittyActivating(const std::string& class_ = "kitty_activating") { + // `XXXXXX` is what `mkstemp` expects to find in the string + std::string tmpFilename = (std::filesystem::temp_directory_path() / "XXXXXX").string(); + int fd = mkstemp(tmpFilename.data()); + if (fd < 0) { + NLog::log("{}Error: could not create tmp file: errno {}", Colors::RED, errno); + return ""; + } + (void)close(fd); + bool ok = + spawnKitty(class_, {"-o", "allow_remote_control=yes", "--", "/bin/sh", "-c", "while [ -f \"" + tmpFilename + "\" ]; do :; done; kitten @ focus-window; sleep infinity"}); + if (!ok) { + NLog::log("{}Error: failed to spawn kitty", Colors::RED); + return ""; + } + return tmpFilename; +} + static std::string getWindowAttribute(const std::string& winInfo, const std::string& attr) { auto pos = winInfo.find(attr); if (pos == std::string::npos) { @@ -70,9 +93,9 @@ static void testSwapWindow() { { getFromSocket("/dispatch focuswindow class:kitty_A"); auto pos = getWindowAttribute(getFromSocket("/activewindow"), "at:"); - NLog::log("{}Testing kitty_A {}, swapwindow with direction 'l'", Colors::YELLOW, pos); + NLog::log("{}Testing kitty_A {}, swapwindow with direction 'r'", Colors::YELLOW, pos); - OK(getFromSocket("/dispatch swapwindow l")); + OK(getFromSocket("/dispatch swapwindow r")); OK(getFromSocket("/dispatch focuswindow class:kitty_B")); EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("{}", pos)); @@ -197,7 +220,7 @@ static void testGroupRules() { Tests::killAllWindows(); } -static bool isActiveWindow(const std::string& class_, char fullscreen, bool log = true) { +static bool isActiveWindow(const std::string& class_, char fullscreen = '0', bool log = true) { std::string activeWin = getFromSocket("/activewindow"); auto winClass = getWindowAttribute(activeWin, "class:"); auto winFullscreen = getWindowAttribute(activeWin, "fullscreen:").back(); @@ -210,13 +233,13 @@ static bool isActiveWindow(const std::string& class_, char fullscreen, bool log } } -static bool waitForActiveWindow(const std::string& class_, char fullscreen, int maxTries = 50) { +static bool waitForActiveWindow(const std::string& class_, char fullscreen = '0', bool logLastCheck = true, int maxTries = 50) { int cnt = 0; while (!isActiveWindow(class_, fullscreen, false)) { ++cnt; std::this_thread::sleep_for(std::chrono::milliseconds(100)); if (cnt > maxTries) { - return isActiveWindow(class_, fullscreen, true); + return isActiveWindow(class_, fullscreen, logLastCheck); } } return true; @@ -232,24 +255,6 @@ static bool testWindowFocusOnFullscreenConflict() { OK(getFromSocket("/keyword misc:focus_on_activate true")); - auto spawnKittyActivating = [] -> std::string { - // `XXXXXX` is what `mkstemp` expects to find in the string - std::string tmpFilename = (std::filesystem::temp_directory_path() / "XXXXXX").string(); - int fd = mkstemp(tmpFilename.data()); - if (fd < 0) { - NLog::log("{}Error: could not create tmp file: errno {}", Colors::RED, errno); - return ""; - } - (void)close(fd); - bool ok = spawnKitty("kitty_activating", - {"-o", "allow_remote_control=yes", "--", "/bin/sh", "-c", "while [ -f \"" + tmpFilename + "\" ]; do :; done; kitten @ focus-window; sleep infinity"}); - if (!ok) { - NLog::log("{}Error: failed to spawn kitty", Colors::RED); - return ""; - } - return tmpFilename; - }; - // Unfullscreen on conflict { OK(getFromSocket("/keyword misc:on_focus_under_fullscreen 2")); @@ -373,6 +378,61 @@ static void testMaximizeSize() { EXPECT(Tests::windowCount(), 0); } +static void testFloatingFocusOnFullscreen() { + NLog::log("{}Testing floating focus on fullscreen", Colors::GREEN); + + EXPECT(spawnKitty("kitty_A"), true); + OK(getFromSocket("/dispatch togglefloating")); + + EXPECT(spawnKitty("kitty_B"), true); + OK(getFromSocket("/dispatch fullscreen 1")); + + OK(getFromSocket("/dispatch cyclenext")); + + OK(getFromSocket("/dispatch plugin:test:floating_focus_on_fullscreen")); + + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + + NLog::log("{}Expecting 0 windows", Colors::YELLOW); + EXPECT(Tests::windowCount(), 0); +} + +static void testGroupFallbackFocus() { + NLog::log("{}Testing group fallback focus", Colors::GREEN); + + EXPECT(spawnKitty("kitty_A"), true); + + OK(getFromSocket("/dispatch togglegroup")); + + EXPECT(spawnKitty("kitty_B"), true); + EXPECT(spawnKitty("kitty_C"), true); + EXPECT(spawnKitty("kitty_D"), true); + + { + auto str = getFromSocket("/activewindow"); + EXPECT(str.contains("class: kitty_D"), true); + } + + OK(getFromSocket("/dispatch focuswindow class:kitty_B")); + OK(getFromSocket("/dispatch focuswindow class:kitty_D")); + OK(getFromSocket("/dispatch killactive")); + + Tests::waitUntilWindowsN(3); + + // Focus must return to the last focus, in this case B. + { + auto str = getFromSocket("/activewindow"); + EXPECT(str.contains("class: kitty_B"), true); + } + + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + + NLog::log("{}Expecting 0 windows", Colors::YELLOW); + EXPECT(Tests::windowCount(), 0); +} + static void testBringActiveToTopMouseMovement() { NLog::log("{}Testing bringactivetotop mouse movement", Colors::GREEN); @@ -412,6 +472,192 @@ static void testBringActiveToTopMouseMovement() { Tests::killAllWindows(); } +static void testInitialFloatSize() { + NLog::log("{}Testing initial float size", Colors::GREEN); + + Tests::killAllWindows(); + OK(getFromSocket("/keyword windowrule match:class kitty, float yes")); + OK(getFromSocket("/keyword input:float_switch_override_focus 0")); + + EXPECT(spawnKitty("kitty"), true); + + { + // Kitty by default opens as 640x400, if this changes this test will break + auto str = getFromSocket("/clients"); + EXPECT(str.contains("size: 640,400"), true); + } + + OK(getFromSocket("/reload")); + + Tests::killAllWindows(); + + OK(getFromSocket("/dispatch exec [float yes]kitty")); + + Tests::waitUntilWindowsN(1); + + { + // Kitty by default opens as 640x400, if this changes this test will break + auto str = getFromSocket("/clients"); + EXPECT(str.contains("size: 640,400"), true); + EXPECT(str.contains("floating: 1"), true); + } + + Tests::killAllWindows(); +} + +/// Tests that the `focus_on_activate` effect of window rules always overrides +/// the `misc:focus_on_activate` variable. +static bool testWindowRuleFocusOnActivate() { + OK(getFromSocket("/reload")); + + if (!spawnKitty("kitty_default")) { + NLog::log("{}Error: failed to spawn kitty", Colors::RED); + return false; + } + + // Do not focus anyone automatically + ///////////OK(getFromSocket("/keyword windowrule match:class .*, no_initial_focus true")); + + // `focus_on_activate off` takes over + { + OK(getFromSocket("/keyword misc:focus_on_activate true")); + OK(getFromSocket("/keyword windowrule match:class kitty_antifocus, focus_on_activate off")); + + const std::string removeToActivate = spawnKittyActivating("kitty_antifocus"); + if (removeToActivate.empty()) { + return false; + } + EXPECT(waitForActiveWindow("kitty_antifocus"), true); + OK(getFromSocket("/dispatch focuswindow class:kitty_default")); + EXPECT(isActiveWindow("kitty_default"), true); + + std::filesystem::remove(removeToActivate); + // The focus should NOT transition, since the window rule explicitly forbids that + EXPECT(waitForActiveWindow("kitty_antifocus", '0', false), false); + } + + // `focus_on_activate on` takes over + { + OK(getFromSocket("/keyword misc:focus_on_activate false")); + OK(getFromSocket("/keyword windowrule match:class kitty_superfocus, focus_on_activate on")); + + const std::string removeToActivate = spawnKittyActivating("kitty_superfocus"); + if (removeToActivate.empty()) { + return false; + } + EXPECT(waitForActiveWindow("kitty_superfocus"), true); + OK(getFromSocket("/dispatch focuswindow class:kitty_default")); + EXPECT(isActiveWindow("kitty_default"), true); + + std::filesystem::remove(removeToActivate); + // Now that we requested activation, the focus SHOULD transition to kitty_superfocus, according to the window rule + EXPECT(waitForActiveWindow("kitty_superfocus"), true); + } + + NLog::log("{}Reloading config", Colors::YELLOW); + OK(getFromSocket("/reload")); + + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + + NLog::log("{}Expecting 0 windows", Colors::YELLOW); + EXPECT(Tests::windowCount(), 0); + + return true; +} + +// tests if a pinned window contains the valid workspace after change +static bool testPinnedWorkspacesValid() { + OK(getFromSocket("/reload")); + getFromSocket("/dispatch workspace 1337"); + + if (!spawnKitty("kitty")) { + NLog::log("{}Error: failed to spawn kitty", Colors::RED); + return false; + } + + OK(getFromSocket("/dispatch setfloating class:kitty")); + OK(getFromSocket("/dispatch pin class:kitty")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT(str.contains("workspace: 1337"), true); + EXPECT(str.contains("pinned: 1"), true); + } + + getFromSocket("/dispatch workspace 1338"); + + { + auto str = getFromSocket("/activewindow"); + EXPECT(str.contains("workspace: 1338"), true); + EXPECT(str.contains("pinned: 1"), true); + } + + OK(getFromSocket("/dispatch settiled class:kitty")) + + { + auto str = getFromSocket("/activewindow"); + EXPECT(str.contains("workspace: 1338"), true); + EXPECT(str.contains("pinned: 0"), true); + } + + NLog::log("{}Reloading config", Colors::YELLOW); + OK(getFromSocket("/reload")); + + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + + NLog::log("{}Expecting 0 windows", Colors::YELLOW); + EXPECT(Tests::windowCount(), 0); + + return true; +} + +static bool testWindowRuleWorkspaceEmpty() { + NLog::log("{}Testing windowrule workspace empty", Colors::YELLOW); + OK(getFromSocket("/reload")); + + OK(getFromSocket("/keyword windowrule match:class kitty_A, workspace empty")); + OK(getFromSocket("/keyword windowrule match:class kitty_B, workspace emptyn")); + + getFromSocket("/dispatch workspace 3"); + + if (!spawnKitty("kitty")) { + NLog::log("{}Error: failed to spawn kitty", Colors::RED); + return false; + } + + { + auto str = getFromSocket("/activewindow"); + EXPECT(str.contains("workspace: 3"), true); + } + + if (!spawnKitty("kitty_A")) { + NLog::log("{}Error: failed to spawn kitty", Colors::RED); + return false; + } + + { + auto str = getFromSocket("/activewindow"); + EXPECT(str.contains("workspace: 1"), true); + } + + getFromSocket("/dispatch workspace 3"); + if (!spawnKitty("kitty_B")) { + NLog::log("{}Error: failed to spawn kitty", Colors::RED); + return false; + } + + { + auto str = getFromSocket("/activewindow"); + EXPECT(str.contains("workspace: 4"), true); + } + + Tests::killAllWindows(); + + return true; +} + static bool test() { NLog::log("{}Testing windows", Colors::GREEN); @@ -525,14 +771,14 @@ static bool test() { EXPECT(Tests::windowCount(), 3); NLog::log("{}Checking props of xeyes", Colors::YELLOW); - // check some window props of xeyes, try to tile them + // check some window props of xeyes, try to float it { auto str = getFromSocket("/clients"); - EXPECT_CONTAINS(str, "floating: 1"); - getFromSocket("/dispatch settiled class:XEyes"); + EXPECT_NOT_CONTAINS(str, "floating: 1"); + getFromSocket("/dispatch setfloating class:XEyes"); std::this_thread::sleep_for(std::chrono::milliseconds(200)); str = getFromSocket("/clients"); - EXPECT_NOT_CONTAINS(str, "floating: 1"); + EXPECT_CONTAINS(str, "floating: 1"); } // kill all @@ -716,6 +962,23 @@ static bool test() { Tests::killAllWindows(); + OK(getFromSocket("/keyword windowrule[border-magic-kitty]:match:class border_kitty")); + OK(getFromSocket("/keyword windowrule[border-magic-kitty]:border_color rgba(c6ff00ff) rgba(ff0000ee) 45deg")); + + if (!spawnKitty("border_kitty")) + return false; + + OK(getFromSocket("/dispatch focuswindow class:border_kitty")); + + { + auto str = getFromSocket("/getprop active active_border_color"); + EXPECT_CONTAINS(str, "ffc6ff00"); + EXPECT_CONTAINS(str, "eeff0000"); + EXPECT_CONTAINS(str, "45deg"); + } + + Tests::killAllWindows(); + if (!spawnKitty("tag_kitty")) return false; @@ -799,7 +1062,8 @@ static bool test() { Tests::killAllWindows(); // test expression rules - OK(getFromSocket("/keyword windowrule match:class expr_kitty, float yes, size monitor_w*0.5 monitor_h*0.5, move 20+(monitor_w*0.1) monitor_h*0.5")); + OK(getFromSocket("/keyword windowrule match:class expr_kitty, float yes, size monitor_w*0.5 monitor_h*0.5, min_size monitor_w*0.25 monitor_h*0.25, " + "max_size monitor_w*0.75 monitor_h*0.75, move 20+(monitor_w*0.1) monitor_h*0.5")); if (!spawnKitty("expr_kitty")) return false; @@ -809,12 +1073,20 @@ static bool test() { EXPECT_CONTAINS(str, "floating: 1"); EXPECT_CONTAINS(str, "at: 212,540"); EXPECT_CONTAINS(str, "size: 960,540"); + + auto min = getFromSocket("/getprop active min_size"); + EXPECT_CONTAINS(min, "480"); + EXPECT_CONTAINS(min, "270"); + + auto max = getFromSocket("/getprop active max_size"); + EXPECT_CONTAINS(max, "1440"); + EXPECT_CONTAINS(max, "810"); } OK(getFromSocket("/reload")); Tests::killAllWindows(); - OK(getFromSocket("/dispatch plugin:test:add_rule")); + OK(getFromSocket("/dispatch plugin:test:add_window_rule")); OK(getFromSocket("/reload")); OK(getFromSocket("/keyword windowrule match:class plugin_kitty, plugin_rule effect")); @@ -822,12 +1094,12 @@ static bool test() { if (!spawnKitty("plugin_kitty")) return false; - OK(getFromSocket("/dispatch plugin:test:check_rule")); + OK(getFromSocket("/dispatch plugin:test:check_window_rule")); OK(getFromSocket("/reload")); Tests::killAllWindows(); - OK(getFromSocket("/dispatch plugin:test:add_rule")); + OK(getFromSocket("/dispatch plugin:test:add_window_rule")); OK(getFromSocket("/reload")); OK(getFromSocket("/keyword windowrule[test-plugin-rule]:match:class plugin_kitty")); @@ -836,16 +1108,20 @@ static bool test() { if (!spawnKitty("plugin_kitty")) return false; - OK(getFromSocket("/dispatch plugin:test:check_rule")); + OK(getFromSocket("/dispatch plugin:test:check_window_rule")); OK(getFromSocket("/reload")); Tests::killAllWindows(); testGroupRules(); - testMaximizeSize(); - + testFloatingFocusOnFullscreen(); testBringActiveToTopMouseMovement(); + testGroupFallbackFocus(); + testInitialFloatSize(); + testWindowRuleFocusOnActivate(); + testPinnedWorkspacesValid(); + testWindowRuleWorkspaceEmpty(); NLog::log("{}Reloading config", Colors::YELLOW); OK(getFromSocket("/reload")); diff --git a/hyprtester/src/tests/main/workspaces.cpp b/hyprtester/src/tests/main/workspaces.cpp index c1b9690a..122cd619 100644 --- a/hyprtester/src/tests/main/workspaces.cpp +++ b/hyprtester/src/tests/main/workspaces.cpp @@ -20,6 +20,91 @@ using namespace Hyprutils::Utils; #define UP CUniquePointer #define SP CSharedPointer +static bool testSpecialWorkspaceFullscreen() { + NLog::log("{}Testing special workspace fullscreen detection", Colors::YELLOW); + + CScopeGuard guard = {[&]() { + NLog::log("{}Cleaning up special workspace fullscreen test", Colors::YELLOW); + // Close special workspace if open + auto monitors = getFromSocket("/monitors"); + if (monitors.contains("(special:test_fs_special)") && !monitors.contains("special workspace: 0 ()")) + getFromSocket("/dispatch togglespecialworkspace test_fs_special"); + Tests::killAllWindows(); + OK(getFromSocket("/reload")); + }}; + + getFromSocket("/dispatch workspace 1"); + EXPECT(Tests::windowCount(), 0); + + NLog::log("{}Test 1: Fullscreen detection on special workspace", Colors::YELLOW); + + OK(getFromSocket("/dispatch workspace special:test_fs_special")); + + if (!Tests::spawnKitty("kitty_special")) + return false; + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: kitty_special"); + EXPECT_CONTAINS(str, "(special:test_fs_special)"); + } + + OK(getFromSocket("/dispatch fullscreen 0")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "fullscreen: 2"); + } + + { + auto str = getFromSocket("/monitors"); + EXPECT_CONTAINS(str, "(special:test_fs_special)"); + } + + NLog::log("{}Test 2: Special workspace fullscreen precedence", Colors::YELLOW); + + // Close special workspace before spawning on regular workspace + OK(getFromSocket("/dispatch togglespecialworkspace test_fs_special")); + getFromSocket("/dispatch workspace 1"); + + if (!Tests::spawnKitty("kitty_regular")) + return false; + + OK(getFromSocket("/dispatch fullscreen 0")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: kitty_regular"); + EXPECT_CONTAINS(str, "fullscreen: 2"); + } + + OK(getFromSocket("/dispatch togglespecialworkspace test_fs_special")); + OK(getFromSocket("/dispatch focuswindow class:kitty_special")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: kitty_special"); + } + + NLog::log("{}Test 3: Toggle special workspace hides it", Colors::YELLOW); + + OK(getFromSocket("/dispatch togglespecialworkspace test_fs_special")); + OK(getFromSocket("/dispatch focuswindow class:kitty_regular")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: kitty_regular"); + EXPECT_CONTAINS(str, "fullscreen: 2"); + } + + { + auto str = getFromSocket("/monitors"); + EXPECT_CONTAINS(str, "special workspace: 0 ()"); + } + + return true; +} + static bool testAsymmetricGaps() { NLog::log("{}Testing asymmetric gap splits", Colors::YELLOW); { @@ -108,6 +193,206 @@ static bool testAsymmetricGaps() { return true; } +static void testMultimonBAF() { + NLog::log("{}Testing multimon back and forth", Colors::YELLOW); + + OK(getFromSocket("/keyword binds:workspace_back_and_forth 1")); + + OK(getFromSocket("/dispatch focusmonitor HEADLESS-2")); + OK(getFromSocket("/dispatch workspace 1")); + + Tests::spawnKitty(); + + OK(getFromSocket("/dispatch workspace 2")); + + Tests::spawnKitty(); + + OK(getFromSocket("/dispatch focusmonitor HEADLESS-3")); + OK(getFromSocket("/dispatch workspace 3")); + + Tests::spawnKitty(); + + OK(getFromSocket("/dispatch workspace 3")); + + { + auto str = getFromSocket("/activeworkspace"); + EXPECT_CONTAINS(str, "workspace ID 2 "); + } + + OK(getFromSocket("/dispatch workspace 4")); + OK(getFromSocket("/dispatch workspace 4")); + + { + auto str = getFromSocket("/activeworkspace"); + EXPECT_CONTAINS(str, "workspace ID 2 "); + } + + OK(getFromSocket("/dispatch workspace 2")); + + { + auto str = getFromSocket("/activeworkspace"); + EXPECT_CONTAINS(str, "workspace ID 4 "); + } + + OK(getFromSocket("/dispatch workspace 3")); + OK(getFromSocket("/dispatch workspace 3")); + + { + auto str = getFromSocket("/activeworkspace"); + EXPECT_CONTAINS(str, "workspace ID 4 "); + } + + OK(getFromSocket("/dispatch workspace 2")); + OK(getFromSocket("/dispatch workspace 3")); + OK(getFromSocket("/dispatch workspace 1")); + OK(getFromSocket("/dispatch workspace 1")); + + { + auto str = getFromSocket("/activeworkspace"); + EXPECT_CONTAINS(str, "workspace ID 3 "); + } + + Tests::killAllWindows(); +} + +static void testMultimonFocus() { + NLog::log("{}Testing multimon focus and move", Colors::YELLOW); + + OK(getFromSocket("/keyword input:follow_mouse 0")); + OK(getFromSocket("/dispatch focusmonitor HEADLESS-3")); + OK(getFromSocket("/dispatch workspace 8")); + OK(getFromSocket("/dispatch focusmonitor HEADLESS-2")); + OK(getFromSocket("/dispatch workspace 7")); + + for (auto const& win : {"a", "b"}) { + if (!Tests::spawnKitty(win)) { + NLog::log("{}Failed to spawn kitty with win class `{}`", Colors::RED, win); + ++TESTS_FAILED; + ret = 1; + return; + } + } + + OK(getFromSocket("/dispatch focuswindow class:a")); + OK(getFromSocket("/dispatch movefocus r")); + + { + auto str = getFromSocket("/activeworkspace"); + EXPECT_CONTAINS(str, "workspace ID 7 "); + } + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: b"); + } + + OK(getFromSocket("/dispatch movefocus r")); + + { + auto str = getFromSocket("/activeworkspace"); + EXPECT_CONTAINS(str, "workspace ID 8 "); + } + + Tests::spawnKitty("c"); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: c"); + } + + { + auto str = getFromSocket("/activeworkspace"); + EXPECT_CONTAINS(str, "workspace ID 8 "); + } + + OK(getFromSocket("/dispatch movefocus l")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: b"); + } + + { + auto str = getFromSocket("/activeworkspace"); + EXPECT_CONTAINS(str, "workspace ID 7 "); + } + + OK(getFromSocket("/dispatch movewindow r")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: b"); + } + + { + auto str = getFromSocket("/activeworkspace"); + EXPECT_CONTAINS(str, "workspace ID 8 "); + } + + OK(getFromSocket("/dispatch movefocus r")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: c"); + } + + { + auto str = getFromSocket("/activeworkspace"); + EXPECT_CONTAINS(str, "workspace ID 8 "); + } + + OK(getFromSocket("/dispatch movefocus l")); + + OK(getFromSocket("/keyword general:no_focus_fallback true")); + OK(getFromSocket("/keyword binds:window_direction_monitor_fallback false")); + + EXPECT_NOT(getFromSocket("/dispatch movefocus l"), "ok"); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: b"); + } + + { + auto str = getFromSocket("/activeworkspace"); + EXPECT_CONTAINS(str, "workspace ID 8 "); + } + + OK(getFromSocket("/dispatch movewindow l")); + + { + auto str = getFromSocket("/activewindow"); + EXPECT_CONTAINS(str, "class: b"); + } + + { + auto str = getFromSocket("/activeworkspace"); + EXPECT_CONTAINS(str, "workspace ID 8 "); + } + + OK(getFromSocket("/reload")); + + Tests::killAllWindows(); +} + +static void testDynamicWsEffects() { + // test dynamic workspace effects, they shouldn't lag + + OK(getFromSocket("/dispatch workspace 69")); + + Tests::spawnKitty("bitch"); + + OK(getFromSocket("r/keyword workspace 69,bordersize:20")); + OK(getFromSocket("r/keyword workspace 69,rounding:false")); + + EXPECT(getFromSocket("/getprop class:bitch border_size"), "20"); + EXPECT(getFromSocket("/getprop class:bitch rounding"), "0"); + + OK(getFromSocket("/reload")); + + Tests::killAllWindows(); +} + static bool test() { NLog::log("{}Testing workspaces", Colors::GREEN); @@ -442,14 +727,19 @@ static bool test() { EXPECT_CONTAINS(str, "class: kitty_B"); } - // destroy the headless output - OK(getFromSocket("/output remove HEADLESS-3")); - // kill all NLog::log("{}Killing all windows", Colors::YELLOW); Tests::killAllWindows(); + testMultimonBAF(); + testMultimonFocus(); + + // destroy the headless output + OK(getFromSocket("/output remove HEADLESS-3")); + + testSpecialWorkspaceFullscreen(); testAsymmetricGaps(); + testDynamicWsEffects(); NLog::log("{}Expecting 0 windows", Colors::YELLOW); EXPECT(Tests::windowCount(), 0); diff --git a/hyprtester/src/tests/shared.cpp b/hyprtester/src/tests/shared.cpp index 8cdd648e..f6e2fce9 100644 --- a/hyprtester/src/tests/shared.cpp +++ b/hyprtester/src/tests/shared.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "../shared.hpp" #include "../hyprctlCompat.hpp" @@ -39,6 +40,38 @@ CUniquePointer Tests::spawnKitty(const std::string& class_, const std: return kitty; } +CUniquePointer Tests::spawnLayerKitty(const std::string& namespace_, const std::vector args) { + std::vector programArgs = args; + if (!namespace_.empty()) { + programArgs.insert(programArgs.begin(), "--class"); + programArgs.insert(programArgs.begin() + 1, namespace_); + } + + programArgs.insert(programArgs.begin(), "+kitten"); + programArgs.insert(programArgs.begin() + 1, "panel"); + + CUniquePointer kitty = makeUnique("kitty", programArgs); + kitty->addEnv("WAYLAND_DISPLAY", WLDISPLAY); + kitty->runAsync(); + + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + + // wait while the layer spawns + int counter = 0; + while (processAlive(kitty->pid()) && countOccurrences(getFromSocket("/layers"), std::format("pid: {}", kitty->pid())) == 0) { + counter++; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + if (counter > 50) + return nullptr; + } + + if (!processAlive(kitty->pid())) + return nullptr; + + return kitty; +} + bool Tests::processAlive(pid_t pid) { errno = 0; int ret = kill(pid, 0); @@ -96,6 +129,38 @@ void Tests::waitUntilWindowsN(int n) { } } +int Tests::layerCount() { + return countOccurrences(getFromSocket("/layers"), "namespace: "); +} + +bool Tests::killAllLayers() { + auto str = getFromSocket("/layers"); + auto pos = str.find("pid: "); + while (pos != std::string::npos) { + auto pid = stoi(str.substr(pos + 5, str.find('\n', pos))); + kill(pid, 15); + + // we need to wait for a bit because for some reason otherwise we'll end up + // with layers with pid -1 if they are all removed at the same time + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + pos = str.find("pid: ", pos + 5); + } + + int counter = 0; + while (Tests::layerCount() != 0) { + counter++; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + if (counter > 50) { + std::println("{}Timed out waiting for layers to close", Colors::RED); + return false; + } + } + + return true; +} + std::string Tests::execAndGet(const std::string& cmd) { CProcess proc("/bin/sh", {"-c", cmd}); @@ -105,3 +170,14 @@ std::string Tests::execAndGet(const std::string& cmd) { return proc.stdOut(); } + +bool Tests::writeFile(const std::string& name, const std::string& contents) { + std::ofstream of(name, std::ios::trunc); + if (!of.good()) + return false; + + of << contents; + of.close(); + + return true; +} diff --git a/hyprtester/src/tests/shared.hpp b/hyprtester/src/tests/shared.hpp index fe28a69d..bf875f8b 100644 --- a/hyprtester/src/tests/shared.hpp +++ b/hyprtester/src/tests/shared.hpp @@ -9,10 +9,14 @@ //NOLINTNEXTLINE namespace Tests { Hyprutils::Memory::CUniquePointer spawnKitty(const std::string& class_ = "", const std::vector args = {}); + Hyprutils::Memory::CUniquePointer spawnLayerKitty(const std::string& namespace_ = "", const std::vector args = {}); bool processAlive(pid_t pid); int windowCount(); int countOccurrences(const std::string& in, const std::string& what); bool killAllWindows(); void waitUntilWindowsN(int n); + int layerCount(); + bool killAllLayers(); std::string execAndGet(const std::string& cmd); + bool writeFile(const std::string& name, const std::string& contents); }; diff --git a/hyprtester/test.conf b/hyprtester/test.conf index ac28bc5a..ab4f8ee3 100644 --- a/hyprtester/test.conf +++ b/hyprtester/test.conf @@ -179,6 +179,17 @@ master { new_status = master } +scrolling { + fullscreen_on_one_column = true + column_width = 0.5 + focus_fit_method = 1 + follow_focus = true + follow_min_visible = 1 + explicit_column_widths = 0.25, 0.333, 0.5, 0.667, 0.75, 1.0 + wrap_focus = true + wrap_swapcol = true +} + # https://wiki.hyprland.org/Configuring/Variables/#misc misc { force_default_wallpaper = -1 # Set to 0 or 1 to disable the anime mascot wallpapers @@ -239,7 +250,7 @@ bind = $mainMod, E, exec, $fileManager bind = $mainMod, V, togglefloating, bind = $mainMod, R, exec, $menu bind = $mainMod, P, pseudo, # dwindle -bind = $mainMod, J, togglesplit, # dwindle +bind = $mainMod, J, layoutmsg, togglesplit, # dwindle # Move focus with mainMod + arrow keys bind = $mainMod, left, movefocus, l @@ -398,3 +409,5 @@ gesture = 5, left, dispatcher, sendshortcut, , i, activewindow gesture = 5, right, dispatcher, sendshortcut, , t, activewindow gesture = 4, right, dispatcher, sendshortcut, , return, activewindow gesture = 4, left, dispatcher, movecursortocorner, 1 + +gesturep = 2, right, float diff --git a/nix/default.nix b/nix/default.nix index 54776871..bc5ba309 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -12,6 +12,7 @@ epoll-shim, git, glaze-hyprland, + glslang, gtest, hyprcursor, hyprgraphics, @@ -21,10 +22,17 @@ hyprutils, hyprwayland-scanner, hyprwire, + lcms2, libGL, libdrm, libexecinfo, libinput, + libxcb, + libxcb-errors, + libxcb-render-util, + libxcb-wm, + libxdmcp, + libxcursor, libxkbcommon, libuuid, libgbm, @@ -38,7 +46,6 @@ wayland, wayland-protocols, wayland-scanner, - xorg, xwayland, debug ? false, withTests ? false, @@ -59,8 +66,20 @@ inherit (builtins) foldl' readFile; inherit (lib.asserts) assertMsg; inherit (lib.attrsets) mapAttrsToList; - inherit (lib.lists) flatten concatLists optional optionals; - inherit (lib.strings) makeBinPath optionalString cmakeBool trim; + inherit + (lib.lists) + flatten + concatLists + optional + optionals + ; + inherit + (lib.strings) + makeBinPath + optionalString + cmakeBool + trim + ; fs = lib.fileset; adapters = flatten [ @@ -72,7 +91,8 @@ 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.hypr.land/Configuring/XWayland"; + assert assertMsg (!hidpiXWayland) + "The option `hidpiXWayland` has been removed. Please refer https://wiki.hypr.land/Configuring/XWayland"; assert assertMsg (!legacyRenderer) "The option `legacyRenderer` has been removed. Legacy renderer is no longer supported."; assert assertMsg (!withHyprtester) "The option `withHyprtester` has been removed. Hyprtester is always built now."; customStdenv.mkDerivation (finalAttrs: { @@ -85,23 +105,29 @@ in fs.intersection # allows non-flake builds to only include files tracked by git (fs.gitTracked ../.) - (fs.unions (flatten [ - ../assets/hyprland-portals.conf - ../assets/install - ../hyprctl - ../hyprland.pc.in - ../LICENSE - ../protocols - ../src - ../start - ../systemd - ../VERSION - (fs.fileFilter (file: file.hasExt "1") ../docs) - (fs.fileFilter (file: file.hasExt "conf" || file.hasExt "in") ../example) - (fs.fileFilter (file: file.hasExt "sh") ../scripts) - (fs.fileFilter (file: file.name == "CMakeLists.txt") ../.) - (optional withTests [../tests ../hyprtester]) - ])); + ( + fs.unions (flatten [ + ../assets/hyprland-portals.conf + ../assets/install + ../hyprctl + ../hyprland.pc.in + ../hyprpm + ../LICENSE + ../protocols + ../src + ../start + ../systemd + ../VERSION + (fs.fileFilter (file: file.hasExt "1") ../docs) + (fs.fileFilter (file: file.hasExt "conf" || file.hasExt "in") ../example) + (fs.fileFilter (file: file.hasExt "sh") ../scripts) + (fs.fileFilter (file: file.name == "CMakeLists.txt") ../.) + (optional withTests [ + ../tests + ../hyprtester + ]) + ]) + ); }; postPatch = '' @@ -117,7 +143,10 @@ in GIT_COMMITS = revCount; GIT_COMMIT_DATE = date; GIT_COMMIT_HASH = commit; - GIT_DIRTY = if (commit == "") then "clean" else "dirty"; + GIT_DIRTY = + if (commit == "") + then "clean" + else "dirty"; GIT_TAG = "v${trim (readFile "${finalAttrs.src}/VERSION")}"; }; @@ -145,6 +174,7 @@ in cairo git glaze-hyprland + glslang gtest hyprcursor hyprgraphics @@ -152,12 +182,14 @@ in hyprlang hyprutils hyprwire + lcms2 libdrm + libgbm libGL libinput libuuid + libxcursor libxkbcommon - libgbm muparser pango pciutils @@ -167,16 +199,15 @@ in wayland wayland-protocols wayland-scanner - xorg.libXcursor ] (optionals customStdenv.hostPlatform.isBSD [epoll-shim]) (optionals customStdenv.hostPlatform.isMusl [libexecinfo]) (optionals enableXWayland [ - xorg.libxcb - xorg.libXdmcp - xorg.xcbutilerrors - xorg.xcbutilrenderutil - xorg.xcbutilwm + libxcb + libxcb-errors + libxcb-render-util + libxcb-wm + libxdmcp xwayland ]) (optional withSystemd systemd) @@ -199,7 +230,6 @@ in "NO_SYSTEMD" = !withSystemd; "CMAKE_DISABLE_PRECOMPILE_HEADERS" = true; "NO_UWSM" = !withSystemd; - "NO_HYPRPM" = true; "TRACY_ENABLE" = false; "WITH_TESTS" = withTests; }; @@ -213,23 +243,26 @@ in postInstall = '' ${optionalString wrapRuntimeDeps '' wrapProgram $out/bin/Hyprland \ - --suffix PATH : ${makeBinPath [ - binutils - hyprland-guiutils - pciutils - pkgconf - ]} + --suffix PATH : ${ + makeBinPath [ + binutils + hyprland-guiutils + pciutils + pkgconf + ] + } ''} ${optionalString withTests '' install hyprtester/pointer-warp -t $out/bin install hyprtester/pointer-scroll -t $out/bin + install hyprtester/shortcut-inhibitor -t $out/bin install hyprland_gtests -t $out/bin install hyprtester/child-window -t $out/bin ''} ''; - passthru.providedSessions = ["hyprland"]; + passthru.providedSessions = ["hyprland"] ++ optionals withSystemd ["hyprland-uwsm"]; meta = { homepage = "https://github.com/hyprwm/Hyprland"; diff --git a/nix/formatter.nix b/nix/formatter.nix index 66721c2c..ac340ae2 100644 --- a/nix/formatter.nix +++ b/nix/formatter.nix @@ -2,7 +2,7 @@ writeShellApplication, deadnix, statix, - alejandra, + nixfmt, llvmPackages_19, fd, }: @@ -11,7 +11,7 @@ writeShellApplication { runtimeInputs = [ deadnix statix - alejandra + nixfmt llvmPackages_19.clang-tools fd ]; @@ -24,14 +24,14 @@ writeShellApplication { nix_format() { if [ "$*" = 0 ]; then fd '.*\.nix' . -E "$excludes" -x statix fix -- {} \; - fd '.*\.nix' . -E "$excludes" -X deadnix -e -- {} \; -X alejandra {} \; + fd '.*\.nix' . -E "$excludes" -X deadnix -e -- {} \; -X nixfmt {} \; elif [ -d "$1" ]; then fd '.*\.nix' "$1" -E "$excludes" -i -x statix fix -- {} \; - fd '.*\.nix' "$1" -E "$excludes" -i -X deadnix -e -- {} \; -X alejandra {} \; + fd '.*\.nix' "$1" -E "$excludes" -i -X deadnix -e -- {} \; -X nixfmt {} \; else statix fix -- "$1" deadnix -e "$1" - alejandra "$1" + nixfmt "$1" fi } diff --git a/nix/hm-module.nix b/nix/hm-module.nix index e3c788d0..948b8217 100644 --- a/nix/hm-module.nix +++ b/nix/hm-module.nix @@ -1,13 +1,15 @@ -self: { - config, +self: +{ lib, pkgs, ... -}: let +}: +let inherit (pkgs.stdenv.hostPlatform) system; package = self.packages.${system}.default; -in { +in +{ config = { wayland.windowManager.hyprland.package = lib.mkDefault package; }; diff --git a/nix/lib.nix b/nix/lib.nix index ca3aadee..54d23440 100644 --- a/nix/lib.nix +++ b/nix/lib.nix @@ -1,4 +1,5 @@ -lib: let +lib: +let inherit (lib) attrNames filterAttrs @@ -17,7 +18,7 @@ lib: let This function takes a nested attribute set and converts it into Hyprland-compatible configuration syntax, supporting top, bottom, and regular command sections. - + Commands are flattened using the `flattenAttrs` function, and attributes are formatted as `key = value` pairs. Lists are expanded as duplicate keys to match Hyprland's expected format. @@ -81,44 +82,51 @@ lib: let ::: */ - toHyprlang = { - topCommandsPrefixes ? ["$" "bezier"], - bottomCommandsPrefixes ? [], - }: attrs: let - toHyprlang' = attrs: let - # Specially configured `toKeyValue` generator with support for duplicate keys - # and a legible key-value separator. - mkCommands = generators.toKeyValue { - mkKeyValue = generators.mkKeyValueDefault {} " = "; - listsAsDuplicateKeys = true; - indent = ""; # No indent, since we don't have nesting - }; + toHyprlang = + { + topCommandsPrefixes ? [ + "$" + "bezier" + ], + bottomCommandsPrefixes ? [ ], + }: + attrs: + let + toHyprlang' = + attrs: + let + # Specially configured `toKeyValue` generator with support for duplicate keys + # and a legible key-value separator. + mkCommands = generators.toKeyValue { + mkKeyValue = generators.mkKeyValueDefault { } " = "; + listsAsDuplicateKeys = true; + indent = ""; # No indent, since we don't have nesting + }; - # Flatten the attrset, combining keys in a "path" like `"a:b:c" = "x"`. - # Uses `flattenAttrs` with a colon separator. - commands = flattenAttrs (p: k: "${p}:${k}") attrs; + # Flatten the attrset, combining keys in a "path" like `"a:b:c" = "x"`. + # Uses `flattenAttrs` with a colon separator. + commands = flattenAttrs (p: k: "${p}:${k}") attrs; - # General filtering function to check if a key starts with any prefix in a given list. - filterCommands = list: n: - foldl (acc: prefix: acc || hasPrefix prefix n) false list; + # General filtering function to check if a key starts with any prefix in a given list. + filterCommands = list: n: foldl (acc: prefix: acc || hasPrefix prefix n) false list; - # Partition keys into top commands and the rest - result = partition (filterCommands topCommandsPrefixes) (attrNames commands); - topCommands = filterAttrs (n: _: builtins.elem n result.right) commands; - remainingCommands = removeAttrs commands result.right; + # Partition keys into top commands and the rest + result = partition (filterCommands topCommandsPrefixes) (attrNames commands); + topCommands = filterAttrs (n: _: builtins.elem n result.right) commands; + remainingCommands = removeAttrs commands result.right; - # Partition remaining commands into bottom commands and regular commands - result2 = partition (filterCommands bottomCommandsPrefixes) result.wrong; - bottomCommands = filterAttrs (n: _: builtins.elem n result2.right) remainingCommands; - regularCommands = removeAttrs remainingCommands result2.right; + # Partition remaining commands into bottom commands and regular commands + result2 = partition (filterCommands bottomCommandsPrefixes) result.wrong; + bottomCommands = filterAttrs (n: _: builtins.elem n result2.right) remainingCommands; + regularCommands = removeAttrs remainingCommands result2.right; + in + # Concatenate strings from mapping `mkCommands` over top, regular, and bottom commands. + concatMapStrings mkCommands [ + topCommands + regularCommands + bottomCommands + ]; in - # Concatenate strings from mapping `mkCommands` over top, regular, and bottom commands. - concatMapStrings mkCommands [ - topCommands - regularCommands - bottomCommands - ]; - in toHyprlang' attrs; /** @@ -131,7 +139,7 @@ lib: let Configuration: * `pred` - A function `(string -> string -> string)` defining how keys should be concatenated. - + # Inputs Structured function argument: @@ -139,7 +147,7 @@ lib: let : pred (required) : A function that determines how parent and child keys should be combined into a single key. It takes a `prefix` (parent key) and `key` (current key) and returns the joined key. - + Value: : The nested attribute set to be flattened. @@ -174,26 +182,21 @@ lib: let ``` ::: - */ - flattenAttrs = pred: attrs: let - flattenAttrs' = prefix: attrs: - builtins.foldl' ( - acc: key: let - value = attrs.${key}; - newKey = - if prefix == "" - then key - else pred prefix key; - in - acc - // ( - if builtins.isAttrs value - then flattenAttrs' newKey value - else {"${newKey}" = value;} - ) - ) {} (builtins.attrNames attrs); - in + flattenAttrs = + pred: attrs: + let + flattenAttrs' = + prefix: attrs: + builtins.foldl' ( + acc: key: + let + value = attrs.${key}; + newKey = if prefix == "" then key else pred prefix key; + in + acc // (if builtins.isAttrs value then flattenAttrs' newKey value else { "${newKey}" = value; }) + ) { } (builtins.attrNames attrs); + in flattenAttrs' "" attrs; in { diff --git a/nix/module.nix b/nix/module.nix index 91705347..32263943 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -1,18 +1,21 @@ -inputs: { +inputs: +{ config, lib, pkgs, ... -}: let +}: +let inherit (pkgs.stdenv.hostPlatform) system; selflib = import ./lib.nix lib; cfg = config.programs.hyprland; -in { +in +{ options = { programs.hyprland = { plugins = lib.mkOption { type = with lib.types; listOf (either package path); - default = []; + default = [ ]; description = '' List of Hyprland plugins to use. Can either be packages or absolute plugin paths. @@ -20,23 +23,25 @@ in { }; settings = lib.mkOption { - type = with lib.types; let - valueType = - nullOr (oneOf [ - bool - int - float - str - path - (attrsOf valueType) - (listOf valueType) - ]) - // { - description = "Hyprland configuration value"; - }; - in + type = + with lib.types; + let + valueType = + nullOr (oneOf [ + bool + int + float + str + path + (attrsOf valueType) + (listOf valueType) + ]) + // { + description = "Hyprland configuration value"; + }; + in valueType; - default = {}; + default = { }; description = '' Hyprland configuration written in Nix. Entries with the same key should be written as lists. Variables' and colors' names should be @@ -92,8 +97,15 @@ in { topPrefixes = lib.mkOption { type = with lib.types; listOf str; - default = ["$" "bezier"]; - example = ["$" "bezier" "source"]; + default = [ + "$" + "bezier" + ]; + example = [ + "$" + "bezier" + "source" + ]; description = '' List of prefix of attributes to put at the top of the config. ''; @@ -101,8 +113,8 @@ in { bottomPrefixes = lib.mkOption { type = with lib.types; listOf str; - default = []; - example = ["source"]; + default = [ ]; + example = [ "source" ]; description = '' List of prefix of attributes to put at the bottom of the config. ''; @@ -117,35 +129,36 @@ in { }; } (lib.mkIf cfg.enable { - environment.etc."xdg/hypr/hyprland.conf" = let - shouldGenerate = cfg.extraConfig != "" || cfg.settings != {} || cfg.plugins != []; + environment.etc."xdg/hypr/hyprland.conf" = + let + shouldGenerate = cfg.extraConfig != "" || cfg.settings != { } || cfg.plugins != [ ]; - pluginsToHyprlang = plugins: - selflib.toHyprlang { - topCommandsPrefixes = cfg.topPrefixes; - bottomCommandsPrefixes = cfg.bottomPrefixes; - } - { - "exec-once" = let - mkEntry = entry: - if lib.types.package.check entry - then "${entry}/lib/lib${entry.pname}.so" - else entry; - hyprctl = lib.getExe' config.programs.hyprland.package "hyprctl"; - in - map (p: "${hyprctl} plugin load ${mkEntry p}") cfg.plugins; - }; - in - lib.mkIf shouldGenerate { - text = - lib.optionalString (cfg.plugins != []) - (pluginsToHyprlang cfg.plugins) - + lib.optionalString (cfg.settings != {}) - (selflib.toHyprlang { + pluginsToHyprlang = + _plugins: + selflib.toHyprlang + { topCommandsPrefixes = cfg.topPrefixes; bottomCommandsPrefixes = cfg.bottomPrefixes; } - cfg.settings) + { + "exec-once" = + let + mkEntry = + entry: if lib.types.package.check entry then "${entry}/lib/lib${entry.pname}.so" else entry; + hyprctl = lib.getExe' config.programs.hyprland.package "hyprctl"; + in + map (p: "${hyprctl} plugin load ${mkEntry p}") cfg.plugins; + }; + in + lib.mkIf shouldGenerate { + text = + lib.optionalString (cfg.plugins != [ ]) (pluginsToHyprlang cfg.plugins) + + lib.optionalString (cfg.settings != { }) ( + selflib.toHyprlang { + topCommandsPrefixes = cfg.topPrefixes; + bottomCommandsPrefixes = cfg.bottomPrefixes; + } cfg.settings + ) + lib.optionalString (cfg.extraConfig != "") cfg.extraConfig; }; }) diff --git a/nix/overlays.nix b/nix/overlays.nix index fdb3e652..0d157701 100644 --- a/nix/overlays.nix +++ b/nix/overlays.nix @@ -2,20 +2,27 @@ self, lib, inputs, -}: let - mkDate = longDate: (lib.concatStringsSep "-" [ - (builtins.substring 0 4 longDate) - (builtins.substring 4 2 longDate) - (builtins.substring 6 2 longDate) - ]); +}: +let + mkDate = + longDate: + (lib.concatStringsSep "-" [ + (builtins.substring 0 4 longDate) + (builtins.substring 4 2 longDate) + (builtins.substring 6 2 longDate) + ]); ver = lib.removeSuffix "\n" (builtins.readFile ../VERSION); -in { +in +{ # Contains what a user is most likely to care about: # Hyprland itself, XDPH and the Share Picker. - default = lib.composeManyExtensions (with self.overlays; [ - hyprland-packages - hyprland-extras - ]); + default = lib.composeManyExtensions ( + with self.overlays; + [ + hyprland-packages + hyprland-extras + ] + ); # Packages for variations of Hyprland, dependencies included. hyprland-packages = lib.composeManyExtensions [ @@ -33,49 +40,45 @@ in { self.overlays.glaze # Hyprland packages themselves - (final: _prev: let - date = mkDate (self.lastModifiedDate or "19700101"); - version = "${ver}+date=${date}_${self.shortRev or "dirty"}"; - in { - hyprland = final.callPackage ./default.nix { - stdenv = final.gcc15Stdenv; - commit = self.rev or ""; - revCount = self.sourceInfo.revCount or ""; - inherit date version; - }; - hyprland-unwrapped = final.hyprland.override {wrapRuntimeDeps = false;}; + ( + final: _prev: + let + date = mkDate (self.lastModifiedDate or "19700101"); + version = "${ver}+date=${date}_${self.shortRev or "dirty"}"; + in + { + hyprland = final.callPackage ./default.nix { + stdenv = final.gcc15Stdenv; + commit = self.rev or ""; + revCount = self.sourceInfo.revCount or ""; + inherit date version; + }; + hyprland-unwrapped = final.hyprland.override { wrapRuntimeDeps = false; }; - hyprland-with-tests = final.hyprland.override {withTests = true;}; + hyprland-with-tests = final.hyprland.override { withTests = true; }; - hyprland-with-hyprtester = - builtins.trace '' + hyprland-with-hyprtester = builtins.trace '' hyprland-with-hyprtester was removed. Please use the hyprland package. Hyprtester is always built now. - '' - final.hyprland; + '' final.hyprland; - # deprecated packages - hyprland-legacy-renderer = - builtins.trace '' + # deprecated packages + hyprland-legacy-renderer = builtins.trace '' hyprland-legacy-renderer was removed. Please use the hyprland package. Legacy renderer is no longer supported. - '' - final.hyprland; + '' final.hyprland; - hyprland-nvidia = - builtins.trace '' + hyprland-nvidia = builtins.trace '' hyprland-nvidia was removed. Please use the hyprland package. Nvidia patches are no longer needed. - '' - final.hyprland; + '' final.hyprland; - hyprland-hidpi = - builtins.trace '' + hyprland-hidpi = builtins.trace '' hyprland-hidpi was removed. Please use the hyprland package. For more information, refer to https://wiki.hypr.land/Configuring/XWayland. - '' - final.hyprland; - }) + '' final.hyprland; + } + ) ]; # Debug @@ -83,10 +86,10 @@ in { # Dependencies self.overlays.hyprland-packages - (final: prev: { - aquamarine = prev.aquamarine.override {debug = true;}; - hyprutils = prev.hyprutils.override {debug = true;}; - hyprland-debug = prev.hyprland.override {debug = true;}; + (_final: prev: { + aquamarine = prev.aquamarine.override { debug = true; }; + hyprutils = prev.hyprutils.override { debug = true; }; + hyprland-debug = prev.hyprland.override { debug = true; }; }) ]; @@ -100,21 +103,23 @@ in { # 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="; - }; + udis86-hyprland = prev.udis86.overrideAttrs ( + _self: _super: { + src = final.fetchFromGitHub { + owner = "canihavesomecoffee"; + repo = "udis86"; + rev = "5336633af70f3917760a6d441ff02d93477b0c86"; + hash = "sha256-HifdUQPGsKQKQprByeIznvRLONdOXeolOsU5nkwIv3g="; + }; - patches = []; - }); + patches = [ ]; + } + ); }; # Even though glaze itself disables it by default, nixpkgs sets ENABLE_SSL set to true. # Since we don't include openssl, the build failes without the `enableSSL = false;` override - glaze = final: prev: { + glaze = _final: prev: { glaze-hyprland = prev.glaze.override { enableSSL = false; enableInterop = false; diff --git a/nix/tests/default.nix b/nix/tests/default.nix index bdb3fe7c..25c4077b 100644 --- a/nix/tests/default.nix +++ b/nix/tests/default.nix @@ -1,68 +1,75 @@ -inputs: pkgs: let +inputs: pkgs: +let flake = inputs.self.packages.${pkgs.stdenv.hostPlatform.system}; hyprland = flake.hyprland-with-tests; -in { +in +{ tests = pkgs.testers.runNixOSTest { name = "hyprland-tests"; - nodes.machine = {pkgs, ...}: { - environment.systemPackages = with pkgs; [ - # Programs needed for tests - jq - kitty - wl-clipboard - xorg.xeyes - ]; + nodes.machine = + { pkgs, ... }: + { + environment.systemPackages = with pkgs; [ + # Programs needed for tests + jq + kitty + wl-clipboard + xeyes + ]; - # Enabled by default for some reason - services.speechd.enable = false; + # Enabled by default for some reason + services.speechd.enable = false; - environment.variables = { - "AQ_TRACE" = "1"; - "HYPRLAND_TRACE" = "1"; - "XDG_RUNTIME_DIR" = "/tmp"; - "XDG_CACHE_HOME" = "/tmp"; - "KITTY_CONFIG_DIRECTORY" = "/etc/kitty"; - }; - - environment.etc."kitty/kitty.conf".text = '' - confirm_os_window_close 0 - ''; - - programs.hyprland = { - enable = true; - package = hyprland; - # We don't need portals in this test, so we don't set portalPackage - }; - - # Test configuration - environment.etc."test.conf".source = "${hyprland}/share/hypr/test.conf"; - - # Disable portals - xdg.portal.enable = pkgs.lib.mkForce false; - - # Autologin root into tty - services.getty.autologinUser = "alice"; - - system.stateVersion = "24.11"; - - users.users.alice = { - isNormalUser = true; - }; - - virtualisation = { - cores = 4; - # Might crash with less - memorySize = 8192; - resolution = { - x = 1920; - y = 1080; + environment.variables = { + "AQ_TRACE" = "1"; + "HYPRLAND_TRACE" = "1"; + "XDG_RUNTIME_DIR" = "/tmp"; + "XDG_CACHE_HOME" = "/tmp"; + "KITTY_CONFIG_DIRECTORY" = "/etc/kitty"; }; - # Doesn't seem to do much, thought it would fix XWayland crashing - qemu.options = ["-vga none -device virtio-gpu-pci"]; + environment.etc."kitty/kitty.conf".text = '' + confirm_os_window_close 0 + remember_window_size no + initial_window_width 640 + initial_window_height 400 + ''; + + programs.hyprland = { + enable = true; + package = hyprland; + # We don't need portals in this test, so we don't set portalPackage + }; + + # Test configuration + environment.etc."test.conf".source = "${hyprland}/share/hypr/test.conf"; + + # Disable portals + xdg.portal.enable = pkgs.lib.mkForce false; + + # Autologin root into tty + services.getty.autologinUser = "alice"; + + system.stateVersion = "24.11"; + + users.users.alice = { + isNormalUser = true; + }; + + virtualisation = { + cores = 4; + # Might crash with less + memorySize = 8192; + resolution = { + x = 1920; + y = 1080; + }; + + # Doesn't seem to do much, thought it would fix XWayland crashing + qemu.options = [ "-vga none -device virtio-gpu-pci" ]; + }; }; - }; testScript = '' # Wait for tty to be up diff --git a/scripts/generateShaderIncludes.sh b/scripts/generateShaderIncludes.sh index 20c78e9d..c9419031 100755 --- a/scripts/generateShaderIncludes.sh +++ b/scripts/generateShaderIncludes.sh @@ -15,7 +15,7 @@ echo 'static const std::map SHADERS = {' >> ./src/rend for filename in `ls ${SHADERS_SRC}`; do echo "-- ${filename}" - { echo 'R"#('; cat ${SHADERS_SRC}/${filename}; echo ')#"'; } > ./src/render/shaders/${filename}.inc + { echo -n 'R"#('; cat ${SHADERS_SRC}/${filename}; echo ')#"'; } > ./src/render/shaders/${filename}.inc echo "{\"${filename}\"," >> ./src/render/shaders/Shaders.hpp echo "#include \"./${filename}.inc\"" >> ./src/render/shaders/Shaders.hpp echo "}," >> ./src/render/shaders/Shaders.hpp diff --git a/src/Compositor.cpp b/src/Compositor.cpp index 772f87fe..b0fc1545 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -7,6 +7,7 @@ #include "desktop/state/FocusState.hpp" #include "desktop/history/WindowHistoryTracker.hpp" #include "desktop/history/WorkspaceHistoryTracker.hpp" +#include "desktop/view/Group.hpp" #include "helpers/Splashes.hpp" #include "config/ConfigValue.hpp" #include "config/ConfigWatcher.hpp" @@ -19,6 +20,7 @@ #include "managers/ANRManager.hpp" #include "managers/eventLoop/EventLoopManager.hpp" #include "managers/permissions/DynamicPermissionManager.hpp" +#include "managers/screenshare/ScreenshareManager.hpp" #include #include #include @@ -45,6 +47,7 @@ #include "protocols/core/Compositor.hpp" #include "protocols/core/Subcompositor.hpp" #include "desktop/view/LayerSurface.hpp" +#include "layout/space/Space.hpp" #include "render/Renderer.hpp" #include "xwayland/XWayland.hpp" #include "helpers/ByteOperations.hpp" @@ -60,9 +63,7 @@ #include "managers/animation/AnimationManager.hpp" #include "managers/animation/DesktopAnimationManager.hpp" #include "managers/EventManager.hpp" -#include "managers/HookSystemManager.hpp" #include "managers/ProtocolManager.hpp" -#include "managers/LayoutManager.hpp" #include "managers/WelcomeManager.hpp" #include "render/AsyncResourceGatherer.hpp" #include "plugins/PluginSystem.hpp" @@ -71,6 +72,9 @@ #include "debug/HyprDebugOverlay.hpp" #include "helpers/MonitorFrameScheduler.hpp" #include "i18n/Engine.hpp" +#include "layout/LayoutManager.hpp" +#include "layout/target/WindowTarget.hpp" +#include "event/EventBus.hpp" #include #include @@ -103,11 +107,6 @@ static void handleUnrecoverableSignal(int sig) { signal(SIGABRT, SIG_DFL); signal(SIGSEGV, SIG_DFL); - if (g_pHookSystem && g_pHookSystem->m_currentEventPlugin) { - longjmp(g_pHookSystem->m_hookFaultJumpBuf, 1); - return; - } - // Kill the program if the crash-reporter is caught in a deadlock. signal(SIGALRM, [](int _) { char const* msg = "\nCrashReporter exceeded timeout, forcefully exiting\n"; @@ -283,7 +282,6 @@ static bool filterGlobals(const wl_client* client, const wl_global* global, void // void CCompositor::initServer(std::string socketName, int socketFd) { if (m_onlyConfigVerification) { - g_pHookSystem = makeUnique(); g_pKeybindManager = makeUnique(); g_pAnimationManager = makeUnique(); g_pConfigManager = makeUnique(); @@ -372,11 +370,11 @@ void CCompositor::initServer(std::string socketName, int socketFd) { return ret == 0 && cap != 0; }; - if ((m_drm.syncobjSupport = syncObjSupport(m_drm.fd))) - Log::logger->log(Log::DEBUG, "DRM DisplayNode syncobj timeline support: {}", m_drm.syncobjSupport ? "yes" : "no"); + m_drm.syncobjSupport = syncObjSupport(m_drm.fd); + Log::logger->log(Log::DEBUG, "DRM DisplayNode syncobj timeline support: {}", m_drm.syncobjSupport ? "yes" : "no"); - if ((m_drmRenderNode.syncObjSupport = syncObjSupport(m_drmRenderNode.fd))) - Log::logger->log(Log::DEBUG, "DRM RenderNode syncobj timeline support: {}", m_drmRenderNode.syncObjSupport ? "yes" : "no"); + m_drmRenderNode.syncObjSupport = syncObjSupport(m_drmRenderNode.fd); + Log::logger->log(Log::DEBUG, "DRM RenderNode syncobj timeline support: {}", m_drmRenderNode.syncObjSupport ? "yes" : "no"); if (!m_drm.syncobjSupport && !m_drmRenderNode.syncObjSupport) Log::logger->log(Log::DEBUG, "DRM no syncobj support, disabling explicit sync"); @@ -483,6 +481,10 @@ void CCompositor::initAllSignals() { m_sessionActive = true; + // Reset animation tick state to avoid stale timer issues after suspend/wake + if (g_pAnimationManager) + g_pAnimationManager->resetTickState(); + for (auto const& m : m_monitors) { scheduleFrameForMonitor(m); m->applyMonitorRule(&m->m_activeMonitorRule, true); @@ -586,11 +588,10 @@ void CCompositor::cleanup() { g_pHyprRenderer.reset(); g_pHyprOpenGL.reset(); g_pConfigManager.reset(); - g_pLayoutManager.reset(); + g_layoutManager.reset(); g_pHyprError.reset(); g_pConfigManager.reset(); g_pKeybindManager.reset(); - g_pHookSystem.reset(); g_pXWaylandManager.reset(); g_pPointerManager.reset(); g_pSeatManager.reset(); @@ -619,9 +620,6 @@ void CCompositor::initManagers(eManagersInitStage stage) { Log::logger->log(Log::DEBUG, "Creating the EventLoopManager!"); g_pEventLoopManager = makeUnique(m_wlDisplay, m_wlEventLoop); - Log::logger->log(Log::DEBUG, "Creating the HookSystem!"); - g_pHookSystem = makeUnique(); - Log::logger->log(Log::DEBUG, "Creating the KeybindManager!"); g_pKeybindManager = makeUnique(); @@ -638,7 +636,7 @@ void CCompositor::initManagers(eManagersInitStage stage) { g_pHyprError = makeUnique(); Log::logger->log(Log::DEBUG, "Creating the LayoutManager!"); - g_pLayoutManager = makeUnique(); + g_layoutManager = makeUnique(); Log::logger->log(Log::DEBUG, "Creating the TokenManager!"); g_pTokenManager = makeUnique(); @@ -792,7 +790,8 @@ void CCompositor::startCompositor() { createLockFile(); - EMIT_HOOK_EVENT("ready", nullptr); + Event::bus()->m_events.ready.emit(); + if (m_watchdogWriteFd.isValid()) write(m_watchdogWriteFd.get(), "vax", 3); @@ -872,7 +871,7 @@ PHLMONITOR CCompositor::getMonitorFromVector(const Vector2D& point) { void CCompositor::removeWindowFromVectorSafe(PHLWINDOW pWindow) { if (!pWindow->m_fadingOut) { - EMIT_HOOK_EVENT("destroyWindow", pWindow); + Event::bus()->m_events.window.destroy.emit(pWindow); std::erase_if(m_windows, [&](SP& el) { return el == pWindow; }); std::erase_if(m_windowsFadingOut, [&](PHLWINDOWREF el) { return el.lock() == pWindow; }); @@ -1034,6 +1033,8 @@ PHLWINDOW CCompositor::vectorToWindowUnified(const Vector2D& pos, uint8_t proper if (!w->m_isFloating && w->m_isMapped && w->workspaceID() == WSPID && !w->isHidden() && !w->m_X11ShouldntFocus && !w->m_ruleApplicator->noFocus().valueOrDefault() && w != pIgnoreWindow && !isShadowedByModal(w)) { CBox box = (properties & Desktop::View::USE_PROP_TILED) ? w->getWindowBoxUnified(properties) : CBox{w->m_position, w->m_size}; + if ((properties & Desktop::View::INPUT_EXTENTS) && BORDER_GRAB_AREA > 0 && !w->isX11OverrideRedirect()) + box.expand(BORDER_GRAB_AREA); if (box.containsPoint(pos)) return w; } @@ -1179,7 +1180,7 @@ PHLWINDOW CCompositor::getWindowFromSurface(SP pSurface) { const auto VIEW = pSurface->m_hlSurface->view(); - if (VIEW->type() != Desktop::View::VIEW_TYPE_WINDOW) + if (!VIEW || VIEW->type() != Desktop::View::VIEW_TYPE_WINDOW) return nullptr; return dynamicPointerCast(VIEW); @@ -1371,8 +1372,8 @@ void CCompositor::addToFadingOutSafe(PHLWINDOW pWindow) { m_windowsFadingOut.emplace_back(pWindow); } -PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, char dir) { - if (!isDirection(dir)) +PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, Math::eDirection dir) { + if (dir == Math::DIRECTION_DEFAULT) return nullptr; const auto PMONITOR = pWindow->m_monitor.lock(); @@ -1389,8 +1390,8 @@ PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, char dir) { return getWindowInDirection(WINDOWIDEALBB, PWORKSPACE, dir, pWindow, pWindow->m_isFloating); } -PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, char dir, PHLWINDOW ignoreWindow, bool useVectorAngles) { - if (!isDirection(dir)) +PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, Math::eDirection dir, PHLWINDOW ignoreWindow, bool useVectorAngles) { + if (dir == Math::DIRECTION_DEFAULT) return nullptr; // 0 -> history, 1 -> shared length @@ -1404,6 +1405,35 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks PHLWINDOW leaderWindow = nullptr; if (!useVectorAngles) { + // helper to check if two rectangles are adjacent along an axis, considering slight overlaps. + // returns true if: STICKS (delta <= 2) OR rectangles overlap but no more than 50% of the smaller dimension. + static auto isAdjacent = [](const double aMin, const double aMax, const double bMin, const double bMax) -> bool { + constexpr double STICK_THRESHOLD = 2.0; + constexpr double MAX_OVERLAP_RATIO = 0.5; + + const double aEdge = aMin; + const double bEdge = bMax; + const double delta = aEdge - bEdge; + + // old STICKS check for 2px + if (std::abs(delta) < STICK_THRESHOLD) + return true; + + if (delta >= 0) + return false; + + const double overlap = -delta; + const double sizeA = aMax - aMin; + const double sizeB = bMax - bMin; + + // reject if one rectangle fully contains the other + if ((bMin <= aMin && bMax >= aMax) || (aMin <= bMin && aMax >= bMax)) + return false; + + // accept if overlap is at most 50% of the smaller dimension + return overlap <= std::min(sizeA, sizeB) * MAX_OVERLAP_RATIO; + }; + for (auto const& w : m_windows) { if (w == ignoreWindow || !w->m_workspace || !w->m_isMapped || w->isHidden() || (!w->isFullscreen() && w->m_isFloating) || !w->m_workspace->isVisible()) continue; @@ -1425,28 +1455,23 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks double intersectLength = -1; switch (dir) { - case 'l': - if (STICKS(POSA.x, POSB.x + SIZEB.x)) { + case Math::DIRECTION_LEFT: + if (isAdjacent(POSA.x, POSA.x + SIZEA.x, POSB.x, POSB.x + SIZEB.x)) intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y)); - } break; - case 'r': - if (STICKS(POSA.x + SIZEA.x, POSB.x)) { + case Math::DIRECTION_RIGHT: + if (isAdjacent(POSB.x, POSB.x + SIZEB.x, POSA.x, POSA.x + SIZEA.x)) intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y)); - } break; - case 't': - case 'u': - if (STICKS(POSA.y, POSB.y + SIZEB.y)) { + case Math::DIRECTION_UP: + if (isAdjacent(POSA.y, POSA.y + SIZEA.y, POSB.y, POSB.y + SIZEB.y)) intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x)); - } break; - case 'b': - case 'd': - if (STICKS(POSA.y + SIZEA.y, POSB.y)) { + case Math::DIRECTION_DOWN: + if (isAdjacent(POSB.y, POSB.y + SIZEB.y, POSA.y, POSA.y + SIZEA.y)) intersectLength = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x)); - } break; + default: break; } if (*PMETHOD == 0 /* history */) { @@ -1475,12 +1500,8 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks } } } else { - if (dir == 'u') - dir = 't'; - if (dir == 'd') - dir = 'b'; - - static const std::unordered_map VECTORS = {{'r', {1, 0}}, {'t', {0, -1}}, {'b', {0, 1}}, {'l', {-1, 0}}}; + static const std::unordered_map VECTORS = { + {Math::DIRECTION_RIGHT, {1, 0}}, {Math::DIRECTION_UP, {0, -1}}, {Math::DIRECTION_DOWN, {0, 1}}, {Math::DIRECTION_LEFT, {-1, 0}}}; // auto vectorAngles = [](const Vector2D& a, const Vector2D& b) -> double { @@ -1632,38 +1653,28 @@ bool CCompositor::isPointOnReservedArea(const Vector2D& point, const PHLMONITOR return VECNOTINRECT(point, box.x, box.y, box.x + box.w, box.y + box.h); } -CBox CCompositor::calculateX11WorkArea() { +std::optional CCompositor::calculateX11WorkArea() { static auto PXWLFORCESCALEZERO = CConfigValue("xwayland:force_zero_scaling"); - CBox workbox = {0, 0, 0, 0}; - bool firstMonitor = true; + // We more than likely won't be able to calculate one + // and even if we could this is minor + if (m_monitors.size() > 1 || m_monitors.empty()) + return std::nullopt; - for (const auto& monitor : m_monitors) { - // we ignore monitor->m_position on purpose - CBox box = monitor->logicalBoxMinusReserved().translate(-monitor->m_position); - if ((*PXWLFORCESCALEZERO)) - box.scale(monitor->m_scale); + const auto M = m_monitors.front(); - if (firstMonitor) { - firstMonitor = false; - workbox = box; - } else { - // if this monitor creates a different workbox than previous monitor, we remove the _NET_WORKAREA property all together - if ((std::abs(box.x - workbox.x) > 3) || (std::abs(box.y - workbox.y) > 3) || (std::abs(box.w - workbox.w) > 3) || (std::abs(box.h - workbox.h) > 3)) { - workbox = {0, 0, 0, 0}; - break; - } - } - } + // we ignore monitor->m_position on purpose + CBox box = M->logicalBoxMinusReserved().translate(-M->m_position); + if ((*PXWLFORCESCALEZERO)) + box.scale(M->m_scale); - // returning 0, 0 will remove the _NET_WORKAREA property - return workbox; + return box.translate(M->m_xwaylandPosition); } -PHLMONITOR CCompositor::getMonitorInDirection(const char& dir) { +PHLMONITOR CCompositor::getMonitorInDirection(Math::eDirection dir) { return getMonitorInDirection(Desktop::focusState()->monitor(), dir); } -PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const char& dir) { +PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, Math::eDirection dir) { if (!pSourceMonitor) return nullptr; @@ -1680,7 +1691,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const c const auto POSB = m->m_position; const auto SIZEB = m->m_size; switch (dir) { - case 'l': + case Math::DIRECTION_LEFT: if (STICKS(POSA.x, POSB.x + SIZEB.x)) { const auto INTERSECTLEN = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y)); if (INTERSECTLEN > longestIntersect) { @@ -1689,7 +1700,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const c } } break; - case 'r': + case Math::DIRECTION_RIGHT: if (STICKS(POSA.x + SIZEA.x, POSB.x)) { const auto INTERSECTLEN = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y)); if (INTERSECTLEN > longestIntersect) { @@ -1698,8 +1709,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const c } } break; - case 't': - case 'u': + case Math::DIRECTION_UP: if (STICKS(POSA.y, POSB.y + SIZEB.y)) { const auto INTERSECTLEN = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x)); if (INTERSECTLEN > longestIntersect) { @@ -1708,8 +1718,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const c } } break; - case 'b': - case 'd': + case Math::DIRECTION_DOWN: if (STICKS(POSA.y + SIZEA.y, POSB.y)) { const auto INTERSECTLEN = std::max(0.0, std::min(POSA.x + SIZEA.x, POSB.x + SIZEB.x) - std::max(POSA.x, POSB.x)); if (INTERSECTLEN > longestIntersect) { @@ -1718,6 +1727,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const c } } break; + default: break; } } @@ -1773,7 +1783,7 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor // additionally, move floating and fs windows manually if (w->m_isFloating) - *w->m_realPosition = w->m_realPosition->goal() - pMonitorA->m_position + pMonitorB->m_position; + w->layoutTarget()->setPositionGlobal(w->layoutTarget()->position().translate(-pMonitorA->m_position + pMonitorB->m_position)); if (w->isFullscreen()) { *w->m_realPosition = pMonitorB->m_position; @@ -1798,7 +1808,7 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor // additionally, move floating and fs windows manually if (w->m_isFloating) - *w->m_realPosition = w->m_realPosition->goal() - pMonitorB->m_position + pMonitorA->m_position; + w->layoutTarget()->setPositionGlobal(w->layoutTarget()->position().translate(-pMonitorB->m_position + pMonitorA->m_position)); if (w->isFullscreen()) { *w->m_realPosition = pMonitorA->m_position; @@ -1812,8 +1822,11 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor pMonitorA->m_activeWorkspace = PWORKSPACEB; pMonitorB->m_activeWorkspace = PWORKSPACEA; - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitorA->m_id); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitorB->m_id); + g_layoutManager->recalculateMonitor(pMonitorA); + g_layoutManager->recalculateMonitor(pMonitorB); + + g_pHyprRenderer->damageMonitor(pMonitorB); + g_pHyprRenderer->damageMonitor(pMonitorA); g_pDesktopAnimationManager->setFullscreenFadeAnimation( PWORKSPACEB, PWORKSPACEB->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT); @@ -1825,29 +1838,30 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor Desktop::focusState()->fullWindowFocus( LASTWIN ? LASTWIN : (g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(), - Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING))); + Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING)), + Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE); const auto PNEWWORKSPACE = pMonitorA->m_id == Desktop::focusState()->monitor()->m_id ? PWORKSPACEB : PWORKSPACEA; g_pEventManager->postEvent(SHyprIPCEvent{.event = "workspace", .data = PNEWWORKSPACE->m_name}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "workspacev2", .data = std::format("{},{}", PNEWWORKSPACE->m_id, PNEWWORKSPACE->m_name)}); - EMIT_HOOK_EVENT("workspace", PNEWWORKSPACE); + Event::bus()->m_events.workspace.active.emit(PNEWWORKSPACE); } - // event + // events g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspace", .data = PWORKSPACEA->m_name + "," + pMonitorB->m_name}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspacev2", .data = std::format("{},{},{}", PWORKSPACEA->m_id, PWORKSPACEA->m_name, pMonitorB->m_name)}); - EMIT_HOOK_EVENT("moveWorkspace", (std::vector{PWORKSPACEA, pMonitorB})); g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspace", .data = PWORKSPACEB->m_name + "," + pMonitorA->m_name}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspacev2", .data = std::format("{},{},{}", PWORKSPACEB->m_id, PWORKSPACEB->m_name, pMonitorA->m_name)}); - EMIT_HOOK_EVENT("moveWorkspace", (std::vector{PWORKSPACEB, pMonitorA})); + Event::bus()->m_events.workspace.moveToMonitor.emit(PWORKSPACEA, pMonitorB); + Event::bus()->m_events.workspace.moveToMonitor.emit(PWORKSPACEB, pMonitorA); } PHLMONITOR CCompositor::getMonitorFromString(const std::string& name) { if (name == "current") return Desktop::focusState()->monitor(); else if (isDirection(name)) - return getMonitorInDirection(name[0]); + return getMonitorInDirection(Math::fromChar(name[0])); else if (name[0] == '+' || name[0] == '-') { // relative @@ -1968,6 +1982,7 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo // move the workspace pWorkspace->m_monitor = pMonitor; + pWorkspace->m_space->recheckWorkArea(); pWorkspace->m_events.monitorChanged.emit(); for (auto const& w : m_windows) { @@ -1983,17 +1998,18 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo if (w->m_isMapped && !w->isHidden()) { if (POLDMON) { if (w->m_isFloating) - *w->m_realPosition = w->m_realPosition->goal() - POLDMON->m_position + pMonitor->m_position; + w->layoutTarget()->setPositionGlobal(w->layoutTarget()->position().translate(-POLDMON->m_position + pMonitor->m_position)); if (w->isFullscreen()) { *w->m_realPosition = pMonitor->m_position; *w->m_realSize = pMonitor->m_size; } } else - *w->m_realPosition = Vector2D{ - (pMonitor->m_size.x != 0) ? sc(w->m_realPosition->goal().x) % sc(pMonitor->m_size.x) : 0, - (pMonitor->m_size.y != 0) ? sc(w->m_realPosition->goal().y) % sc(pMonitor->m_size.y) : 0, - }; + w->layoutTarget()->setPositionGlobal(CBox{Vector2D{ + (pMonitor->m_size.x != 0) ? sc(w->m_realPosition->goal().x) % sc(pMonitor->m_size.x) : 0, + (pMonitor->m_size.y != 0) ? sc(w->m_realPosition->goal().y) % sc(pMonitor->m_size.y) : 0, + }, + w->layoutTarget()->position().size()}); } w->updateToplevel(); @@ -2021,7 +2037,8 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo pWorkspace->m_events.activeChanged.emit(); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitor->m_id); + g_layoutManager->recalculateMonitor(pMonitor); + g_pHyprRenderer->damageMonitor(pMonitor); g_pDesktopAnimationManager->startAnimation(pWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); pWorkspace->m_visible = true; @@ -2034,7 +2051,7 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo // finalize if (POLDMON) { - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(POLDMON->m_id); + g_layoutManager->recalculateMonitor(POLDMON); if (valid(POLDMON->m_activeWorkspace)) g_pDesktopAnimationManager->setFullscreenFadeAnimation(POLDMON->m_activeWorkspace, POLDMON->m_activeWorkspace->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : @@ -2049,7 +2066,8 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo // event g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspace", .data = pWorkspace->m_name + "," + pMonitor->m_name}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "moveworkspacev2", .data = std::format("{},{},{}", pWorkspace->m_id, pWorkspace->m_name, pMonitor->m_name)}); - EMIT_HOOK_EVENT("moveWorkspace", (std::vector{pWorkspace, pMonitor})); + + Event::bus()->m_events.workspace.moveToMonitor.emit(pWorkspace, pMonitor); } bool CCompositor::workspaceIDOutOfBounds(const WORKSPACEID& id) { @@ -2132,24 +2150,25 @@ void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, Desktop::Vie PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FULLSCREEN | Desktop::Rule::RULE_PROP_FULLSCREENSTATE_CLIENT | Desktop::Rule::RULE_PROP_FULLSCREENSTATE_INTERNAL | Desktop::Rule::RULE_PROP_ON_WORKSPACE); PWINDOW->updateDecorationValues(); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->monitorID()); + g_layoutManager->recalculateMonitor(PMONITOR); return; } - g_pLayoutManager->getCurrentLayout()->fullscreenRequestForWindow(PWINDOW, CURRENT_EFFECTIVE_MODE, EFFECTIVE_MODE); + PWORKSPACE->m_fullscreenMode = EFFECTIVE_MODE; + PWORKSPACE->m_hasFullscreenWindow = EFFECTIVE_MODE != FSMODE_NONE; + + g_layoutManager->fullscreenRequestForTarget(PWINDOW->layoutTarget(), CURRENT_EFFECTIVE_MODE, EFFECTIVE_MODE); PWINDOW->m_fullscreenState.internal = state.internal; - PWORKSPACE->m_fullscreenMode = EFFECTIVE_MODE; - PWORKSPACE->m_hasFullscreenWindow = EFFECTIVE_MODE != FSMODE_NONE; g_pEventManager->postEvent(SHyprIPCEvent{.event = "fullscreen", .data = std::to_string(sc(EFFECTIVE_MODE) != FSMODE_NONE)}); - EMIT_HOOK_EVENT("fullscreen", PWINDOW); + Event::bus()->m_events.window.fullscreen.emit(PWINDOW); PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FULLSCREEN | Desktop::Rule::RULE_PROP_FULLSCREENSTATE_CLIENT | Desktop::Rule::RULE_PROP_FULLSCREENSTATE_INTERNAL | Desktop::Rule::RULE_PROP_ON_WORKSPACE); PWINDOW->updateDecorationValues(); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->monitorID()); + g_layoutManager->recalculateMonitor(PMONITOR); // make all windows and layers on the same workspace under the fullscreen window for (auto const& w : m_windows) { @@ -2262,7 +2281,7 @@ PHLWINDOW CCompositor::getWindowByRegex(const std::string& regexp_) { } for (auto const& w : g_pCompositor->m_windows) { - if (!w->m_isMapped || (w->isHidden() && !g_pLayoutManager->getCurrentLayout()->isWindowReachable(w))) + if (!w->m_isMapped) continue; switch (mode) { @@ -2567,59 +2586,30 @@ void CCompositor::moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWor setWindowFullscreenInternal(pWindow, FSMODE_NONE); const PHLWINDOW pFirstWindowOnWorkspace = pWorkspace->getFirstWindow(); - const int visibleWindowsOnWorkspace = pWorkspace->getWindows(std::nullopt, true); + const int visibleWindowsOnWorkspace = pWorkspace->getWindows(true, std::nullopt, true); const auto POSTOMON = pWindow->m_realPosition->goal() - (pWindow->m_monitor ? pWindow->m_monitor->m_position : Vector2D{}); const auto PWORKSPACEMONITOR = pWorkspace->m_monitor.lock(); - if (!pWindow->m_isFloating) - g_pLayoutManager->getCurrentLayout()->onWindowRemovedTiling(pWindow); - pWindow->moveToWorkspace(pWorkspace); pWindow->m_monitor = pWorkspace->m_monitor; static auto PGROUPONMOVETOWORKSPACE = CConfigValue("group:group_on_movetoworkspace"); - if (*PGROUPONMOVETOWORKSPACE && visibleWindowsOnWorkspace == 1 && pFirstWindowOnWorkspace && pFirstWindowOnWorkspace != pWindow && - pFirstWindowOnWorkspace->m_groupData.pNextWindow.lock() && pWindow->canBeGroupedInto(pFirstWindowOnWorkspace)) { - - pWindow->m_isFloating = pFirstWindowOnWorkspace->m_isFloating; // match the floating state. Needed to group tiled into floated and vice versa. - if (!pWindow->m_groupData.pNextWindow.expired()) { - PHLWINDOW next = pWindow->m_groupData.pNextWindow.lock(); - while (next != pWindow) { - next->m_isFloating = pFirstWindowOnWorkspace->m_isFloating; // match the floating state of group members - next = next->m_groupData.pNextWindow.lock(); - } - } - - static auto USECURRPOS = CConfigValue("group:insert_after_current"); - (*USECURRPOS ? pFirstWindowOnWorkspace : pFirstWindowOnWorkspace->getGroupTail())->insertWindowToGroup(pWindow); - - pFirstWindowOnWorkspace->setGroupCurrent(pWindow); - pWindow->updateWindowDecos(); - g_pLayoutManager->getCurrentLayout()->recalculateWindow(pWindow); - - if (!pWindow->getDecorationByType(DECORATION_GROUPBAR)) - pWindow->addWindowDeco(makeUnique(pWindow)); - + if (*PGROUPONMOVETOWORKSPACE && visibleWindowsOnWorkspace == 1 && pFirstWindowOnWorkspace && pFirstWindowOnWorkspace != pWindow && pFirstWindowOnWorkspace->m_group && + pWindow->canBeGroupedInto(pFirstWindowOnWorkspace->m_group)) { + pFirstWindowOnWorkspace->m_group->add(pWindow); } else { - if (!pWindow->m_isFloating) - g_pLayoutManager->getCurrentLayout()->onWindowCreatedTiling(pWindow); - if (pWindow->m_isFloating) - *pWindow->m_realPosition = POSTOMON + PWORKSPACEMONITOR->m_position; + pWindow->layoutTarget()->setPositionGlobal(CBox{POSTOMON + PWORKSPACEMONITOR->m_position, pWindow->layoutTarget()->position().size()}); } pWindow->updateToplevel(); pWindow->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE); pWindow->uncacheWindowDecos(); - pWindow->updateGroupOutputs(); - if (!pWindow->m_groupData.pNextWindow.expired()) { - PHLWINDOW next = pWindow->m_groupData.pNextWindow.lock(); - while (next != pWindow) { - next->updateToplevel(); - next = next->m_groupData.pNextWindow.lock(); - } - } + if (pWindow->m_group) + pWindow->m_group->updateWorkspace(pWorkspace); + + g_layoutManager->newTarget(pWindow->layoutTarget(), pWorkspace->m_space); if (FULLSCREEN) setWindowFullscreenInternal(pWindow, FULLSCREENMODE); @@ -2788,12 +2778,17 @@ void CCompositor::arrangeMonitors() { } PROTO::xdgOutput->updateAllOutputs(); + Event::bus()->m_events.monitor.layoutChanged.emit(); #ifndef NO_XWAYLAND - CBox box = g_pCompositor->calculateX11WorkArea(); - if (!g_pXWayland || !g_pXWayland->m_wm) - return; - g_pXWayland->m_wm->updateWorkArea(box.x, box.y, box.w, box.h); + const auto box = g_pCompositor->calculateX11WorkArea(); + if (g_pXWayland && g_pXWayland->m_wm) { + if (box) + g_pXWayland->m_wm->updateWorkArea(box->x, box->y, box->w, box->h); + else + g_pXWayland->m_wm->updateWorkArea(0, 0, 0, 0); + } + #endif } @@ -2920,7 +2915,7 @@ void CCompositor::onNewMonitor(SP output) { PNEWMONITOR->m_id = FALLBACK ? MONITOR_INVALID : g_pCompositor->getNextAvailableMonitorID(output->name); PNEWMONITOR->m_isUnsafeFallback = FALLBACK; - EMIT_HOOK_EVENT("newMonitor", PNEWMONITOR); + Event::bus()->m_events.monitor.newMon.emit(PNEWMONITOR); if (!FALLBACK) PNEWMONITOR->onConnect(false); @@ -2973,13 +2968,16 @@ PImageDescription CCompositor::getHDRImageDescription() { } return m_monitors.size() == 1 && m_monitors[0]->m_output && m_monitors[0]->m_output->parsedEDID.hdrMetadata.has_value() ? - CImageDescription::from(SImageDescription{.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ, - .primariesNameSet = true, - .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, - .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020), - .luminances = {.min = m_monitors[0]->m_output->parsedEDID.hdrMetadata->desiredContentMinLuminance, - .max = m_monitors[0]->m_output->parsedEDID.hdrMetadata->desiredContentMaxLuminance, - .reference = m_monitors[0]->m_output->parsedEDID.hdrMetadata->desiredMaxFrameAverageLuminance}}) : + CImageDescription::from(SImageDescription{ + .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ, + .primariesNameSet = true, + .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, + .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020), + .masteringPrimaries = m_monitors[0]->getMasteringPrimaries(), + .luminances = {.min = m_monitors[0]->minLuminance(HDR_MIN_LUMINANCE), .max = m_monitors[0]->maxLuminance(HDR_MAX_LUMINANCE), .reference = HDR_REF_LUMINANCE}, + .masteringLuminances = m_monitors[0]->getMasteringLuminances(), + .maxCLL = m_monitors[0]->maxCLL(), + .maxFALL = m_monitors[0]->maxFALL()}) : DEFAULT_HDR_IMAGE_DESCRIPTION; } @@ -3076,6 +3074,27 @@ void CCompositor::ensurePersistentWorkspacesPresent(const std::vectorm_isSpecialWorkspace) + continue; + + const auto RULE = g_pConfigManager->getWorkspaceRuleFor(ws); + if (RULE.monitor.empty()) + continue; + + const auto PMONITOR = getMonitorFromString(RULE.monitor); + if (!PMONITOR) + continue; + + if (ws->m_monitor == PMONITOR) + continue; + + Log::logger->log(Log::DEBUG, "ensureWorkspacesOnAssignedMonitors: moving workspace {} to {}", ws->m_name, PMONITOR->m_name); + moveWorkspaceToMonitor(ws, PMONITOR, true); + } +} + std::optional CCompositor::getVTNr() { if (!m_aqBackend->hasSession()) return std::nullopt; @@ -3096,3 +3115,7 @@ std::optional CCompositor::getVTNr() { return ttynum; } + +bool CCompositor::isVRRActiveOnAnyMonitor() const { + return std::ranges::any_of(m_monitors, [](const PHLMONITOR& m) { return m->m_vrrActive; }); +} diff --git a/src/Compositor.hpp b/src/Compositor.hpp index abcf7ec6..6d2044fe 100644 --- a/src/Compositor.hpp +++ b/src/Compositor.hpp @@ -4,11 +4,12 @@ #include +#include "helpers/math/Direction.hpp" #include "managers/XWaylandManager.hpp" #include "managers/KeybindManager.hpp" #include "managers/SessionLockManager.hpp" #include "desktop/view/Window.hpp" -#include "protocols/types/ColorManagement.hpp" +#include "helpers/cm/ColorManagement.hpp" #include #include @@ -113,16 +114,16 @@ class CCompositor { bool isWindowActive(PHLWINDOW); void changeWindowZOrder(PHLWINDOW, bool); void cleanupFadingOut(const MONITORID& monid); - PHLWINDOW getWindowInDirection(PHLWINDOW, char); - PHLWINDOW getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, char dir, PHLWINDOW ignoreWindow = nullptr, bool useVectorAngles = false); + PHLWINDOW getWindowInDirection(PHLWINDOW, Math::eDirection); + PHLWINDOW getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, Math::eDirection dir, PHLWINDOW ignoreWindow = nullptr, bool useVectorAngles = false); PHLWINDOW getWindowCycle(PHLWINDOW cur, bool focusableOnly = false, std::optional floating = std::nullopt, bool visible = false, bool prev = false); PHLWINDOW getWindowCycleHist(PHLWINDOWREF cur, bool focusableOnly = false, std::optional floating = std::nullopt, bool visible = false, bool next = false); WORKSPACEID getNextAvailableNamedWorkspace(); bool isPointOnAnyMonitor(const Vector2D&); bool isPointOnReservedArea(const Vector2D& point, const PHLMONITOR monitor = nullptr); - CBox calculateX11WorkArea(); - PHLMONITOR getMonitorInDirection(const char&); - PHLMONITOR getMonitorInDirection(PHLMONITOR, const char&); + std::optional calculateX11WorkArea(); + PHLMONITOR getMonitorInDirection(Math::eDirection); + PHLMONITOR getMonitorInDirection(PHLMONITOR, Math::eDirection); void updateAllWindowsAnimatedDecorationValues(); MONITORID getNextAvailableMonitorID(std::string const& name); void moveWorkspaceToMonitor(PHLWORKSPACE, PHLMONITOR, bool noWarpCursor = false); @@ -160,7 +161,9 @@ class CCompositor { void updateSuspendedStates(); void onNewMonitor(SP output); void ensurePersistentWorkspacesPresent(const std::vector& rules, PHLWORKSPACE pWorkspace = nullptr); + void ensureWorkspacesOnAssignedMonitors(); std::optional getVTNr(); + bool isVRRActiveOnAnyMonitor() const; NColorManagement::PImageDescription getPreferredImageDescription(); NColorManagement::PImageDescription getHDRImageDescription(); diff --git a/src/SharedDefs.hpp b/src/SharedDefs.hpp index 639d160a..bb7c601e 100644 --- a/src/SharedDefs.hpp +++ b/src/SharedDefs.hpp @@ -38,10 +38,6 @@ enum eInputType : uint8_t { INPUT_TYPE_MOTION }; -struct SCallbackInfo { - bool cancelled = false; /* on cancellable events, will cancel the event. */ -}; - enum eHyprCtlOutputFormat : uint8_t { FORMAT_NORMAL = 0, FORMAT_JSON @@ -62,5 +58,3 @@ struct SDispatchResult { using WINDOWID = int64_t; using MONITORID = int64_t; using WORKSPACEID = int64_t; - -using HOOK_CALLBACK_FN = std::function; diff --git a/src/config/ConfigDescriptions.hpp b/src/config/ConfigDescriptions.hpp index 132b4789..f6f777d1 100644 --- a/src/config/ConfigDescriptions.hpp +++ b/src/config/ConfigDescriptions.hpp @@ -1115,6 +1115,12 @@ inline static const std::vector CONFIG_OPTIONS = { .type = CONFIG_OPTION_BOOL, .data = SConfigOptionDescription::SRangeData{0, -20, 20}, }, + SConfigOptionDescription{ + .value = "group:groupbar:text_padding", + .description = "set horizontal padding for a text", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SRangeData{0, 0, 22}, + }, SConfigOptionDescription{ .value = "group:groupbar:blur", .description = "enable background blur for groupbars", @@ -1562,10 +1568,28 @@ inline static const std::vector CONFIG_OPTIONS = { }, SConfigOptionDescription{ .value = "render:cm_sdr_eotf", - .description = "Default transfer function for displaying SDR apps. 0 - Use default value (Gamma 2.2), 1 - Treat unspecified as Gamma 2.2, 2 - Treat " - "unspecified and sRGB as Gamma 2.2, 3 - Treat unspecified as sRGB", - .type = CONFIG_OPTION_CHOICE, - .data = SConfigOptionDescription::SChoiceData{0, "default,gamma22,gamma22force,srgb"}, + .description = "Default transfer function for displaying SDR apps. default - Use default value (Gamma 2.2), gamma22 - Treat unspecified as Gamma 2.2, gamma22force - Treat " + "unspecified and sRGB as Gamma 2.2, srgb - Treat unspecified as sRGB", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{"default"}, + }, + SConfigOptionDescription{ + .value = "render:commit_timing_enabled", + .description = "Enable commit timing proto. Requires restart", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "render:icc_vcgt_enabled", + .description = "Enable sending VCGT ramps to KMS with ICC profiles", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + { + .value = "render:use_shader_blur_blend", + .description = "Use experimental blurred bg blending", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, }, /* @@ -1690,9 +1714,9 @@ inline static const std::vector CONFIG_OPTIONS = { }, SConfigOptionDescription{ .value = "cursor:use_cpu_buffer", - .description = "Makes HW cursors use a CPU buffer. Required on Nvidia to have HW cursors. Experimental", - .type = CONFIG_OPTION_BOOL, - .data = SConfigOptionDescription::SBoolData{false}, + .description = "Makes HW cursors use a CPU buffer. Required on Nvidia to have HW cursors. 0 - off, 1 - on, 2 - auto (nvidia only)", + .type = CONFIG_OPTION_INT, + .data = SConfigOptionDescription::SRangeData{.value = 2, .min = 0, .max = 2}, }, SConfigOptionDescription{ .value = "cursor:sync_gsettings_theme", @@ -1746,6 +1770,12 @@ inline static const std::vector CONFIG_OPTIONS = { .type = CONFIG_OPTION_BOOL, .data = SConfigOptionDescription::SBoolData{false}, }, + SConfigOptionDescription{ + .value = "debug:gl_debugging", + .description = "enable OpenGL debugging and error checking, they hurt performance.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, SConfigOptionDescription{ .value = "debug:disable_logs", .description = "disable logging to a file", @@ -1824,6 +1854,46 @@ inline static const std::vector CONFIG_OPTIONS = { .type = CONFIG_OPTION_BOOL, .data = SConfigOptionDescription::SBoolData{false}, }, + SConfigOptionDescription{ + .value = "debug:ds_handle_same_buffer", + .description = "Special case for DS with unmodified buffer", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "debug:ds_handle_same_buffer_fifo", + .description = "Special case for DS with unmodified buffer unlocks fifo", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "debug:fifo_pending_workaround", + .description = "Fifo workaround for empty pending list", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + SConfigOptionDescription{ + .value = "debug:render_solitary_wo_damage", + .description = "Render solitary window with empty damage", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, + }, + + /* + * layout: + */ + SConfigOptionDescription{ + .value = "layout:single_window_aspect_ratio", + .description = "If specified, whenever only a single window is open, it will be coerced into the specified aspect ratio. Ignored if the y-value is zero.", + .type = CONFIG_OPTION_VECTOR, + .data = SConfigOptionDescription::SVectorData{{0, 0}, {0, 0}, {1000., 1000.}}, + }, + SConfigOptionDescription{ + .value = "layout:single_window_aspect_ratio_tolerance", + .description = "Minimum distance for single_window_aspect_ratio to take effect, in fractions of the monitor's size.", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{0.1f, 0.f, 1.f}, + }, /* * dwindle: @@ -1904,18 +1974,6 @@ inline static const std::vector CONFIG_OPTIONS = { .type = CONFIG_OPTION_BOOL, .data = SConfigOptionDescription::SBoolData{false}, }, - SConfigOptionDescription{ - .value = "dwindle:single_window_aspect_ratio", - .description = "If specified, whenever only a single window is open, it will be coerced into the specified aspect ratio. Ignored if the y-value is zero.", - .type = CONFIG_OPTION_VECTOR, - .data = SConfigOptionDescription::SVectorData{{0, 0}, {0, 0}, {1000., 1000.}}, - }, - SConfigOptionDescription{ - .value = "dwindle:single_window_aspect_ratio_tolerance", - .description = "Minimum distance for single_window_aspect_ratio to take effect, in fractions of the monitor's size.", - .type = CONFIG_OPTION_FLOAT, - .data = SConfigOptionDescription::SFloatData{0.1f, 0.f, 1.f}, - }, /* * master: @@ -2001,6 +2059,65 @@ inline static const std::vector CONFIG_OPTIONS = { .data = SConfigOptionDescription::SBoolData{false}, }, + /* + * scrolling: + */ + + SConfigOptionDescription{ + .value = "scrolling:fullscreen_on_one_column", + .description = "when enabled, a single column on a workspace will always span the entire screen.", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, + SConfigOptionDescription{ + .value = "scrolling:column_width", + .description = "the default width of a column, [0.1 - 1.0].", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{.value = 0.5, .min = 0.1, .max = 1.0}, + }, + SConfigOptionDescription{ + .value = "scrolling:focus_fit_method", + .description = "When a column is focused, what method should be used to bring it into view", + .type = CONFIG_OPTION_CHOICE, + .data = SConfigOptionDescription::SChoiceData{.firstIndex = 0, .choices = "center,fit"}, + }, + SConfigOptionDescription{ + .value = "scrolling:follow_focus", + .description = "when a window is focused, should the layout move to bring it into view automatically", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{.value = true}, + }, + SConfigOptionDescription{ + .value = "scrolling:follow_min_visible", + .description = "when a window is focused, require that at least a given fraction of it is visible for focus to follow", + .type = CONFIG_OPTION_FLOAT, + .data = SConfigOptionDescription::SFloatData{.value = 0.4, .min = 0.0, .max = 1.0}, + }, + SConfigOptionDescription{ + .value = "scrolling:explicit_column_widths", + .description = "A comma-separated list of preconfigured widths for colresize +conf/-conf", + .type = CONFIG_OPTION_STRING_SHORT, + .data = SConfigOptionDescription::SStringData{"0.333, 0.5, 0.667, 1.0"}, + }, + SConfigOptionDescription{ + .value = "scrolling:direction", + .description = "Direction in which new windows appear and the layout scrolls", + .type = CONFIG_OPTION_CHOICE, + .data = SConfigOptionDescription::SChoiceData{.firstIndex = 0, .choices = "right,left,down,up"}, + }, + SConfigOptionDescription{ + .value = "scrolling:wrap_focus", + .description = "Determines if column focus wraps around when going before the first column or past the last column", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{.value = true}, + }, + SConfigOptionDescription{ + .value = "scrolling:wrap_swapcol", + .description = "Determines if column movement wraps around when moving to before the first column or past the last column", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{.value = true}, + }, + /* * Quirks */ @@ -2011,5 +2128,11 @@ inline static const std::vector CONFIG_OPTIONS = { .type = CONFIG_OPTION_INT, .data = SConfigOptionDescription::SRangeData{.value = 0, .min = 0, .max = 2}, }, + SConfigOptionDescription{ + .value = "quirks:skip_non_kms_dmabuf_formats", + .description = "Do not report dmabuf formats which cannot be imported into KMS", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{true}, + }, }; diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index d82983a1..29967e58 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -18,13 +18,14 @@ #include "../desktop/rule/layerRule/LayerRule.hpp" #include "../debug/HyprCtl.hpp" #include "../desktop/state/FocusState.hpp" +#include "../layout/space/Space.hpp" +#include "../layout/supplementary/WorkspaceAlgoMatcher.hpp" #include "defaultConfig.hpp" #include "../render/Renderer.hpp" #include "../hyprerror/HyprError.hpp" #include "../managers/input/InputManager.hpp" #include "../managers/eventLoop/EventLoopManager.hpp" -#include "../managers/LayoutManager.hpp" #include "../managers/EventManager.hpp" #include "../managers/permissions/DynamicPermissionManager.hpp" #include "../debug/HyprNotificationOverlay.hpp" @@ -39,8 +40,10 @@ #include "../managers/input/trackpad/gestures/CloseGesture.hpp" #include "../managers/input/trackpad/gestures/FloatGesture.hpp" #include "../managers/input/trackpad/gestures/FullscreenGesture.hpp" +#include "../managers/input/trackpad/gestures/CursorZoomGesture.hpp" + +#include "../event/EventBus.hpp" -#include "../managers/HookSystemManager.hpp" #include "../protocols/types/ContentType.hpp" #include #include @@ -399,6 +402,12 @@ static Hyprlang::CParseResult handleWindowrule(const char* c, const char* v) { return result; } +static Hyprlang::CParseResult handleWindowrulev2(const char* c, const char* v) { + Hyprlang::CParseResult res; + res.setError("windowrulev2 is deprecated. Correct syntax can be found on the wiki."); + return res; +} + static Hyprlang::CParseResult handleLayerrule(const char* c, const char* v) { const std::string VALUE = v; const std::string COMMAND = c; @@ -411,6 +420,12 @@ static Hyprlang::CParseResult handleLayerrule(const char* c, const char* v) { return result; } +static Hyprlang::CParseResult handleLayerrulev2(const char* c, const char* v) { + Hyprlang::CParseResult res; + res.setError("layerrulev2 doesn't exist. Correct syntax can be found on the wiki."); + return res; +} + void CConfigManager::registerConfigVar(const char* name, const Hyprlang::INT& val) { m_configValueNumber++; m_config->addConfigValue(name, val); @@ -541,12 +556,14 @@ CConfigManager::CConfigManager() { registerConfigVar("group:groupbar:gaps_in", Hyprlang::INT{2}); registerConfigVar("group:groupbar:keep_upper_gap", Hyprlang::INT{1}); registerConfigVar("group:groupbar:text_offset", Hyprlang::INT{0}); + registerConfigVar("group:groupbar:text_padding", Hyprlang::INT{0}); registerConfigVar("group:groupbar:blur", Hyprlang::INT{0}); registerConfigVar("debug:log_damage", Hyprlang::INT{0}); registerConfigVar("debug:overlay", Hyprlang::INT{0}); registerConfigVar("debug:damage_blink", Hyprlang::INT{0}); registerConfigVar("debug:pass", Hyprlang::INT{0}); + registerConfigVar("debug:gl_debugging", Hyprlang::INT{0}); registerConfigVar("debug:disable_logs", Hyprlang::INT{1}); registerConfigVar("debug:disable_time", Hyprlang::INT{1}); registerConfigVar("debug:enable_stdout_logs", Hyprlang::INT{0}); @@ -558,6 +575,10 @@ CConfigManager::CConfigManager() { registerConfigVar("debug:disable_scale_checks", Hyprlang::INT{0}); registerConfigVar("debug:colored_stdout_logs", Hyprlang::INT{1}); registerConfigVar("debug:full_cm_proto", Hyprlang::INT{0}); + registerConfigVar("debug:ds_handle_same_buffer", Hyprlang::INT{1}); + registerConfigVar("debug:ds_handle_same_buffer_fifo", Hyprlang::INT{1}); + registerConfigVar("debug:fifo_pending_workaround", Hyprlang::INT{0}); + registerConfigVar("debug:render_solitary_wo_damage", Hyprlang::INT{0}); registerConfigVar("decoration:rounding", Hyprlang::INT{0}); registerConfigVar("decoration:rounding_power", {2.F}); @@ -597,6 +618,9 @@ CConfigManager::CConfigManager() { registerConfigVar("decoration:screen_shader", {STRVAL_EMPTY}); registerConfigVar("decoration:border_part_of_window", Hyprlang::INT{1}); + registerConfigVar("layout:single_window_aspect_ratio", Hyprlang::VEC2{0, 0}); + registerConfigVar("layout:single_window_aspect_ratio_tolerance", {0.1f}); + registerConfigVar("dwindle:pseudotile", Hyprlang::INT{0}); registerConfigVar("dwindle:force_split", Hyprlang::INT{0}); registerConfigVar("dwindle:permanent_direction_override", Hyprlang::INT{0}); @@ -609,8 +633,6 @@ CConfigManager::CConfigManager() { registerConfigVar("dwindle:smart_split", Hyprlang::INT{0}); registerConfigVar("dwindle:smart_resizing", Hyprlang::INT{1}); registerConfigVar("dwindle:precise_mouse_move", Hyprlang::INT{0}); - registerConfigVar("dwindle:single_window_aspect_ratio", Hyprlang::VEC2{0, 0}); - registerConfigVar("dwindle:single_window_aspect_ratio_tolerance", {0.1f}); registerConfigVar("master:special_scale_factor", {1.f}); registerConfigVar("master:mfact", {0.55f}); @@ -626,6 +648,16 @@ CConfigManager::CConfigManager() { registerConfigVar("master:drop_at_cursor", Hyprlang::INT{1}); registerConfigVar("master:always_keep_position", Hyprlang::INT{0}); + registerConfigVar("scrolling:fullscreen_on_one_column", Hyprlang::INT{1}); + registerConfigVar("scrolling:column_width", Hyprlang::FLOAT{0.5F}); + registerConfigVar("scrolling:focus_fit_method", Hyprlang::INT{1}); + registerConfigVar("scrolling:follow_focus", Hyprlang::INT{1}); + registerConfigVar("scrolling:follow_min_visible", Hyprlang::FLOAT{0.4}); + registerConfigVar("scrolling:explicit_column_widths", Hyprlang::STRING{"0.333, 0.5, 0.667, 1.0"}); + registerConfigVar("scrolling:direction", Hyprlang::STRING{"right"}); + registerConfigVar("scrolling:wrap_focus", Hyprlang::INT{1}); + registerConfigVar("scrolling:wrap_swapcol", Hyprlang::INT{1}); + registerConfigVar("animations:enabled", Hyprlang::INT{1}); registerConfigVar("animations:workspace_wraparound", Hyprlang::INT{0}); @@ -696,9 +728,9 @@ CConfigManager::CConfigManager() { registerConfigVar("binds:movefocus_cycles_fullscreen", Hyprlang::INT{0}); registerConfigVar("binds:movefocus_cycles_groupfirst", Hyprlang::INT{0}); registerConfigVar("binds:disable_keybind_grabbing", Hyprlang::INT{0}); - registerConfigVar("binds:window_direction_monitor_fallback", Hyprlang::INT{1}); registerConfigVar("binds:allow_pin_fullscreen", Hyprlang::INT{0}); registerConfigVar("binds:drag_threshold", Hyprlang::INT{0}); + registerConfigVar("binds:window_direction_monitor_fallback", Hyprlang::INT{1}); registerConfigVar("gestures:workspace_swipe_distance", Hyprlang::INT{300}); registerConfigVar("gestures:workspace_swipe_invert", Hyprlang::INT{1}); @@ -765,13 +797,17 @@ CConfigManager::CConfigManager() { registerConfigVar("render:cm_auto_hdr", Hyprlang::INT{1}); registerConfigVar("render:new_render_scheduling", Hyprlang::INT{0}); registerConfigVar("render:non_shader_cm", Hyprlang::INT{3}); - registerConfigVar("render:cm_sdr_eotf", Hyprlang::INT{0}); + registerConfigVar("render:cm_sdr_eotf", {"default"}); + registerConfigVar("render:commit_timing_enabled", Hyprlang::INT{1}); + registerConfigVar("render:icc_vcgt_enabled", Hyprlang::INT{1}); + registerConfigVar("render:use_shader_blur_blend", Hyprlang::INT{0}); registerConfigVar("ecosystem:no_update_news", Hyprlang::INT{0}); registerConfigVar("ecosystem:no_donation_nag", Hyprlang::INT{0}); registerConfigVar("ecosystem:enforce_permissions", Hyprlang::INT{0}); registerConfigVar("quirks:prefer_hdr", Hyprlang::INT{0}); + registerConfigVar("quirks:skip_non_kms_dmabuf_formats", Hyprlang::INT{0}); // devices m_config->addSpecialCategory("device", {"name"}); @@ -827,7 +863,7 @@ CConfigManager::CConfigManager() { m_config->addSpecialConfigValue("monitorv2", "mirror", {STRVAL_EMPTY}); m_config->addSpecialConfigValue("monitorv2", "bitdepth", {STRVAL_EMPTY}); // TODO use correct type m_config->addSpecialConfigValue("monitorv2", "cm", {"auto"}); - m_config->addSpecialConfigValue("monitorv2", "sdr_eotf", Hyprlang::INT{0}); + m_config->addSpecialConfigValue("monitorv2", "sdr_eotf", {"default"}); m_config->addSpecialConfigValue("monitorv2", "sdrbrightness", Hyprlang::FLOAT{1.0}); m_config->addSpecialConfigValue("monitorv2", "sdrsaturation", Hyprlang::FLOAT{1.0}); m_config->addSpecialConfigValue("monitorv2", "vrr", Hyprlang::INT{0}); @@ -839,6 +875,7 @@ CConfigManager::CConfigManager() { m_config->addSpecialConfigValue("monitorv2", "min_luminance", Hyprlang::FLOAT{-1.0}); m_config->addSpecialConfigValue("monitorv2", "max_luminance", Hyprlang::INT{-1}); m_config->addSpecialConfigValue("monitorv2", "max_avg_luminance", Hyprlang::INT{-1}); + m_config->addSpecialConfigValue("monitorv2", "icc", Hyprlang::STRING{""}); // windowrule v3 m_config->addSpecialCategory("windowrule", {.key = "name"}); @@ -868,9 +905,13 @@ CConfigManager::CConfigManager() { m_config->registerHandler(&::handleSubmap, "submap", {false}); m_config->registerHandler(&::handlePlugin, "plugin", {false}); m_config->registerHandler(&::handlePermission, "permission", {false}); - m_config->registerHandler(&::handleGesture, "gesture", {false}); + m_config->registerHandler(&::handleGesture, "gesture", {true}); m_config->registerHandler(&::handleEnv, "env", {true}); + // windowrulev2 and layerrulev2 errors + m_config->registerHandler(&::handleWindowrulev2, "windowrulev2", {false}); + m_config->registerHandler(&::handleLayerrulev2, "layerrulev2", {false}); + // pluginza m_config->addSpecialCategory("plugin", {nullptr, true}); @@ -1031,7 +1072,7 @@ static void clearHlVersionVars() { } void CConfigManager::reload() { - EMIT_HOOK_EVENT("preConfigReload", nullptr); + Event::bus()->m_events.config.preReload.emit(); setDefaultAnimationVars(); resetHLConfig(); m_configCurrentPath = getMainConfigPath(); @@ -1173,8 +1214,18 @@ std::optional CConfigManager::handleMonitorv2(const std::string& ou if (VAL && VAL->m_bSetByUser) parser.parseCM(std::any_cast(VAL->getValue())); VAL = m_config->getSpecialConfigValuePtr("monitorv2", "sdr_eotf", output.c_str()); - if (VAL && VAL->m_bSetByUser) - parser.rule().sdrEotf = std::any_cast(VAL->getValue()); + if (VAL && VAL->m_bSetByUser) { + const std::string value = std::any_cast(VAL->getValue()); + // remap legacy + if (value == "0") + parser.rule().sdrEotf = NTransferFunction::TF_AUTO; + else if (value == "1") + parser.rule().sdrEotf = NTransferFunction::TF_SRGB; + else if (value == "2") + parser.rule().sdrEotf = NTransferFunction::TF_GAMMA22; + else + parser.rule().sdrEotf = NTransferFunction::fromString(value); + } VAL = m_config->getSpecialConfigValuePtr("monitorv2", "sdrbrightness", output.c_str()); if (VAL && VAL->m_bSetByUser) parser.rule().sdrBrightness = std::any_cast(VAL->getValue()); @@ -1211,6 +1262,10 @@ std::optional CConfigManager::handleMonitorv2(const std::string& ou if (VAL && VAL->m_bSetByUser) parser.rule().maxAvgLuminance = std::any_cast(VAL->getValue()); + VAL = m_config->getSpecialConfigValuePtr("monitorv2", "icc", output.c_str()); + if (VAL && VAL->m_bSetByUser) + parser.rule().iccFile = std::any_cast(VAL->getValue()); + auto newrule = parser.rule(); std::erase_if(m_monitorRules, [&](const auto& other) { return other.name == newrule.name; }); @@ -1313,7 +1368,8 @@ void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) { static auto PZOOMFACTOR = CConfigValue("cursor:zoom_factor"); for (auto const& m : g_pCompositor->m_monitors) { *(m->m_cursorZoom) = *PZOOMFACTOR; - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id); + if (m->m_activeWorkspace) + m->m_activeWorkspace->m_space->recalculate(); } // Update the keyboard layout to the cfg'd one if this is not the first launch @@ -1381,9 +1437,6 @@ void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) { // Update window border colors g_pCompositor->updateAllWindowsAnimatedDecorationValues(); - // update layout - g_pLayoutManager->switchToLayout(std::any_cast(m_config->getConfigValue("general:layout"))); - // manual crash if (std::any_cast(m_config->getConfigValue("debug:manual_crash")) && !m_manualCrashInitiated) { m_manualCrashInitiated = true; @@ -1422,7 +1475,10 @@ void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) { if (!m_isFirstLaunch) ensurePersistentWorkspacesPresent(); - EMIT_HOOK_EVENT("configReloaded", nullptr); + // update layouts + Layout::Supplementary::algoMatcher()->updateWorkspaceLayouts(); + + Event::bus()->m_events.config.reloaded.emit(); if (g_pEventManager) g_pEventManager->postEvent(SHyprIPCEvent{"configreloaded", ""}); } @@ -1444,8 +1500,9 @@ std::string CConfigManager::parseKeyword(const std::string& COMMAND, const std:: // invalidate layouts if they changed if (COMMAND == "monitor" || COMMAND.contains("gaps_") || COMMAND.starts_with("dwindle:") || COMMAND.starts_with("master:")) { - for (auto const& m : g_pCompositor->m_monitors) - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id); + for (auto const& m : g_pCompositor->m_monitors) { + g_layoutManager->recalculateMonitor(m); + } } // Update window border colors @@ -1617,6 +1674,8 @@ SWorkspaceRule CConfigManager::mergeWorkspaceRules(const SWorkspaceRule& rule1, mergedRule.onCreatedEmptyRunCmd = rule2.onCreatedEmptyRunCmd; if (rule2.defaultName.has_value()) mergedRule.defaultName = rule2.defaultName; + if (rule2.layout.has_value()) + mergedRule.layout = rule2.layout; if (!rule2.layoutopts.empty()) { for (const auto& layoutopt : rule2.layoutopts) { mergedRule.layoutopts[layoutopt.first] = layoutopt.second; @@ -1708,7 +1767,7 @@ void CConfigManager::performMonitorReload() { m_wantsMonitorReload = false; - EMIT_HOOK_EVENT("monitorLayoutChanged", nullptr); + Event::bus()->m_events.monitor.layoutChanged.emit(); } void* const* CConfigManager::getConfigValuePtr(const std::string& val) { @@ -1766,24 +1825,41 @@ void CConfigManager::ensureVRR(PHLMONITOR pMonitor) { } m->m_vrrActive = false; return; - } else if (USEVRR == 1) { - if (!m->m_vrrActive) { - m->m_output->state->resetExplicitFences(); - m->m_output->state->setAdaptiveSync(true); + } - if (!m->m_state.test()) { - Log::logger->log(Log::DEBUG, "Pending output {} does not accept VRR.", m->m_output->name); - m->m_output->state->setAdaptiveSync(false); + const auto PWORKSPACE = m->m_activeWorkspace; + + if (USEVRR == 1) { + bool wantVRR = true; + if (PWORKSPACE && PWORKSPACE->m_hasFullscreenWindow && (PWORKSPACE->m_fullscreenMode & FSMODE_FULLSCREEN)) + wantVRR = !PWORKSPACE->getFullscreenWindow()->m_ruleApplicator->noVRR().valueOrDefault(); + + if (wantVRR) { + if (!m->m_vrrActive) { + m->m_output->state->resetExplicitFences(); + m->m_output->state->setAdaptiveSync(true); + + if (!m->m_state.test()) { + Log::logger->log(Log::DEBUG, "Pending output {} does not accept VRR.", m->m_output->name); + m->m_output->state->setAdaptiveSync(false); + } + + if (!m->m_state.commit()) + Log::logger->log(Log::ERR, "Couldn't commit output {} in ensureVRR -> true", m->m_output->name); } + m->m_vrrActive = true; + } else { + if (m->m_vrrActive) { + m->m_output->state->resetExplicitFences(); + m->m_output->state->setAdaptiveSync(false); - if (!m->m_state.commit()) - Log::logger->log(Log::ERR, "Couldn't commit output {} in ensureVRR -> true", m->m_output->name); + if (!m->m_state.commit()) + Log::logger->log(Log::ERR, "Couldn't commit output {} in ensureVRR -> false", m->m_output->name); + } + m->m_vrrActive = false; } - m->m_vrrActive = true; return; } else if (USEVRR == 2 || USEVRR == 3) { - const auto PWORKSPACE = m->m_activeWorkspace; - if (!PWORKSPACE) return; // ??? @@ -2208,6 +2284,15 @@ bool CMonitorRuleParser::parseVRR(const std::string& value) { return true; } +bool CMonitorRuleParser::parseICC(const std::string& val) { + if (val.empty()) { + m_error += "invalid icc "; + return false; + } + m_rule.iccFile = val; + return true; +} + void CMonitorRuleParser::setDisabled() { m_rule.disabled = true; } @@ -2302,6 +2387,9 @@ std::optional CConfigManager::handleMonitor(const std::string& comm } else if (ARGS[argno] == "vrr") { parser.parseVRR(std::string(ARGS[argno + 1])); argno++; + } else if (ARGS[argno] == "icc") { + parser.parseICC(std::string(ARGS[argno + 1])); + argno++; } else if (ARGS[argno] == "workspace") { const auto& [id, name, isAutoID] = getWorkspaceIDNameFromString(std::string(ARGS[argno + 1])); @@ -2668,6 +2756,9 @@ std::optional CConfigManager::handleWorkspaceRules(const std::strin opt = opt.substr(0, opt.find(':')); wsRule.layoutopts[opt] = val; + } else if ((delim = rule.find("layout:")) != std::string::npos) { + std::string layout = rule.substr(delim + 7); + wsRule.layout = std::move(layout); } return {}; @@ -2804,6 +2895,8 @@ std::optional CConfigManager::handlePermission(const std::string& c if (data[1] == "screencopy") type = PERMISSION_TYPE_SCREENCOPY; + else if (data[1] == "cursorpos") + type = PERMISSION_TYPE_CURSOR_POS; else if (data[1] == "plugin") type = PERMISSION_TYPE_PLUGIN; else if (data[1] == "keyboard" || data[1] == "keeb") @@ -2821,7 +2914,7 @@ std::optional CConfigManager::handlePermission(const std::string& c if (mode == PERMISSION_RULE_ALLOW_MODE_UNKNOWN) return "unknown permission allow mode"; - if (m_isFirstLaunch) + if (m_isFirstLaunch && g_pDynamicPermissionManager) g_pDynamicPermissionManager->addConfigPermissionRule(std::string(data[0]), type, mode); return {}; @@ -2845,9 +2938,17 @@ std::optional CConfigManager::handleGesture(const std::string& comm if (direction == TRACKPAD_GESTURE_DIR_NONE) return std::format("Invalid direction: {}", data[1]); - int startDataIdx = 2; - uint32_t modMask = 0; - float deltaScale = 1.F; + int startDataIdx = 2; + uint32_t modMask = 0; + float deltaScale = 1.F; + bool disableInhibit = false; + + for (const auto arg : command.substr(7)) { + switch (arg) { + case 'p': disableInhibit = true; break; + default: return "gesture: invalid flag"; + } + } while (true) { @@ -2870,23 +2971,29 @@ std::optional CConfigManager::handleGesture(const std::string& comm if (data[startDataIdx] == "dispatcher") result = g_pTrackpadGestures->addGesture(makeUnique(std::string{data[startDataIdx + 1]}, data.join(",", startDataIdx + 2)), fingerCount, - direction, modMask, deltaScale); + direction, modMask, deltaScale, disableInhibit); else if (data[startDataIdx] == "workspace") - result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale); + result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale, disableInhibit); else if (data[startDataIdx] == "resize") - result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale); + result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale, disableInhibit); else if (data[startDataIdx] == "move") - result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale); + result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale, disableInhibit); else if (data[startDataIdx] == "special") - result = g_pTrackpadGestures->addGesture(makeUnique(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale); + result = + g_pTrackpadGestures->addGesture(makeUnique(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale, disableInhibit); else if (data[startDataIdx] == "close") - result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale); + result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale, disableInhibit); else if (data[startDataIdx] == "float") - result = g_pTrackpadGestures->addGesture(makeUnique(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale); + result = + g_pTrackpadGestures->addGesture(makeUnique(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale, disableInhibit); else if (data[startDataIdx] == "fullscreen") - result = g_pTrackpadGestures->addGesture(makeUnique(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale); - else if (data[startDataIdx] == "unset") - result = g_pTrackpadGestures->removeGesture(fingerCount, direction, modMask, deltaScale); + result = g_pTrackpadGestures->addGesture(makeUnique(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale, + disableInhibit); + else if (data[startDataIdx] == "cursorZoom") { + result = g_pTrackpadGestures->addGesture(makeUnique(std::string{data[startDataIdx + 1]}, std::string{data[startDataIdx + 2]}), fingerCount, + direction, modMask, deltaScale, disableInhibit); + } else if (data[startDataIdx] == "unset") + result = g_pTrackpadGestures->removeGesture(fingerCount, direction, modMask, deltaScale, disableInhibit); else return std::format("Invalid gesture: {}", data[startDataIdx]); @@ -2989,7 +3096,7 @@ bool CConfigManager::shouldUseSoftwareCursors(PHLMONITOR pMonitor) { switch (*PNOHW) { case 0: return false; case 1: return true; - case 2: return g_pHyprRenderer->isNvidia() && g_pHyprRenderer->isMgpu(); + case 2: return g_pHyprRenderer->isNvidia() && (g_pHyprRenderer->isMgpu() || g_pCompositor->isVRRActiveOnAnyMonitor()); default: break; } @@ -3016,20 +3123,20 @@ std::string SConfigOptionDescription::jsonify() const { else if (typeid(Hyprlang::FLOAT) == std::type_index(CONFIGVALUE.type())) currentValue = std::format("{:.2f}", std::any_cast(CONFIGVALUE)); else if (typeid(Hyprlang::STRING) == std::type_index(CONFIGVALUE.type())) - currentValue = std::any_cast(CONFIGVALUE); + currentValue = std::format("\"{}\"", std::any_cast(CONFIGVALUE)); else if (typeid(Hyprlang::VEC2) == std::type_index(CONFIGVALUE.type())) { const auto V = std::any_cast(CONFIGVALUE); - currentValue = std::format("{}, {}", V.x, V.y); + currentValue = std::format("\"{}, {}\"", V.x, V.y); } else if (typeid(void*) == std::type_index(CONFIGVALUE.type())) { const auto DATA = sc(std::any_cast(CONFIGVALUE)); - currentValue = DATA->toString(); + currentValue = std::format("\"{}\"", DATA->toString()); } try { using T = std::decay_t; if constexpr (std::is_same_v) { return std::format(R"#( "value": "{}", - "current": "{}", + "current": {}, "explicit": {})#", val.value, currentValue, EXPLICIT); } else if constexpr (std::is_same_v) { @@ -3048,7 +3155,7 @@ std::string SConfigOptionDescription::jsonify() const { val.value, val.min, val.max, currentValue, EXPLICIT); } else if constexpr (std::is_same_v) { return std::format(R"#( "value": "{}", - "current": "{}", + "current": {}, "explicit": {})#", val.color.getAsHex(), currentValue, EXPLICIT); } else if constexpr (std::is_same_v) { @@ -3069,12 +3176,12 @@ std::string SConfigOptionDescription::jsonify() const { "min_y": {}, "max_x": {}, "max_y": {}, - "current": "{}", + "current": {}, "explicit": {})#", val.vec.x, val.vec.y, val.min.x, val.min.y, val.max.x, val.max.y, currentValue, EXPLICIT); } else if constexpr (std::is_same_v) { return std::format(R"#( "value": "{}", - "current": "{}", + "current": {}, "explicit": {})#", val.gradient, currentValue, EXPLICIT); } diff --git a/src/config/ConfigManager.hpp b/src/config/ConfigManager.hpp index 83fef7b0..36e25f6b 100644 --- a/src/config/ConfigManager.hpp +++ b/src/config/ConfigManager.hpp @@ -46,6 +46,7 @@ struct SWorkspaceRule { std::optional noShadow; std::optional onCreatedEmptyRunCmd; std::optional defaultName; + std::optional layout; std::map layoutopts; }; @@ -176,6 +177,7 @@ class CMonitorRuleParser { bool parseSDRBrightness(const std::string& value); bool parseSDRSaturation(const std::string& value); bool parseVRR(const std::string& value); + bool parseICC(const std::string& value); void setDisabled(); void setMirror(const std::string& value); diff --git a/src/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp index 41b34e8a..3f51e8df 100644 --- a/src/debug/HyprCtl.cpp +++ b/src/debug/HyprCtl.cpp @@ -44,6 +44,7 @@ using namespace Hyprutils::OS; #include "config/ConfigManager.hpp" #include "helpers/MiscFunctions.hpp" #include "../desktop/view/LayerSurface.hpp" +#include "../desktop/view/Group.hpp" #include "../desktop/rule/Engine.hpp" #include "../desktop/history/WindowHistoryTracker.hpp" #include "../desktop/state/FocusState.hpp" @@ -52,12 +53,15 @@ using namespace Hyprutils::OS; #include "../Compositor.hpp" #include "../managers/input/InputManager.hpp" #include "../managers/XWaylandManager.hpp" -#include "../managers/LayoutManager.hpp" #include "../plugins/PluginSystem.hpp" #include "../managers/animation/AnimationManager.hpp" #include "../debug/HyprNotificationOverlay.hpp" #include "../render/Renderer.hpp" #include "../render/OpenGL.hpp" +#include "../layout/space/Space.hpp" +#include "../layout/algorithm/Algorithm.hpp" +#include "../layout/algorithm/TiledAlgorithm.hpp" +#include "../layout/supplementary/WorkspaceAlgoMatcher.hpp" #if defined(__DragonFly__) || defined(__FreeBSD__) #include @@ -144,13 +148,13 @@ std::string CHyprCtl::getSolitaryBlockedReason(Hyprutils::Memory::CSharedPointer } const std::array DS_REASONS_JSON = { - "\"UNKNOWN\"", "\"USER\"", "\"WINDOWED\"", "\"CONTENT\"", "\"MIRROR\"", "\"RECORD\"", "\"SW\"", - "\"CANDIDATE\"", "\"SURFACE\"", "\"TRANSFORM\"", "\"DMA\"", "\"TEARING\"", "\"FAILED\"", "\"CM\"", + "\"UNKNOWN\"", "\"USER\"", "\"WINDOWED\"", "\"CONTENT\"", "\"MIRROR\"", "\"RECORD\"", "\"SW\"", + "\"CANDIDATE\"", "\"SURFACE\"", "\"TRANSFORM\"", "\"DMA\"", "\"FAILED\"", "\"CM\"", }; const std::array DS_REASONS_TEXT = { - "unknown reason", "user settings", "windowed mode", "content type", "monitor mirrors", "screen record/screenshot", "software renders/cursors", - "missing candidate", "invalid surface", "surface transformations", "invalid buffer", "tearing", "activation failed", "color management", + "unknown reason", "user settings", "windowed mode", "content type", "monitor mirrors", "screen record/screenshot", "software renders/cursors", + "missing candidate", "invalid surface", "surface transformations", "invalid buffer", "activation failed", "color management", }; std::string CHyprCtl::getDSBlockedReason(Hyprutils::Memory::CSharedPointer m, eHyprCtlOutputFormat format) { @@ -173,14 +177,13 @@ std::string CHyprCtl::getDSBlockedReason(Hyprutils::Memory::CSharedPointer TEARING_REASONS_JSON = { - "\"UNKNOWN\"", "\"NOT_TORN\"", "\"USER\"", "\"ZOOM\"", "\"SUPPORT\"", "\"CANDIDATE\"", "\"WINDOW\"", + "\"UNKNOWN\"", "\"NOT_TORN\"", "\"USER\"", "\"ZOOM\"", "\"SUPPORT\"", "\"CANDIDATE\"", "\"WINDOW\"", "\"HW_CURSOR\"", }; -const std::array TEARING_REASONS_TEXT = { - "unknown reason", "next frame is not torn", "user settings", "zoom", "not supported by monitor", "missing candidate", "window settings", -}; +const std::array TEARING_REASONS_TEXT = {"unknown reason", "next frame is not torn", "user settings", "zoom", + "not supported by monitor", "missing candidate", "window settings", "hw cursor"}; -std::string CHyprCtl::getTearingBlockedReason(Hyprutils::Memory::CSharedPointer m, eHyprCtlOutputFormat format) { +std::string CHyprCtl::getTearingBlockedReason(Hyprutils::Memory::CSharedPointer m, eHyprCtlOutputFormat format) { const auto reasons = m->isTearingBlocked(true); if (!reasons || (reasons == CMonitor::TC_NOT_TORN && m->m_tearingState.activelyTearing)) return "null"; @@ -331,23 +334,19 @@ static std::string getTagsData(PHLWINDOW w, eHyprCtlOutputFormat format) { static std::string getGroupedData(PHLWINDOW w, eHyprCtlOutputFormat format) { const bool isJson = format == eHyprCtlOutputFormat::FORMAT_JSON; - if (w->m_groupData.pNextWindow.expired()) + if (!w->m_group) return isJson ? "" : "0"; std::ostringstream result; - PHLWINDOW head = w->getGroupHead(); - PHLWINDOW curr = head; - while (true) { + for (const auto& curr : w->m_group->windows()) { if (isJson) result << std::format("\"0x{:x}\"", rc(curr.get())); else result << std::format("{:x}", rc(curr.get())); - curr = curr->m_groupData.pNextWindow.lock(); - // We've wrapped around to the start, break out without trailing comma - if (curr == head) - break; - result << (isJson ? ", " : ","); + + if (curr != w->m_group->windows().back()) + result << (isJson ? ", " : ","); } return result.str(); @@ -376,7 +375,6 @@ std::string CHyprCtl::getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format) { "name": "{}" }}, "floating": {}, - "pseudo": {}, "monitor": {}, "class": "{}", "title": "{}", @@ -387,6 +385,7 @@ std::string CHyprCtl::getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format) { "pinned": {}, "fullscreen": {}, "fullscreenClient": {}, + "overFullscreen": {}, "grouped": [{}], "tags": [{}], "swallowing": "0x{:x}", @@ -394,29 +393,31 @@ std::string CHyprCtl::getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format) { "inhibitingIdle": {}, "xdgTag": "{}", "xdgDescription": "{}", - "contentType": "{}" + "contentType": "{}", + "stableId": "{:x}" }},)#", rc(w.get()), (w->m_isMapped ? "true" : "false"), (w->isHidden() ? "true" : "false"), sc(w->m_realPosition->goal().x), sc(w->m_realPosition->goal().y), sc(w->m_realSize->goal().x), sc(w->m_realSize->goal().y), w->m_workspace ? w->workspaceID() : WORKSPACE_INVALID, - escapeJSONStrings(!w->m_workspace ? "" : w->m_workspace->m_name), (sc(w->m_isFloating) == 1 ? "true" : "false"), (w->m_isPseudotiled ? "true" : "false"), - w->monitorID(), escapeJSONStrings(w->m_class), escapeJSONStrings(w->m_title), escapeJSONStrings(w->m_initialClass), escapeJSONStrings(w->m_initialTitle), w->getPID(), - (sc(w->m_isX11) == 1 ? "true" : "false"), (w->m_pinned ? "true" : "false"), sc(w->m_fullscreenState.internal), sc(w->m_fullscreenState.client), + escapeJSONStrings(!w->m_workspace ? "" : w->m_workspace->m_name), (sc(w->m_isFloating) == 1 ? "true" : "false"), w->monitorID(), escapeJSONStrings(w->m_class), + escapeJSONStrings(w->m_title), escapeJSONStrings(w->m_initialClass), escapeJSONStrings(w->m_initialTitle), w->getPID(), (sc(w->m_isX11) == 1 ? "true" : "false"), + (w->m_pinned ? "true" : "false"), sc(w->m_fullscreenState.internal), sc(w->m_fullscreenState.client), (w->m_createdOverFullscreen ? "true" : "false"), getGroupedData(w, format), getTagsData(w, format), rc(w->m_swallowed.get()), getFocusHistoryID(w), (g_pInputManager->isWindowInhibiting(w, false) ? "true" : "false"), escapeJSONStrings(w->xdgTag().value_or("")), escapeJSONStrings(w->xdgDescription().value_or("")), - escapeJSONStrings(NContentType::toString(w->getContentType()))); + escapeJSONStrings(NContentType::toString(w->getContentType())), w->m_stableID); } else { return std::format( - "Window {:x} -> {}:\n\tmapped: {}\n\thidden: {}\n\tat: {},{}\n\tsize: {},{}\n\tworkspace: {} ({})\n\tfloating: {}\n\tpseudo: {}\n\tmonitor: {}\n\tclass: {}\n\ttitle: " + "Window {:x} -> {}:\n\tmapped: {}\n\thidden: {}\n\tat: {},{}\n\tsize: {},{}\n\tworkspace: {} ({})\n\tfloating: {}\n\tmonitor: {}\n\tclass: {}\n\ttitle: " "{}\n\tinitialClass: {}\n\tinitialTitle: {}\n\tpid: " "{}\n\txwayland: {}\n\tpinned: " - "{}\n\tfullscreen: {}\n\tfullscreenClient: {}\n\tgrouped: {}\n\ttags: {}\n\tswallowing: {:x}\n\tfocusHistoryID: {}\n\tinhibitingIdle: {}\n\txdgTag: " - "{}\n\txdgDescription: {}\n\tcontentType: {}\n\n", + "{}\n\tfullscreen: {}\n\tfullscreenClient: {}\n\toverFullscreen: {}\n\tgrouped: {}\n\ttags: {}\n\tswallowing: {:x}\n\tfocusHistoryID: {}\n\tinhibitingIdle: " + "{}\n\txdgTag: " + "{}\n\txdgDescription: {}\n\tcontentType: {}\n\tstableID: {:x}\n\n", rc(w.get()), w->m_title, sc(w->m_isMapped), sc(w->isHidden()), sc(w->m_realPosition->goal().x), sc(w->m_realPosition->goal().y), sc(w->m_realSize->goal().x), sc(w->m_realSize->goal().y), w->m_workspace ? w->workspaceID() : WORKSPACE_INVALID, - (!w->m_workspace ? "" : w->m_workspace->m_name), sc(w->m_isFloating), sc(w->m_isPseudotiled), w->monitorID(), w->m_class, w->m_title, w->m_initialClass, - w->m_initialTitle, w->getPID(), sc(w->m_isX11), sc(w->m_pinned), sc(w->m_fullscreenState.internal), sc(w->m_fullscreenState.client), + (!w->m_workspace ? "" : w->m_workspace->m_name), sc(w->m_isFloating), w->monitorID(), w->m_class, w->m_title, w->m_initialClass, w->m_initialTitle, w->getPID(), + sc(w->m_isX11), sc(w->m_pinned), sc(w->m_fullscreenState.internal), sc(w->m_fullscreenState.client), sc(w->m_createdOverFullscreen), getGroupedData(w, format), getTagsData(w, format), rc(w->m_swallowed.get()), getFocusHistoryID(w), sc(g_pInputManager->isWindowInhibiting(w, false)), - w->xdgTag().value_or(""), w->xdgDescription().value_or(""), NContentType::toString(w->getContentType())); + w->xdgTag().value_or(""), w->xdgDescription().value_or(""), NContentType::toString(w->getContentType()), w->m_stableID); } } @@ -450,8 +451,15 @@ static std::string clientsRequest(eHyprCtlOutputFormat format, std::string reque } std::string CHyprCtl::getWorkspaceData(PHLWORKSPACE w, eHyprCtlOutputFormat format) { - const auto PLASTW = w->getLastFocusedWindow(); - const auto PMONITOR = w->m_monitor.lock(); + const auto PLASTW = w->getLastFocusedWindow(); + const auto PMONITOR = w->m_monitor.lock(); + + std::string layoutName = "unknown"; + if (w->m_space && w->m_space->algorithm() && w->m_space->algorithm()->tiledAlgo()) { + const auto& TILED_ALGO = w->m_space->algorithm()->tiledAlgo(); + layoutName = Layout::Supplementary::algoMatcher()->getNameForTiledAlgo(&typeid(*TILED_ALGO.get())); + } + if (format == eHyprCtlOutputFormat::FORMAT_JSON) { return std::format(R"#({{ "id": {}, @@ -462,16 +470,17 @@ std::string CHyprCtl::getWorkspaceData(PHLWORKSPACE w, eHyprCtlOutputFormat form "hasfullscreen": {}, "lastwindow": "0x{:x}", "lastwindowtitle": "{}", - "ispersistent": {} + "ispersistent": {}, + "tiledLayout": "{}" }})#", w->m_id, escapeJSONStrings(w->m_name), escapeJSONStrings(PMONITOR ? PMONITOR->m_name : "?"), escapeJSONStrings(PMONITOR ? std::to_string(PMONITOR->m_id) : "null"), w->getWindows(), w->m_hasFullscreenWindow ? "true" : "false", - rc(PLASTW.get()), PLASTW ? escapeJSONStrings(PLASTW->m_title) : "", w->isPersistent() ? "true" : "false"); + rc(PLASTW.get()), PLASTW ? escapeJSONStrings(PLASTW->m_title) : "", w->isPersistent() ? "true" : "false", escapeJSONStrings(layoutName)); } else { - return std::format( - "workspace ID {} ({}) on monitor {}:\n\tmonitorID: {}\n\twindows: {}\n\thasfullscreen: {}\n\tlastwindow: 0x{:x}\n\tlastwindowtitle: {}\n\tispersistent: {}\n\n", - w->m_id, w->m_name, PMONITOR ? PMONITOR->m_name : "?", PMONITOR ? std::to_string(PMONITOR->m_id) : "null", w->getWindows(), sc(w->m_hasFullscreenWindow), - rc(PLASTW.get()), PLASTW ? PLASTW->m_title : "", sc(w->isPersistent())); + return std::format("workspace ID {} ({}) on monitor {}:\n\tmonitorID: {}\n\twindows: {}\n\thasfullscreen: {}\n\tlastwindow: 0x{:x}\n\tlastwindowtitle: {}\n\tispersistent: " + "{}\n\ttiledLayout: {}\n\n", + w->m_id, w->m_name, PMONITOR ? PMONITOR->m_name : "?", PMONITOR ? std::to_string(PMONITOR->m_id) : "null", w->getWindows(), + sc(w->m_hasFullscreenWindow), rc(PLASTW.get()), PLASTW ? PLASTW->m_title : "", sc(w->isPersistent()), layoutName); } } @@ -670,28 +679,6 @@ static std::string layersRequest(eHyprCtlOutputFormat format, std::string reques return result; } -static std::string layoutsRequest(eHyprCtlOutputFormat format, std::string request) { - std::string result = ""; - if (format == eHyprCtlOutputFormat::FORMAT_JSON) { - result += "["; - - for (auto const& m : g_pLayoutManager->getAllLayoutNames()) { - result += std::format( - R"#( - "{}",)#", - m); - } - trimTrailingComma(result); - - result += "\n]\n"; - } else { - for (auto const& m : g_pLayoutManager->getAllLayoutNames()) { - result += std::format("{}\n", m); - } - } - return result; -} - static std::string configErrorsRequest(eHyprCtlOutputFormat format, std::string request) { std::string result = ""; std::string currErrors = g_pConfigManager->getErrors(); @@ -802,7 +789,7 @@ static std::string devicesRequest(eHyprCtlOutputFormat format, std::string reque result += std::format( R"#( {{ "address": "0x{:x}", - "type": "tabletTool", + "type": "tabletTool" }},)#", rc(d.get())); } @@ -1294,8 +1281,13 @@ static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in) g_pHyprCtl->m_currentRequestParams.isDynamicKeyword = false; + if (COMMAND == "source") { + g_pConfigManager->m_wantsMonitorReload = true; + g_pEventLoopManager->doLater([] { g_pConfigManager->reloadRules(); }); + } + // if we are executing a dynamic source we have to reload everything, so every if will have a check for source. - if (COMMAND == "monitor" || COMMAND == "source") + if (COMMAND == "monitor") g_pConfigManager->m_wantsMonitorReload = true; // for monitor keywords if (COMMAND.contains("monitorv2")) @@ -1308,10 +1300,8 @@ static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in) g_pInputManager->setTabletConfigs(); // update tablets } - static auto PLAYOUT = CConfigValue("general:layout"); - - if (COMMAND.contains("general:layout")) - g_pLayoutManager->switchToLayout(*PLAYOUT); // update layout + if (COMMAND.contains("general:layout") || (COMMAND.contains("workspace") && VALUE.contains("layout:"))) + Layout::Supplementary::algoMatcher()->updateWorkspaceLayouts(); if (COMMAND.contains("decoration:screen_shader") || COMMAND == "source") g_pHyprOpenGL->m_reloadScreenShader = true; @@ -1331,13 +1321,22 @@ static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in) for (auto const& m : g_pCompositor->m_monitors) { *(m->m_cursorZoom) = *PZOOMFACTOR; g_pHyprRenderer->damageMonitor(m); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id); + if (m->m_activeWorkspace) + m->m_activeWorkspace->m_space->recalculate(); } } if (COMMAND.contains("windowrule ") || COMMAND.contains("windowrule[")) g_pConfigManager->reloadRules(); + if (COMMAND.contains("layerrule") || COMMAND.contains("layerrule[")) { + g_pConfigManager->reloadRules(); + // Damage all monitors to redraw static layers. + for (auto const& m : g_pCompositor->m_monitors) { + g_pHyprRenderer->damageMonitor(m); + } + } + if (COMMAND.contains("workspace")) g_pConfigManager->ensurePersistentWorkspacesPresent(); @@ -1597,7 +1596,7 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ static auto PGROUPACTIVELOCKEDCOL = CConfigValue("group:col.border_locked_active"); static auto PGROUPINACTIVELOCKEDCOL = CConfigValue("group:col.border_locked_inactive"); - const bool GROUPLOCKED = PWINDOW->m_groupData.pNextWindow.lock() ? PWINDOW->getGroupHead()->m_groupData.locked : false; + const bool GROUPLOCKED = PWINDOW->m_group ? PWINDOW->m_group->locked() : false; if (active) { auto* const ACTIVECOL = (CGradientValueData*)(PACTIVECOL.ptr())->getData(); @@ -1605,7 +1604,7 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ auto* const GROUPACTIVECOL = (CGradientValueData*)(PGROUPACTIVECOL.ptr())->getData(); auto* const GROUPACTIVELOCKEDCOL = (CGradientValueData*)(PGROUPACTIVELOCKEDCOL.ptr())->getData(); const auto* const ACTIVECOLOR = - !PWINDOW->m_groupData.pNextWindow.lock() ? (!PWINDOW->m_groupData.deny ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL); + !PWINDOW->m_group ? (!(PWINDOW->m_groupRules & Desktop::View::GROUP_DENY) ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL); std::string borderColorString = PWINDOW->m_ruleApplicator->activeBorderColor().valueOr(*ACTIVECOLOR).toString(); if (FORMNORM) @@ -1617,8 +1616,8 @@ static std::string dispatchGetProp(eHyprCtlOutputFormat format, std::string requ auto* const NOGROUPINACTIVECOL = (CGradientValueData*)(PNOGROUPINACTIVECOL.ptr())->getData(); auto* const GROUPINACTIVECOL = (CGradientValueData*)(PGROUPINACTIVECOL.ptr())->getData(); auto* const GROUPINACTIVELOCKEDCOL = (CGradientValueData*)(PGROUPINACTIVELOCKEDCOL.ptr())->getData(); - const auto* const INACTIVECOLOR = !PWINDOW->m_groupData.pNextWindow.lock() ? (!PWINDOW->m_groupData.deny ? INACTIVECOL : NOGROUPINACTIVECOL) : - (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL); + const auto* const INACTIVECOLOR = !PWINDOW->m_group ? (!(PWINDOW->m_groupRules & Desktop::View::GROUP_DENY) ? INACTIVECOL : NOGROUPINACTIVECOL) : + (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL); std::string borderColorString = PWINDOW->m_ruleApplicator->inactiveBorderColor().valueOr(*INACTIVECOLOR).toString(); if (FORMNORM) @@ -2050,7 +2049,12 @@ static std::string submapRequest(eHyprCtlOutputFormat format, std::string reques } static std::string reloadShaders(eHyprCtlOutputFormat format, std::string request) { - if (g_pHyprOpenGL->initShaders()) + CVarList vars(request, 0, ' '); + + if (vars.size() > 2) + return "too many args"; + + if (g_pHyprOpenGL && g_pHyprRenderer->reloadShaders(vars.size() == 2 ? vars[1] : "")) return format == FORMAT_JSON ? "{\"ok\": true}" : "ok"; else return format == FORMAT_JSON ? "{\"ok\": false}" : "error"; @@ -2073,13 +2077,12 @@ CHyprCtl::CHyprCtl() { registerCommand(SHyprCtlCommand{"systeminfo", true, systemInfoRequest}); registerCommand(SHyprCtlCommand{"animations", true, animationsRequest}); registerCommand(SHyprCtlCommand{"rollinglog", true, rollinglogRequest}); - 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{.name = "reloadshaders", .exact = true, .fn = reloadShaders}); + registerCommand(SHyprCtlCommand{.name = "reloadshaders", .exact = false, .fn = reloadShaders}); registerCommand(SHyprCtlCommand{"monitors", false, monitorsRequest}); registerCommand(SHyprCtlCommand{"reload", false, reloadRequest}); registerCommand(SHyprCtlCommand{"plugin", false, dispatchPlugin}); @@ -2186,10 +2189,6 @@ std::string CHyprCtl::getReply(std::string request) { g_pInputManager->setTouchDeviceConfigs(); // update touch device cfgs g_pInputManager->setTabletConfigs(); // update tablets - static auto PLAYOUT = CConfigValue("general:layout"); - - g_pLayoutManager->switchToLayout(*PLAYOUT); // update layout - g_pHyprOpenGL->m_reloadScreenShader = true; for (auto& [m, rd] : g_pHyprOpenGL->m_monitorRenderResources) { @@ -2203,9 +2202,17 @@ std::string CHyprCtl::getReply(std::string request) { Desktop::Rule::ruleEngine()->updateAllRules(); } + for (const auto& ws : g_pCompositor->getWorkspaces()) { + if (!ws) + continue; + + ws->updateWindows(); + ws->updateWindowData(); + ws->updateWindowDecos(); + } + for (auto const& m : g_pCompositor->m_monitors) { g_pHyprRenderer->damageMonitor(m); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id); } } @@ -2217,16 +2224,38 @@ std::string CHyprCtl::makeDynamicCall(const std::string& input) { } static bool successWrite(int fd, const std::string& data, bool needLog = true) { - if (write(fd, data.c_str(), data.length()) > 0) - return true; + size_t totalWritten = 0; + size_t remaining = data.length(); + size_t waitsDone = 0; + constexpr const size_t MAX_WAITS = 20; // 2000µs = 2ms - if (errno == EAGAIN) - return true; + while (totalWritten < data.length()) { + ssize_t written = write(fd, data.c_str() + totalWritten, remaining); - if (needLog) - Log::logger->log(Log::ERR, "Couldn't write to socket. Error: " + std::string(strerror(errno))); + if (waitsDone > MAX_WAITS) { + Log::logger->log(Log::ERR, "Couldn't write to socket. Buffer was full and the client couldn't read in time."); + return false; + } - return false; + if (written < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + // socket buffer full, wait a bit and retry + std::this_thread::sleep_for(std::chrono::microseconds(100)); + waitsDone++; + continue; + } + if (needLog) + Log::logger->log(Log::ERR, "Couldn't write to socket. Error: {}", strerror(errno)); + return false; + } + + waitsDone = 0; + + totalWritten += written; + remaining -= written; + } + + return true; } static void runWritingDebugLogThread(const int conn) { diff --git a/src/debug/HyprDebugOverlay.cpp b/src/debug/HyprDebugOverlay.cpp index 8f4189b0..17ce12fa 100644 --- a/src/debug/HyprDebugOverlay.cpp +++ b/src/debug/HyprDebugOverlay.cpp @@ -8,7 +8,7 @@ #include "../desktop/state/FocusState.hpp" CHyprDebugOverlay::CHyprDebugOverlay() { - m_texture = makeShared(); + m_texture = g_pHyprRenderer->createTexture(); } void CHyprMonitorDebugOverlay::renderData(PHLMONITOR pMonitor, float durationUs) { @@ -259,15 +259,7 @@ void CHyprDebugOverlay::draw() { cairo_surface_flush(m_cairoSurface); // copy the data to an OpenGL texture we have - const auto DATA = cairo_image_surface_get_data(m_cairoSurface); - m_texture->allocate(); - m_texture->bind(); - m_texture->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST); - m_texture->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST); - m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE); - m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED); - - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA); + m_texture = g_pHyprRenderer->createTexture(m_cairoSurface); CTexPassElement::SRenderData data; data.tex = m_texture; diff --git a/src/debug/HyprDebugOverlay.hpp b/src/debug/HyprDebugOverlay.hpp index 72987d94..bf188359 100644 --- a/src/debug/HyprDebugOverlay.hpp +++ b/src/debug/HyprDebugOverlay.hpp @@ -42,7 +42,7 @@ class CHyprDebugOverlay { cairo_surface_t* m_cairoSurface = nullptr; cairo_t* m_cairo = nullptr; - SP m_texture; + SP m_texture; friend class CHyprMonitorDebugOverlay; friend class CHyprRenderer; diff --git a/src/debug/HyprNotificationOverlay.cpp b/src/debug/HyprNotificationOverlay.cpp index 1c66a53b..e67b0434 100644 --- a/src/debug/HyprNotificationOverlay.cpp +++ b/src/debug/HyprNotificationOverlay.cpp @@ -4,9 +4,9 @@ #include "../Compositor.hpp" #include "../config/ConfigValue.hpp" #include "../render/pass/TexPassElement.hpp" +#include "../event/EventBus.hpp" #include "../managers/animation/AnimationManager.hpp" -#include "../managers/HookSystemManager.hpp" #include "../render/Renderer.hpp" static inline auto iconBackendFromLayout(PangoLayout* layout) { @@ -22,14 +22,12 @@ static inline auto iconBackendFromLayout(PangoLayout* layout) { } CHyprNotificationOverlay::CHyprNotificationOverlay() { - static auto P = g_pHookSystem->hookDynamic("focusedMon", [&](void* self, SCallbackInfo& info, std::any param) { + static auto P = Event::bus()->m_events.monitor.focused.listen([&](PHLMONITOR mon) { if (m_notifications.empty()) return; g_pHyprRenderer->damageBox(m_lastDamage); }); - - m_texture = makeShared(); } CHyprNotificationOverlay::~CHyprNotificationOverlay() { @@ -232,16 +230,7 @@ void CHyprNotificationOverlay::draw(PHLMONITOR pMonitor) { m_lastDamage = damage; - // copy the data to an OpenGL texture we have - const auto DATA = cairo_image_surface_get_data(m_cairoSurface); - m_texture->allocate(); - m_texture->bind(); - m_texture->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST); - m_texture->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST); - m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE); - m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED); - - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, MONSIZE.x, MONSIZE.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA); + m_texture = g_pHyprRenderer->createTexture(m_cairoSurface); CTexPassElement::SRenderData data; data.tex = m_texture; diff --git a/src/debug/HyprNotificationOverlay.hpp b/src/debug/HyprNotificationOverlay.hpp index 868eb05b..ec7aed72 100644 --- a/src/debug/HyprNotificationOverlay.hpp +++ b/src/debug/HyprNotificationOverlay.hpp @@ -18,7 +18,7 @@ enum eIconBackend : uint8_t { static const std::array, 3 /* backends */> ICONS_ARRAY = { std::array{"[!]", "[i]", "[Hint]", "[Err]", "[?]", "[ok]", ""}, std::array{"", "", "", "", "", "󰸞", ""}, std::array{"", "", "", "", "", ""}}; -static const std::array ICONS_COLORS = {CHyprColor{255.0 / 255.0, 204 / 255.0, 102 / 255.0, 1.0}, +static const std::array ICONS_COLORS = {CHyprColor{1.0, 204 / 255.0, 102 / 255.0, 1.0}, CHyprColor{128 / 255.0, 255 / 255.0, 255 / 255.0, 1.0}, CHyprColor{179 / 255.0, 255 / 255.0, 204 / 255.0, 1.0}, CHyprColor{255 / 255.0, 77 / 255.0, 77 / 255.0, 1.0}, @@ -57,7 +57,7 @@ class CHyprNotificationOverlay { PHLMONITORREF m_lastMonitor; Vector2D m_lastSize = Vector2D(-1, -1); - SP m_texture; + SP m_texture; }; inline UP g_pHyprNotificationOverlay; diff --git a/src/debug/log/Logger.cpp b/src/debug/log/Logger.cpp index 44b82a50..1aca4d51 100644 --- a/src/debug/log/Logger.cpp +++ b/src/debug/log/Logger.cpp @@ -1,9 +1,8 @@ #include "Logger.hpp" #include "RollingLogFollow.hpp" -#include "../../defines.hpp" +#include "../../event/EventBus.hpp" -#include "../../managers/HookSystemManager.hpp" #include "../../config/ConfigValue.hpp" using namespace Log; @@ -39,7 +38,7 @@ void CLogger::initIS(const std::string_view& IS) { } void CLogger::initCallbacks() { - static auto P = g_pHookSystem->hookDynamic("configReloaded", [this](void* hk, SCallbackInfo& info, std::any param) { recheckCfg(); }); + static auto P = Event::bus()->m_events.config.reloaded.listen([this]() { recheckCfg(); }); recheckCfg(); } diff --git a/src/defines.hpp b/src/defines.hpp index cd4c524d..571679dc 100644 --- a/src/defines.hpp +++ b/src/defines.hpp @@ -5,3 +5,7 @@ #include "helpers/Color.hpp" #include "macros.hpp" #include "desktop/DesktopTypes.hpp" + +#if !defined(__GXX_RTTI) +#error "Hyprland requires C++ RTTI. Shit will hit the fan otherwise. Do not even try." +#endif diff --git a/src/desktop/Workspace.cpp b/src/desktop/Workspace.cpp index 2895137d..5df3f087 100644 --- a/src/desktop/Workspace.cpp +++ b/src/desktop/Workspace.cpp @@ -1,10 +1,13 @@ #include "Workspace.hpp" +#include "view/Group.hpp" #include "../Compositor.hpp" #include "../config/ConfigValue.hpp" #include "config/ConfigManager.hpp" #include "managers/animation/AnimationManager.hpp" #include "../managers/EventManager.hpp" -#include "../managers/HookSystemManager.hpp" +#include "../layout/space/Space.hpp" +#include "../layout/supplementary/WorkspaceAlgoMatcher.hpp" +#include "../event/EventBus.hpp" #include #include @@ -34,13 +37,14 @@ void CWorkspace::init(PHLWORKSPACE self) { if (RULEFORTHIS.defaultName.has_value()) m_name = RULEFORTHIS.defaultName.value(); - m_focusedWindowHook = g_pHookSystem->hookDynamic("closeWindow", [this](void* self, SCallbackInfo& info, std::any param) { - const auto PWINDOW = std::any_cast(param); - - if (PWINDOW == m_lastFocusedWindow.lock()) + m_focusedWindowHook = Event::bus()->m_events.window.close.listen([this](PHLWINDOW pWindow) { + if (pWindow == m_lastFocusedWindow.lock()) m_lastFocusedWindow.reset(); }); + m_space = Layout::CSpace::create(m_self.lock()); + m_space->setAlgorithmProvider(Layout::Supplementary::algoMatcher()->createAlgorithmForWorkspace(m_self.lock())); + m_inert = false; const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(self); @@ -52,22 +56,19 @@ void CWorkspace::init(PHLWORKSPACE self) { g_pEventManager->postEvent({.event = "createworkspace", .data = m_name}); g_pEventManager->postEvent({.event = "createworkspacev2", .data = std::format("{},{}", m_id, m_name)}); - EMIT_HOOK_EVENT("createWorkspace", this); + Event::bus()->m_events.workspace.created.emit(self); } CWorkspace::~CWorkspace() { Log::logger->log(Log::DEBUG, "Destroying workspace ID {}", m_id); - // check if g_pHookSystem and g_pEventManager exist, they might be destroyed as in when the compositor is closing. - if (g_pHookSystem) - g_pHookSystem->unhook(m_focusedWindowHook); - if (g_pEventManager) { g_pEventManager->postEvent({.event = "destroyworkspace", .data = m_name}); g_pEventManager->postEvent({.event = "destroyworkspacev2", .data = std::format("{},{}", m_id, m_name)}); - EMIT_HOOK_EVENT("destroyWorkspace", this); } + Event::bus()->m_events.workspace.removed.emit(m_self); + m_events.destroy.emit(); } @@ -406,14 +407,19 @@ bool CWorkspace::isVisibleNotCovered() { int CWorkspace::getWindows(std::optional onlyTiled, std::optional onlyPinned, std::optional onlyVisible) { int no = 0; - for (auto const& w : g_pCompositor->m_windows) { - if (w->workspaceID() != m_id || !w->m_isMapped) + + if (!m_space) + return 0; + + for (auto const& t : m_space->targets()) { + if (!t) continue; - if (onlyTiled.has_value() && w->m_isFloating == onlyTiled.value()) + + if (onlyTiled.has_value() && t->floating() == onlyTiled.value()) continue; - if (onlyPinned.has_value() && w->m_pinned != onlyPinned.value()) + if (onlyPinned.has_value() && (!t->window() || t->window()->m_pinned != onlyPinned.value())) continue; - if (onlyVisible.has_value() && w->isHidden() == onlyVisible.value()) + if (onlyVisible.has_value() && (!t->window() || t->window()->isHidden() == onlyVisible.value())) continue; no++; } @@ -423,16 +429,16 @@ int CWorkspace::getWindows(std::optional onlyTiled, std::optional on int CWorkspace::getGroups(std::optional onlyTiled, std::optional onlyPinned, std::optional onlyVisible) { int no = 0; - for (auto const& w : g_pCompositor->m_windows) { - if (w->workspaceID() != m_id || !w->m_isMapped) + for (auto const& g : Desktop::View::groups()) { + const auto HEAD = g->head(); + + if (HEAD->workspaceID() != m_id || !HEAD->m_isMapped) continue; - if (!w->m_groupData.head) + if (onlyTiled.has_value() && HEAD->m_isFloating == onlyTiled.value()) continue; - if (onlyTiled.has_value() && w->m_isFloating == onlyTiled.value()) + if (onlyPinned.has_value() && HEAD->m_pinned != onlyPinned.value()) continue; - if (onlyPinned.has_value() && w->m_pinned != onlyPinned.value()) - continue; - if (onlyVisible.has_value() && w->isHidden() == onlyVisible.value()) + if (onlyVisible.has_value() && g->current()->isHidden() == onlyVisible.value()) continue; no++; } @@ -514,13 +520,11 @@ void CWorkspace::rename(const std::string& name) { } void CWorkspace::updateWindows() { - m_hasFullscreenWindow = std::ranges::any_of(g_pCompositor->m_windows, [this](const auto& w) { return w->m_isMapped && w->m_workspace == m_self && w->isFullscreen(); }); + m_hasFullscreenWindow = std::ranges::any_of(m_space->targets(), [](const auto& t) { return t->fullscreenMode() != FSMODE_NONE; }); - for (auto const& w : g_pCompositor->m_windows) { - if (!w->m_isMapped || w->m_workspace != m_self) - continue; - - w->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE); + for (auto const& t : m_space->targets()) { + if (t->window()) + t->window()->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE); } } diff --git a/src/desktop/Workspace.hpp b/src/desktop/Workspace.hpp index 1aad1aaa..87d1c2d8 100644 --- a/src/desktop/Workspace.hpp +++ b/src/desktop/Workspace.hpp @@ -6,6 +6,10 @@ #include "../helpers/MiscFunctions.hpp" #include "../helpers/signal/Signal.hpp" +namespace Layout { + class CSpace; +}; + enum eFullscreenMode : int8_t { FSMODE_NONE = 0, FSMODE_MAXIMIZED = 1 << 0, @@ -20,7 +24,9 @@ class CWorkspace { CWorkspace(WORKSPACEID id, PHLMONITOR monitor, std::string name, bool special = false, bool isEmpty = true); ~CWorkspace(); - WP m_self; + WP m_self; + + SP m_space; // Workspaces ID-based have IDs > 0 // and workspaces name-based have IDs starting with -1337 @@ -87,13 +93,13 @@ class CWorkspace { } m_events; private: - void init(PHLWORKSPACE self); + void init(PHLWORKSPACE self); - SP m_focusedWindowHook; - bool m_inert = true; + CHyprSignalListener m_focusedWindowHook; + bool m_inert = true; - SP m_selfPersistent; // for persistent workspaces. - bool m_persistent = false; + SP m_selfPersistent; // for persistent workspaces. + bool m_persistent = false; }; inline bool valid(const PHLWORKSPACE& ref) { diff --git a/src/desktop/history/WindowHistoryTracker.cpp b/src/desktop/history/WindowHistoryTracker.cpp index 5dc0742f..1dd32164 100644 --- a/src/desktop/history/WindowHistoryTracker.cpp +++ b/src/desktop/history/WindowHistoryTracker.cpp @@ -1,7 +1,7 @@ #include "WindowHistoryTracker.hpp" -#include "../../managers/HookSystemManager.hpp" #include "../view/Window.hpp" +#include "../../event/EventBus.hpp" using namespace Desktop; using namespace Desktop::History; @@ -12,18 +12,12 @@ SP History::windowTracker() { } CWindowHistoryTracker::CWindowHistoryTracker() { - static auto P = g_pHookSystem->hookDynamic("openWindowEarly", [this](void* self, SCallbackInfo& info, std::any data) { - auto window = std::any_cast(data); - + static auto P = Event::bus()->m_events.window.openEarly.listen([this](PHLWINDOW pWindow) { // add a last track - m_history.insert(m_history.begin(), window); + m_history.insert(m_history.begin(), pWindow); }); - static auto P1 = g_pHookSystem->hookDynamic("activeWindow", [this](void* self, SCallbackInfo& info, std::any data) { - auto window = std::any_cast(data); - - track(window); - }); + static auto P1 = Event::bus()->m_events.window.active.listen([this](PHLWINDOW window, uint8_t reason) { track(window); }); } void CWindowHistoryTracker::track(PHLWINDOW w) { diff --git a/src/desktop/history/WorkspaceHistoryTracker.cpp b/src/desktop/history/WorkspaceHistoryTracker.cpp index bfedda13..daa115f8 100644 --- a/src/desktop/history/WorkspaceHistoryTracker.cpp +++ b/src/desktop/history/WorkspaceHistoryTracker.cpp @@ -2,9 +2,13 @@ #include "../../helpers/Monitor.hpp" #include "../Workspace.hpp" -#include "../../managers/HookSystemManager.hpp" +#include "../state/FocusState.hpp" +#include "../../managers/eventLoop/EventLoopManager.hpp" +#include "../../event/EventBus.hpp" #include "../../config/ConfigValue.hpp" +#include + using namespace Desktop; using namespace Desktop::History; @@ -14,27 +18,16 @@ SP History::workspaceTracker() { } CWorkspaceHistoryTracker::CWorkspaceHistoryTracker() { - static auto P = g_pHookSystem->hookDynamic("workspace", [this](void* self, SCallbackInfo& info, std::any data) { - auto workspace = std::any_cast(data); - track(workspace); - }); + static auto P = Event::bus()->m_events.workspace.active.listen([this](PHLWORKSPACE workspace) { track(workspace); }); - static auto P1 = g_pHookSystem->hookDynamic("monitorAdded", [this](void* self, SCallbackInfo& info, std::any data) { - auto mon = std::any_cast(data); - track(mon); - }); -} - -CWorkspaceHistoryTracker::SMonitorData& CWorkspaceHistoryTracker::dataFor(PHLMONITOR mon) { - for (auto& ref : m_monitorDatas) { - if (ref.monitor != mon) - continue; - - return ref; - } - - return m_monitorDatas.emplace_back(SMonitorData{ - .monitor = mon, + static auto P1 = Event::bus()->m_events.monitor.focused.listen([this](PHLMONITOR mon) { + // This sucks ASS, but we have to do this because switching to a workspace on another mon will trigger a workspace event right afterwards and we don't + // want to remember the workspace that was not visible there + // TODO: do something about this + g_pEventLoopManager->doLater([this, mon = PHLMONITORREF{mon}] { + if (mon) + track(mon->m_activeWorkspace); + }); }); } @@ -52,44 +45,32 @@ CWorkspaceHistoryTracker::SWorkspacePreviousData& CWorkspaceHistoryTracker::data } void CWorkspaceHistoryTracker::track(PHLWORKSPACE w) { - if (!w->m_monitor) + if (!w || !w->m_monitor || w == m_lastWorkspaceData.workspace) return; - static auto PALLOWWORKSPACECYCLES = CConfigValue("binds:allow_workspace_cycles"); + static auto PALLOWWORKSPACECYCLES = CConfigValue("binds:allow_workspace_cycles"); - auto& data = dataFor(w); - auto& monData = dataFor(w->m_monitor.lock()); + auto& data = dataFor(w); - if (!monData.workspace) { - data.previous.reset(); - data.previousID = WORKSPACE_INVALID; - data.previousName = ""; + Hyprutils::Utils::CScopeGuard x([&] { setLastWorkspaceData(w); }); + + if (m_lastWorkspaceData.workspace == w && !*PALLOWWORKSPACECYCLES) return; + + data.previous = m_lastWorkspaceData.workspace; + if (m_lastWorkspaceData.workspace) { + data.previousName = m_lastWorkspaceData.workspace->m_name; + data.previousID = m_lastWorkspaceData.workspace->m_id; + data.previousMon = m_lastWorkspaceData.workspace->m_monitor; + } else { + data.previousName = m_lastWorkspaceData.workspaceName; + data.previousID = m_lastWorkspaceData.workspaceID; + data.previousMon = m_lastWorkspaceData.monitor; } - - if (monData.workspace == w && !*PALLOWWORKSPACECYCLES) { - track(w->m_monitor.lock()); - return; - } - - data.previous = monData.workspace; - data.previousName = monData.workspace->m_name; - data.previousID = monData.workspace->m_id; - data.previousMon = monData.workspace->m_monitor; - - track(w->m_monitor.lock()); -} - -void CWorkspaceHistoryTracker::track(PHLMONITOR mon) { - auto& data = dataFor(mon); - data.workspace = mon->m_activeWorkspace; - data.workspaceName = mon->m_activeWorkspace ? mon->m_activeWorkspace->m_name : ""; - data.workspaceID = mon->activeWorkspaceID(); } void CWorkspaceHistoryTracker::gc() { std::erase_if(m_datas, [](const auto& e) { return !e.workspace; }); - std::erase_if(m_monitorDatas, [](const auto& e) { return !e.monitor; }); } const CWorkspaceHistoryTracker::SWorkspacePreviousData* CWorkspaceHistoryTracker::previousWorkspace(PHLWORKSPACE ws) { @@ -156,3 +137,15 @@ SWorkspaceIDName CWorkspaceHistoryTracker::previousWorkspaceIDName(PHLWORKSPACE return SWorkspaceIDName{.id = DATA->previousID, .name = DATA->previousName, .isAutoIDd = DATA->previousID <= 0}; } + +void CWorkspaceHistoryTracker::setLastWorkspaceData(PHLWORKSPACE w) { + if (!w) { + m_lastWorkspaceData = {}; + return; + } + + m_lastWorkspaceData.workspace = w; + m_lastWorkspaceData.workspaceID = w->m_id; + m_lastWorkspaceData.workspaceName = w->m_name; + m_lastWorkspaceData.monitor = w->m_monitor; +} diff --git a/src/desktop/history/WorkspaceHistoryTracker.hpp b/src/desktop/history/WorkspaceHistoryTracker.hpp index 4a3c109a..baecb363 100644 --- a/src/desktop/history/WorkspaceHistoryTracker.hpp +++ b/src/desktop/history/WorkspaceHistoryTracker.hpp @@ -32,21 +32,19 @@ namespace Desktop::History { SWorkspaceIDName previousWorkspaceIDName(PHLWORKSPACE ws, PHLMONITOR restrict); private: - struct SMonitorData { + struct SLastWorkspaceData { PHLMONITORREF monitor; PHLWORKSPACEREF workspace; std::string workspaceName = ""; WORKSPACEID workspaceID = WORKSPACE_INVALID; - }; + } m_lastWorkspaceData; std::vector m_datas; - std::vector m_monitorDatas; void track(PHLWORKSPACE w); - void track(PHLMONITOR mon); void gc(); + void setLastWorkspaceData(PHLWORKSPACE w); - SMonitorData& dataFor(PHLMONITOR mon); SWorkspacePreviousData& dataFor(PHLWORKSPACE ws); }; diff --git a/src/desktop/reserved/ReservedArea.cpp b/src/desktop/reserved/ReservedArea.cpp index b67524ce..8b4956dd 100644 --- a/src/desktop/reserved/ReservedArea.cpp +++ b/src/desktop/reserved/ReservedArea.cpp @@ -6,16 +6,18 @@ using namespace Desktop; // fuck me. Writing this at 11pm, and I have an in-class test tomorrow. // I am failing that bitch -CReservedArea::CReservedArea(const Vector2D& tl, const Vector2D& br) : m_initialTopLeft(tl), m_initialBottomRight(br) { +CReservedArea::CReservedArea(const Vector2D& tl, const Vector2D& br) : m_initialTopLeft(tl.clamp({0, 0})), m_initialBottomRight(br.clamp({0, 0})) { calculate(); } -CReservedArea::CReservedArea(double top, double right, double bottom, double left) : m_initialTopLeft(left, top), m_initialBottomRight(right, bottom) { +CReservedArea::CReservedArea(double top, double right, double bottom, double left) : + m_initialTopLeft(std::max(left, 0.0), std::max(top, 0.0)), m_initialBottomRight(std::max(right, 0.0), std::max(bottom, 0.0)) { calculate(); } CReservedArea::CReservedArea(const CBox& parent, const CBox& child) { - ASSERT(!parent.empty() && !child.empty()); + if (parent.empty() || child.empty()) + return; // empty reserved area ASSERT(parent.containsPoint(child.pos() + Vector2D{0.0001, 0.0001})); ASSERT(parent.containsPoint(child.pos() + child.size() - Vector2D{0.0001, 0.0001})); @@ -81,6 +83,8 @@ void CReservedArea::addType(eReservedDynamicType t, const Vector2D& topLeft, con auto& ref = m_dynamicReserved[t]; ref.topLeft += topLeft; ref.bottomRight += bottomRight; + ref.topLeft = ref.topLeft.clamp({0, 0}); + ref.bottomRight = ref.bottomRight.clamp({0, 0}); calculate(); } diff --git a/src/desktop/rule/Rule.cpp b/src/desktop/rule/Rule.cpp index 3d981587..ab5525e8 100644 --- a/src/desktop/rule/Rule.cpp +++ b/src/desktop/rule/Rule.cpp @@ -52,6 +52,7 @@ static const std::unordered_map RULE_ENGINES = {RULE_PROP_XDG_TAG, RULE_MATCH_ENGINE_REGEX}, // {RULE_PROP_NAMESPACE, RULE_MATCH_ENGINE_REGEX}, // {RULE_PROP_EXEC_TOKEN, RULE_MATCH_ENGINE_REGEX}, // + {RULE_PROP_EXEC_PID, RULE_MATCH_ENGINE_INT}, // }; const std::vector& Rule::allMatchPropStrings() { @@ -125,10 +126,11 @@ const std::string& IRule::name() { return m_name; } -void IRule::markAsExecRule(const std::string& token, bool persistent) { +void IRule::markAsExecRule(const std::string& token, uint64_t pid, bool persistent) { m_execData.isExecRule = true; m_execData.isExecPersistent = persistent; m_execData.token = token; + m_execData.pid = pid; m_execData.expiresAt = Time::steadyNow() + std::chrono::minutes(1); } diff --git a/src/desktop/rule/Rule.hpp b/src/desktop/rule/Rule.hpp index 2b852b3a..efd3cb39 100644 --- a/src/desktop/rule/Rule.hpp +++ b/src/desktop/rule/Rule.hpp @@ -6,7 +6,6 @@ #include "../../helpers/time/Time.hpp" #include #include -#include #include namespace Desktop::Rule { @@ -31,6 +30,7 @@ namespace Desktop::Rule { RULE_PROP_XDG_TAG = (1 << 16), RULE_PROP_NAMESPACE = (1 << 17), RULE_PROP_EXEC_TOKEN = (1 << 18), + RULE_PROP_EXEC_PID = (1 << 19), RULE_PROP_ALL = std::numeric_limits>::max(), }; @@ -52,7 +52,7 @@ namespace Desktop::Rule { virtual std::underlying_type_t getPropertiesMask(); void registerMatch(eRuleProperty, const std::string&); - void markAsExecRule(const std::string& token, bool persistent = false); + void markAsExecRule(const std::string& token, uint64_t pid, bool persistent = false); bool isExecRule(); bool isExecPersistent(); bool execExpired(); @@ -78,7 +78,8 @@ namespace Desktop::Rule { bool isExecRule = false; bool isExecPersistent = false; std::string token; + uint64_t pid = 0; Time::steady_tp expiresAt; } m_execData; }; -} \ No newline at end of file +} diff --git a/src/desktop/rule/layerRule/LayerRuleApplicator.cpp b/src/desktop/rule/layerRule/LayerRuleApplicator.cpp index d80f839c..fec3a5b2 100644 --- a/src/desktop/rule/layerRule/LayerRuleApplicator.cpp +++ b/src/desktop/rule/layerRule/LayerRuleApplicator.cpp @@ -4,6 +4,7 @@ #include "../../view/LayerSurface.hpp" #include "../../types/OverridableVar.hpp" #include "../../../helpers/MiscFunctions.hpp" +#include "../../../event/EventBus.hpp" using namespace Desktop; using namespace Desktop::Rule; @@ -32,11 +33,38 @@ void CLayerRuleApplicator::resetProps(std::underlying_type_t prop UNSET(aboveLock) UNSET(ignoreAlpha) UNSET(animationStyle) + +#undef UNSET + + if (prio == Types::PRIORITY_WINDOW_RULE) + std::erase_if(m_otherProps.props, [props](const auto& el) { return !el.second || el.second->propMask & props; }); } void CLayerRuleApplicator::applyDynamicRule(const SP& rule) { for (const auto& [key, effect] : rule->effects()) { switch (key) { + default: { + if (key <= LAYER_RULE_EFFECT_LAST_STATIC) { + Log::logger->log(Log::TRACE, "CLayerRuleApplicator::applyDynamicRule: Skipping effect {}, not dynamic", sc>(key)); + break; + } + + // custom type, add to our vec + if (!m_otherProps.props.contains(key)) { + m_otherProps.props.emplace(key, + makeUnique(SCustomPropContainer{ + .idx = key, + .propMask = rule->getPropertiesMask(), + .effect = effect, + })); + } else { + auto& e = m_otherProps.props[key]; + e->propMask |= rule->getPropertiesMask(); + e->effect = effect; + } + + break; + } case LAYER_RULE_EFFECT_NONE: { Log::logger->log(Log::ERR, "CLayerRuleApplicator::applyDynamicRule: BUG THIS: LAYER_RULE_EFFECT_NONE??"); break; @@ -73,8 +101,8 @@ void CLayerRuleApplicator::applyDynamicRule(const SP& rule) { } case LAYER_RULE_EFFECT_ORDER: { try { - m_noScreenShare.first.set(std::stoi(effect), Types::PRIORITY_WINDOW_RULE); - m_noScreenShare.second |= rule->getPropertiesMask(); + m_order.first.set(std::stoi(effect), Types::PRIORITY_WINDOW_RULE); + m_order.second |= rule->getPropertiesMask(); } catch (...) { Log::logger->log(Log::ERR, "CLayerRuleApplicator::applyDynamicRule: invalid order {}", effect); } break; } @@ -125,4 +153,7 @@ void CLayerRuleApplicator::propertiesChanged(std::underlying_type_tm_events.layer.updateRules.emit(m_ls.lock()); } diff --git a/src/desktop/rule/layerRule/LayerRuleApplicator.hpp b/src/desktop/rule/layerRule/LayerRuleApplicator.hpp index 97f15b04..35aa18c5 100644 --- a/src/desktop/rule/layerRule/LayerRuleApplicator.hpp +++ b/src/desktop/rule/layerRule/LayerRuleApplicator.hpp @@ -1,5 +1,6 @@ #pragma once +#include "LayerRuleEffectContainer.hpp" #include "../../DesktopTypes.hpp" #include "../Rule.hpp" #include "../../types/OverridableVar.hpp" @@ -21,6 +22,17 @@ namespace Desktop::Rule { void propertiesChanged(std::underlying_type_t props); void resetProps(std::underlying_type_t props, Types::eOverridePriority prio = Types::PRIORITY_WINDOW_RULE); + struct SCustomPropContainer { + CLayerRuleEffectContainer::storageType idx = LAYER_RULE_EFFECT_NONE; + std::underlying_type_t propMask = RULE_PROP_NONE; + std::string effect; + }; + + // This struct holds props that were dynamically registered. Plugins may read this. + struct { + std::unordered_map> props; + } m_otherProps; + #define COMMA , #define DEFINE_PROP(type, name, def) \ private: \ diff --git a/src/desktop/rule/matchEngine/WorkspaceMatchEngine.cpp b/src/desktop/rule/matchEngine/WorkspaceMatchEngine.cpp index abaa1657..fea5c384 100644 --- a/src/desktop/rule/matchEngine/WorkspaceMatchEngine.cpp +++ b/src/desktop/rule/matchEngine/WorkspaceMatchEngine.cpp @@ -8,5 +8,5 @@ CWorkspaceMatchEngine::CWorkspaceMatchEngine(const std::string& s) : m_value(s) } bool CWorkspaceMatchEngine::match(PHLWORKSPACE ws) { - return ws->matchesStaticSelector(m_value); + return ws && ws->matchesStaticSelector(m_value); } diff --git a/src/desktop/rule/windowRule/WindowRule.cpp b/src/desktop/rule/windowRule/WindowRule.cpp index d1994d2e..8028e482 100644 --- a/src/desktop/rule/windowRule/WindowRule.cpp +++ b/src/desktop/rule/windowRule/WindowRule.cpp @@ -77,7 +77,7 @@ bool CWindowRule::matches(PHLWINDOW w, bool allowEnvLookup) { return false; break; case RULE_PROP_GROUP: - if (!engine->match(w->m_groupData.pNextWindow)) + if (!engine->match(!!w->m_group)) return false; break; case RULE_PROP_MODAL: @@ -97,27 +97,31 @@ bool CWindowRule::matches(PHLWINDOW w, bool allowEnvLookup) { return false; break; case RULE_PROP_CONTENT: - if (!engine->match(NContentType::toString(w->getContentType()))) + if (!engine->match(w->getContentType()) && !engine->match(NContentType::toString(w->getContentType()))) return false; break; case RULE_PROP_XDG_TAG: if (!w->xdgTag().has_value() || !engine->match(*w->xdgTag())) return false; break; + case RULE_PROP_EXEC_TOKEN: - // this is only allowed on static rules, we don't need it on dynamic plus it's expensive if (!allowEnvLookup) break; - const auto ENV = w->getEnv(); - if (ENV.contains(EXEC_RULE_ENV_NAME)) { - const auto TKN = ENV.at(EXEC_RULE_ENV_NAME); - if (!engine->match(TKN)) - return false; - break; - } + const auto ENV = w->getEnv(); + bool match = false; - return false; + if (ENV.contains(EXEC_RULE_ENV_NAME)) { + if (engine->match(ENV.at(EXEC_RULE_ENV_NAME))) + match = true; + } else if (m_matchEngines.contains(RULE_PROP_EXEC_PID)) { + if (m_matchEngines.at(RULE_PROP_EXEC_PID)->match(w->getPID())) + match = true; + } + if (!match) + return false; + break; } } @@ -153,11 +157,6 @@ SP CWindowRule::buildFromExecString(std::string&& s) { wr->addEffect(*EFFECT, std::string{"1"}); } - const auto TOKEN = g_pTokenManager->registerNewToken(nullptr, std::chrono::seconds(1)); - - wr->markAsExecRule(TOKEN, false /* TODO: could be nice. */); - wr->registerMatch(RULE_PROP_EXEC_TOKEN, TOKEN); - return wr; } diff --git a/src/desktop/rule/windowRule/WindowRuleApplicator.cpp b/src/desktop/rule/windowRule/WindowRuleApplicator.cpp index 0b6cba0f..07cb5f64 100644 --- a/src/desktop/rule/windowRule/WindowRuleApplicator.cpp +++ b/src/desktop/rule/windowRule/WindowRuleApplicator.cpp @@ -4,8 +4,7 @@ #include "../utils/SetUtils.hpp" #include "../../view/Window.hpp" #include "../../types/OverridableVar.hpp" -#include "../../../managers/LayoutManager.hpp" -#include "../../../managers/HookSystemManager.hpp" +#include "../../../event/EventBus.hpp" #include @@ -151,7 +150,7 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const CGradientValueData activeBorderGradient = {}; CGradientValueData inactiveBorderGradient = {}; bool active = true; - CVarList colorsAndAngles = CVarList(trim(effect.substr(effect.find_first_of(' ') + 1)), 0, 's', true); + CVarList colorsAndAngles = CVarList(trim(effect), 0, 's', true); // Basic form has only two colors, everything else can be parsed as a gradient if (colorsAndAngles.size() == 2 && !colorsAndAngles[1].contains("deg")) { @@ -159,6 +158,8 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const Types::COverridableVar(CGradientValueData(CHyprColor(configStringToInt(colorsAndAngles[0]).value_or(0))), Types::PRIORITY_WINDOW_RULE); m_inactiveBorderColor.first = Types::COverridableVar(CGradientValueData(CHyprColor(configStringToInt(colorsAndAngles[1]).value_or(0))), Types::PRIORITY_WINDOW_RULE); + m_activeBorderColor.second |= rule->getPropertiesMask(); + m_inactiveBorderColor.second |= rule->getPropertiesMask(); break; } @@ -265,13 +266,17 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const if (!m_window) break; - const auto VEC = configStringToVector2D(effect); - if (VEC.x < 1 || VEC.y < 1) { + const auto VEC = m_window->calculateExpression(effect); + if (!VEC) { + Log::logger->log(Log::ERR, "failed to parse {} as an expression", effect); + break; + } + if (VEC->x < 1 || VEC->y < 1) { Log::logger->log(Log::ERR, "Invalid size for maxsize"); break; } - m_maxSize.first = Types::COverridableVar(VEC, Types::PRIORITY_WINDOW_RULE); + m_maxSize.first = Types::COverridableVar(*VEC, Types::PRIORITY_WINDOW_RULE); if (*PCLAMP_TILED || m_window->m_isFloating) m_window->clampWindowSize(std::nullopt, m_maxSize.first.value()); @@ -286,13 +291,18 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const if (!m_window) break; - const auto VEC = configStringToVector2D(effect); - if (VEC.x < 1 || VEC.y < 1) { + const auto VEC = m_window->calculateExpression(effect); + if (!VEC) { + Log::logger->log(Log::ERR, "failed to parse {} as an expression", effect); + break; + } + + if (VEC->x < 1 || VEC->y < 1) { Log::logger->log(Log::ERR, "Invalid size for maxsize"); break; } - m_minSize.first = Types::COverridableVar(VEC, Types::PRIORITY_WINDOW_RULE); + m_minSize.first = Types::COverridableVar(*VEC, Types::PRIORITY_WINDOW_RULE); if (*PCLAMP_TILED || m_window->m_isFloating) m_window->clampWindowSize(m_minSize.first.value(), std::nullopt); } catch (std::exception& e) { Log::logger->log(Log::ERR, "minsize rule \"{}\" failed with: {}", effect, e.what()); } @@ -537,7 +547,7 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyStaticRule(const return SRuleResult{}; } -void CWindowRuleApplicator::readStaticRules() { +void CWindowRuleApplicator::readStaticRules(bool preRead) { if (!m_window) return; @@ -592,7 +602,8 @@ void CWindowRuleApplicator::readStaticRules() { for (const auto& wr : execRules) { applyStaticRule(wr); applyDynamicRule(wr); - ruleEngine()->unregisterRule(wr); + if (!preRead) + ruleEngine()->unregisterRule(wr); } } @@ -619,11 +630,13 @@ void CWindowRuleApplicator::propertiesChanged(std::underlying_type_tupdateWindowData(); + m_window->updateWindowDecos(); m_window->updateDecorationValues(); if (needsRelayout) g_pDecorationPositioner->forceRecalcFor(m_window.lock()); // for plugins - EMIT_HOOK_EVENT("windowUpdateRules", m_window.lock()); + Event::bus()->m_events.window.updateRules.emit(m_window.lock()); } diff --git a/src/desktop/rule/windowRule/WindowRuleApplicator.hpp b/src/desktop/rule/windowRule/WindowRuleApplicator.hpp index 121de727..5c1d4fd1 100644 --- a/src/desktop/rule/windowRule/WindowRuleApplicator.hpp +++ b/src/desktop/rule/windowRule/WindowRuleApplicator.hpp @@ -33,8 +33,7 @@ namespace Desktop::Rule { void propertiesChanged(std::underlying_type_t props); std::unordered_set resetProps(std::underlying_type_t props, Types::eOverridePriority prio = Types::PRIORITY_WINDOW_RULE); - void readStaticRules(); - void applyStaticRules(); + void readStaticRules(bool preRead = false); // static props struct { diff --git a/src/desktop/state/FocusState.cpp b/src/desktop/state/FocusState.cpp index 2c1158be..c1298766 100644 --- a/src/desktop/state/FocusState.cpp +++ b/src/desktop/state/FocusState.cpp @@ -3,14 +3,19 @@ #include "../../Compositor.hpp" #include "../../protocols/XDGShell.hpp" #include "../../render/Renderer.hpp" -#include "../../managers/LayoutManager.hpp" #include "../../managers/EventManager.hpp" -#include "../../managers/HookSystemManager.hpp" +#include "../../managers/input/InputManager.hpp" +#include "../../managers/SeatManager.hpp" #include "../../xwayland/XSurface.hpp" #include "../../protocols/PointerConstraints.hpp" +#include "managers/animation/DesktopAnimationManager.hpp" +#include "../../layout/LayoutManager.hpp" +#include "../../event/EventBus.hpp" using namespace Desktop; +#define COMMA , + SP Desktop::focusState() { static SP state = makeShared(); return state; @@ -32,6 +37,7 @@ static SFullscreenWorkspaceFocusResult onFullscreenWorkspaceFocusWindow(PHLWINDO if (pWindow->m_isFloating) { // if the window is floating, just bring it to the top pWindow->m_createdOverFullscreen = true; + g_pDesktopAnimationManager->setFullscreenFloatingFade(pWindow, 1.f); g_pHyprRenderer->damageWindow(pWindow); return {}; } @@ -61,7 +67,7 @@ static SFullscreenWorkspaceFocusResult onFullscreenWorkspaceFocusWindow(PHLWINDO return {}; } -void CFocusState::fullWindowFocus(PHLWINDOW pWindow, SP surface, bool forceFSCycle) { +void CFocusState::fullWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SP surface, bool forceFSCycle) { if (pWindow) { if (!pWindow->m_workspace) return; @@ -81,10 +87,10 @@ void CFocusState::fullWindowFocus(PHLWINDOW pWindow, SP surf return; } - rawWindowFocus(pWindow, surface); + rawWindowFocus(pWindow, reason, surface); } -void CFocusState::rawWindowFocus(PHLWINDOW pWindow, SP surface) { +void CFocusState::rawWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SP surface) { static auto PFOLLOWMOUSE = CConfigValue("input:follow_mouse"); static auto PSPECIALFALLTHROUGH = CConfigValue("input:special_fallthrough"); @@ -103,7 +109,9 @@ void CFocusState::rawWindowFocus(PHLWINDOW pWindow, SP surfa if (pWindow && pWindow->m_isX11 && pWindow->isX11OverrideRedirect() && !pWindow->m_xwaylandSurface->wantsFocus()) return; - g_pLayoutManager->getCurrentLayout()->bringWindowToTop(pWindow); + // m_target on purpose, this avoids the group + if (pWindow) + g_layoutManager->bringTargetToTop(pWindow->m_target); if (!pWindow || !validMapped(pWindow)) { @@ -125,9 +133,7 @@ void CFocusState::rawWindowFocus(PHLWINDOW pWindow, SP surfa g_pEventManager->postEvent(SHyprIPCEvent{"activewindow", ","}); g_pEventManager->postEvent(SHyprIPCEvent{"activewindowv2", ""}); - EMIT_HOOK_EVENT("activeWindow", PHLWINDOW{nullptr}); - - g_pLayoutManager->getCurrentLayout()->onWindowFocusChange(nullptr); + Event::bus()->m_events.window.active.emit(nullptr, reason); m_focusSurface.reset(); @@ -194,16 +200,14 @@ void CFocusState::rawWindowFocus(PHLWINDOW pWindow, SP surfa g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = pWindow->m_class + "," + pWindow->m_title}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = std::format("{:x}", rc(pWindow.get()))}); - EMIT_HOOK_EVENT("activeWindow", pWindow); - - g_pLayoutManager->getCurrentLayout()->onWindowFocusChange(pWindow); + Event::bus()->m_events.window.active.emit(pWindow, reason); g_pInputManager->recheckIdleInhibitorStatus(); if (*PFOLLOWMOUSE == 0) g_pInputManager->sendMotionEventsToFocused(); - if (pWindow->m_groupData.pNextWindow) + if (pWindow->m_group) pWindow->deactivateGroupMembers(); } @@ -229,7 +233,7 @@ void CFocusState::rawSurfaceFocus(SP pSurface, PHLWINDOW pWi g_pSeatManager->setKeyboardFocus(nullptr); g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = ","}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = ""}); - EMIT_HOOK_EVENT("keyboardFocus", SP{nullptr}); + Event::bus()->m_events.input.keyboard.focus.emit(nullptr); m_focusSurface.reset(); return; } @@ -245,7 +249,7 @@ void CFocusState::rawSurfaceFocus(SP pSurface, PHLWINDOW pWi g_pXWaylandManager->activateSurface(pSurface, true); m_focusSurface = pSurface; - EMIT_HOOK_EVENT("keyboardFocus", pSurface); + Event::bus()->m_events.input.keyboard.focus.emit(pSurface); const auto SURF = Desktop::View::CWLSurface::fromResource(pSurface); const auto OLDSURF = Desktop::View::CWLSurface::fromResource(PLASTSURF); @@ -274,7 +278,7 @@ void CFocusState::rawMonitorFocus(PHLMONITOR pMonitor) { g_pEventManager->postEvent(SHyprIPCEvent{.event = "focusedmon", .data = pMonitor->m_name + "," + WORKSPACE_NAME}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "focusedmonv2", .data = pMonitor->m_name + "," + WORKSPACE_ID}); - EMIT_HOOK_EVENT("focusedMon", pMonitor); + Event::bus()->m_events.monitor.focused.emit(pMonitor); m_focusMonitor = pMonitor; } @@ -294,3 +298,7 @@ void CFocusState::resetWindowFocus() { m_focusWindow.reset(); m_focusSurface.reset(); } + +bool Desktop::isHardInputFocusReason(eFocusReason r) { + return r == FOCUS_REASON_NEW_WINDOW || r == FOCUS_REASON_KEYBIND || r == FOCUS_REASON_GHOSTS || r == FOCUS_REASON_CLICK || r == FOCUS_REASON_DESKTOP_STATE_CHANGE; +} diff --git a/src/desktop/state/FocusState.hpp b/src/desktop/state/FocusState.hpp index 93ab2215..71330a3e 100644 --- a/src/desktop/state/FocusState.hpp +++ b/src/desktop/state/FocusState.hpp @@ -1,11 +1,24 @@ #pragma once #include "../DesktopTypes.hpp" -#include "../../SharedDefs.hpp" +#include "../../helpers/signal/Signal.hpp" class CWLSurfaceResource; namespace Desktop { + enum eFocusReason : uint8_t { + FOCUS_REASON_UNKNOWN = 0, + FOCUS_REASON_FFM, + FOCUS_REASON_KEYBIND, + FOCUS_REASON_CLICK, + FOCUS_REASON_OTHER, + FOCUS_REASON_DESKTOP_STATE_CHANGE, + FOCUS_REASON_NEW_WINDOW, + FOCUS_REASON_GHOSTS, + }; + + bool isHardInputFocusReason(eFocusReason r); + class CFocusState { public: CFocusState(); @@ -15,8 +28,8 @@ namespace Desktop { CFocusState(CFocusState&) = delete; CFocusState(const CFocusState&) = delete; - void fullWindowFocus(PHLWINDOW w, SP surface = nullptr, bool forceFSCycle = false); - void rawWindowFocus(PHLWINDOW w, SP surface = nullptr); + void fullWindowFocus(PHLWINDOW w, eFocusReason reason, SP surface = nullptr, bool forceFSCycle = false); + void rawWindowFocus(PHLWINDOW w, eFocusReason reason, SP surface = nullptr); void rawSurfaceFocus(SP s, PHLWINDOW pWindowOwner = nullptr); void rawMonitorFocus(PHLMONITOR m); @@ -31,7 +44,7 @@ namespace Desktop { PHLWINDOWREF m_focusWindow; PHLMONITORREF m_focusMonitor; - SP m_windowOpen, m_windowClose; + CHyprSignalListener m_windowOpen, m_windowClose; }; SP focusState(); diff --git a/src/desktop/view/Group.cpp b/src/desktop/view/Group.cpp new file mode 100644 index 00000000..06884cff --- /dev/null +++ b/src/desktop/view/Group.cpp @@ -0,0 +1,351 @@ +#include "Group.hpp" +#include "Window.hpp" + +#include "../../render/decorations/CHyprGroupBarDecoration.hpp" +#include "../../layout/target/WindowGroupTarget.hpp" +#include "../../layout/target/WindowTarget.hpp" +#include "../../layout/target/Target.hpp" +#include "../../layout/space/Space.hpp" +#include "../../layout/LayoutManager.hpp" +#include "../../desktop/state/FocusState.hpp" +#include "../../Compositor.hpp" + +#include + +using namespace Desktop; +using namespace Desktop::View; + +std::vector>& View::groups() { + static std::vector> g; + return g; +} + +SP CGroup::create(std::vector&& windows) { + auto x = SP(new CGroup(std::move(windows))); + x->m_self = x; + x->m_target = Layout::CWindowGroupTarget::create(x); + groups().emplace_back(x); + + x->init(); + + return x; +} + +CGroup::CGroup(std::vector&& windows) : m_windows(std::move(windows)) { + ; +} + +void CGroup::init() { + // for proper group logic: + // - add all windows to us + // - replace the first window with our target + // - remove all window targets from layout + // - apply updates + + // FIXME: what if some windows are grouped? For now we only do 1-window but YNK + for (const auto& w : m_windows) { + RASSERT(!w->m_group, "CGroup: windows cannot contain grouped in init, this will explode"); + w->m_group = m_self.lock(); + } + + g_layoutManager->switchTargets(m_windows.at(0)->m_target, m_target); + + for (const auto& w : m_windows) { + w->m_target->setSpaceGhost(m_target->space()); + } + + for (const auto& w : m_windows) { + applyWindowDecosAndUpdates(w.lock()); + } + + updateWindowVisibility(); +} + +void CGroup::destroy() { + while (true) { + if (m_windows.size() == 1) { + remove(m_windows.at(0).lock()); + break; + } + + remove(m_windows.at(0).lock()); + } +} + +CGroup::~CGroup() { + if (m_target->space()) + m_target->assignToSpace(nullptr); + std::erase_if(groups(), [this](const auto& e) { return !e || e == m_self; }); +} + +bool CGroup::has(PHLWINDOW w) const { + return std::ranges::contains(m_windows, w); +} + +void CGroup::add(PHLWINDOW w) { + static auto INSERT_AFTER_CURRENT = CConfigValue("group:insert_after_current"); + + if (w->m_group) { + if (w->m_group == m_self) + return; + + const auto WINDOWS = w->m_group->windows(); + for (const auto& w : WINDOWS) { + w->m_group->remove(w.lock()); + add(w.lock()); + } + + return; + } + + if (w->layoutTarget()->space()) { + // remove the target from a space if it is in one + g_layoutManager->removeTarget(w->layoutTarget()); + } + + w->m_group = m_self.lock(); + w->m_target->setSpaceGhost(m_target->space()); + w->m_target->setFloating(m_target->floating()); + + if (*INSERT_AFTER_CURRENT) { + m_windows.insert(m_windows.begin() + m_current + 1, w); + m_current++; + } else { + m_windows.emplace_back(w); + m_current = m_windows.size() - 1; + } + + applyWindowDecosAndUpdates(w); + updateWindowVisibility(); + m_target->recalc(); +} + +void CGroup::remove(PHLWINDOW w, Math::eDirection dir) { + std::optional idx; + for (size_t i = 0; i < m_windows.size(); ++i) { + if (m_windows.at(i) == w) { + idx = i; + break; + } + } + + if (!idx) + return; + + if ((m_current >= *idx && idx != 0) || (m_current >= m_windows.size() - 1 && m_current > 0)) + m_current--; + + auto g = m_self.lock(); // keep ref to avoid uaf after w->m_group.reset() + + w->m_group.reset(); + removeWindowDecos(w); + + w->setHidden(false); + + const bool REMOVING_GROUP = m_windows.size() <= 1; + + if (REMOVING_GROUP) { + w->m_target->assignToSpace(nullptr); + g_layoutManager->switchTargets(m_target, w->m_target); + } + + // we do it after the above because switchTargets expects this to be a valid group + m_windows.erase(m_windows.begin() + *idx); + + if (!m_windows.empty()) + updateWindowVisibility(); + + // do this here: otherwise the new current is hidden and workspace rules get wrong data + if (!REMOVING_GROUP) { + std::optional focalPoint; + if (dir != Math::DIRECTION_DEFAULT) { + const auto box = m_target->position(); + switch (dir) { + case Math::DIRECTION_RIGHT: focalPoint = Vector2D(box.x + box.w, box.y + box.h / 2.0); break; + case Math::DIRECTION_LEFT: focalPoint = Vector2D(box.x, box.y + box.h / 2.0); break; + case Math::DIRECTION_DOWN: focalPoint = Vector2D(box.x + box.w / 2.0, box.y + box.h); break; + case Math::DIRECTION_UP: focalPoint = Vector2D(box.x + box.w / 2.0, box.y); break; + default: break; + } + } + w->m_target->assignToSpace(m_target->space(), focalPoint); + } +} + +void CGroup::moveCurrent(bool next) { + size_t idx = m_current; + + if (next) { + idx++; + if (idx >= m_windows.size()) + idx = 0; + } else { + if (idx == 0) + idx = m_windows.size() - 1; + else + idx--; + } + + setCurrent(idx); +} + +void CGroup::setCurrent(size_t idx) { + if (idx == m_current) + return; + + const auto FS_STATE = m_target->fullscreenMode(); + const auto WASFOCUS = Desktop::focusState()->window() == current(); + auto oldWindow = m_windows.at(m_current).lock(); + + if (FS_STATE != FSMODE_NONE) + g_pCompositor->setWindowFullscreenInternal(oldWindow, FSMODE_NONE); + + m_current = std::clamp(idx, sc(0), m_windows.size() - 1); + updateWindowVisibility(); + + auto newWindow = m_windows.at(m_current).lock(); + + if (FS_STATE != FSMODE_NONE) { + g_pCompositor->setWindowFullscreenInternal(newWindow, FS_STATE); + newWindow->m_target->warpPositionSize(); + oldWindow->m_target->setPositionGlobal(newWindow->m_target->position()); // TODO: this is a hack and sucks + } + + if (WASFOCUS) + Desktop::focusState()->rawWindowFocus(current(), FOCUS_REASON_DESKTOP_STATE_CHANGE); +} + +void CGroup::setCurrent(PHLWINDOW w) { + if (w == current()) + return; + + for (size_t i = 0; i < m_windows.size(); ++i) { + if (m_windows.at(i) == w) { + setCurrent(i); + return; + } + } +} + +size_t CGroup::getCurrentIdx() const { + return m_current; +} + +PHLWINDOW CGroup::head() const { + return m_windows.front().lock(); +} + +PHLWINDOW CGroup::tail() const { + return m_windows.back().lock(); +} + +PHLWINDOW CGroup::current() const { + return m_windows.at(m_current).lock(); +} + +PHLWINDOW CGroup::next() const { + return (m_current >= m_windows.size() - 1 ? m_windows.front() : m_windows.at(m_current + 1)).lock(); +} + +PHLWINDOW CGroup::fromIndex(size_t idx) const { + if (idx >= m_windows.size()) + return nullptr; + + return m_windows.at(idx).lock(); +} + +const std::vector& CGroup::windows() const { + return m_windows; +} + +void CGroup::applyWindowDecosAndUpdates(PHLWINDOW x) { + x->addWindowDeco(makeUnique(x)); + + x->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE); + x->updateWindowDecos(); + x->updateDecorationValues(); +} + +void CGroup::removeWindowDecos(PHLWINDOW x) { + x->removeWindowDeco(x->getDecorationByType(DECORATION_GROUPBAR)); + + x->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE); + x->updateWindowDecos(); + x->updateDecorationValues(); +} + +void CGroup::updateWindowVisibility() { + for (size_t i = 0; i < m_windows.size(); ++i) { + if (i == m_current) { + auto& x = m_windows.at(i); + x->setHidden(false); + x->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE); + x->updateWindowDecos(); + x->updateDecorationValues(); + } else + m_windows.at(i)->setHidden(true); + } + + m_target->recalc(); + + m_target->damageEntire(); +} + +size_t CGroup::size() const { + return m_windows.size(); +} + +bool CGroup::locked() const { + return m_locked; +} + +void CGroup::setLocked(bool x) { + m_locked = x; +} + +bool CGroup::denied() const { + return m_deny; +} + +void CGroup::setDenied(bool x) { + m_deny = x; +} + +void CGroup::updateWorkspace(PHLWORKSPACE ws) { + if (!ws) + return; + + for (const auto& w : windows()) { + w->m_monitor = ws->m_monitor; + w->moveToWorkspace(ws); + w->updateToplevel(); + w->updateWindowDecos(); + w->m_target->setSpaceGhost(ws->m_space); + } +} + +void CGroup::swapWithNext() { + const bool HAD_FOCUS = Desktop::focusState()->window() == m_windows.at(m_current); + + size_t idx = m_current + 1 >= m_windows.size() ? 0 : m_current + 1; + std::iter_swap(m_windows.begin() + m_current, m_windows.begin() + idx); + m_current = idx; + + updateWindowVisibility(); + + if (HAD_FOCUS) + Desktop::focusState()->fullWindowFocus(m_windows.at(m_current).lock(), FOCUS_REASON_DESKTOP_STATE_CHANGE); +} + +void CGroup::swapWithLast() { + const bool HAD_FOCUS = Desktop::focusState()->window() == m_windows.at(m_current); + + size_t idx = m_current == 0 ? m_windows.size() - 1 : m_current - 1; + std::iter_swap(m_windows.begin() + m_current, m_windows.begin() + idx); + m_current = idx; + + updateWindowVisibility(); + + if (HAD_FOCUS) + Desktop::focusState()->fullWindowFocus(m_windows.at(m_current).lock(), FOCUS_REASON_DESKTOP_STATE_CHANGE); +} diff --git a/src/desktop/view/Group.hpp b/src/desktop/view/Group.hpp new file mode 100644 index 00000000..36c4baae --- /dev/null +++ b/src/desktop/view/Group.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include "../DesktopTypes.hpp" +#include "../../helpers/math/Direction.hpp" + +#include + +namespace Layout { + class CWindowGroupTarget; +}; + +namespace Desktop::View { + class CGroup { + public: + static SP create(std::vector&& windows); + ~CGroup(); + + bool has(PHLWINDOW w) const; + + void add(PHLWINDOW w); + void remove(PHLWINDOW w, Math::eDirection dir = Math::DIRECTION_DEFAULT); + void moveCurrent(bool next); + void setCurrent(size_t idx); + void setCurrent(PHLWINDOW w); + size_t getCurrentIdx() const; + size_t size() const; + void destroy(); + void updateWorkspace(PHLWORKSPACE); + + void swapWithNext(); + void swapWithLast(); + + PHLWINDOW head() const; + PHLWINDOW tail() const; + PHLWINDOW current() const; + PHLWINDOW next() const; + + PHLWINDOW fromIndex(size_t idx) const; + + bool locked() const; + void setLocked(bool x); + + bool denied() const; + void setDenied(bool x); + + const std::vector& windows() const; + + SP m_target; + + private: + CGroup(std::vector&& windows); + + void applyWindowDecosAndUpdates(PHLWINDOW x); + void removeWindowDecos(PHLWINDOW x); + void init(); + void updateWindowVisibility(); + + WP m_self; + + std::vector m_windows; + + bool m_locked = false; + bool m_deny = false; + + size_t m_current = 0; + }; + + std::vector>& groups(); +}; \ No newline at end of file diff --git a/src/desktop/view/LayerSurface.cpp b/src/desktop/view/LayerSurface.cpp index f61d9554..a10c9d4d 100644 --- a/src/desktop/view/LayerSurface.cpp +++ b/src/desktop/view/LayerSurface.cpp @@ -10,8 +10,8 @@ #include "../../config/ConfigManager.hpp" #include "../../helpers/Monitor.hpp" #include "../../managers/input/InputManager.hpp" -#include "../../managers/HookSystemManager.hpp" #include "../../managers/EventManager.hpp" +#include "../../event/EventBus.hpp" using namespace Desktop; using namespace Desktop::View; @@ -23,24 +23,11 @@ PHLLS CLayerSurface::create(SP resource) { pLS->m_wlSurface->assign(resource->m_surface.lock(), pLS); - if (!pMonitor) { - Log::logger->log(Log::ERR, "New LS has no monitor??"); - return pLS; - } - - if (pMonitor->m_mirrorOf) - pMonitor = g_pCompositor->m_monitors.front(); - - pLS->m_self = pLS; - - pLS->m_namespace = resource->m_layerNamespace; - - pLS->m_layer = resource->m_current.layer; - pLS->m_popupHead = CPopup::create(pLS); - pLS->m_monitor = pMonitor; - pMonitor->m_layerSurfaceLayers[resource->m_current.layer].emplace_back(pLS); - pLS->m_ruleApplicator = makeUnique(pLS); + pLS->m_self = pLS; + pLS->m_namespace = resource->m_layerNamespace; + pLS->m_layer = std::clamp(resource->m_current.layer, ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY); + pLS->m_popupHead = CPopup::create(pLS); g_pAnimationManager->createAnimation(0.f, pLS->m_alpha, g_pConfigManager->getAnimationPropertyConfig("fadeLayersIn"), pLS, AVARDAMAGE_ENTIRE); g_pAnimationManager->createAnimation(Vector2D(0, 0), pLS->m_realPosition, g_pConfigManager->getAnimationPropertyConfig("layersIn"), pLS, AVARDAMAGE_ENTIRE); @@ -50,6 +37,19 @@ PHLLS CLayerSurface::create(SP resource) { pLS->m_alpha->setValueAndWarp(0.f); + if (!pMonitor) { + Log::logger->log(Log::DEBUG, "LayerSurface {:x} (namespace {} layer {}) created on NO MONITOR ?!", rc(resource.get()), resource->m_layerNamespace, + sc(pLS->m_layer)); + + return pLS; + } + + if (pMonitor->m_mirrorOf) + pMonitor = g_pCompositor->m_monitors.front(); + + pLS->m_monitor = pMonitor; + pMonitor->m_layerSurfaceLayers[resource->m_current.layer].emplace_back(pLS); + Log::logger->log(Log::DEBUG, "LayerSurface {:x} (namespace {} layer {}) created on monitor {}", rc(resource.get()), resource->m_layerNamespace, sc(pLS->m_layer), pMonitor->m_name); @@ -222,7 +222,7 @@ void CLayerSurface::onMap() { m_fadingOut = false; g_pEventManager->postEvent(SHyprIPCEvent{.event = "openlayer", .data = m_namespace}); - EMIT_HOOK_EVENT("openLayer", m_self.lock()); + Event::bus()->m_events.layer.opened.emit(m_self.lock()); g_pCompositor->setPreferredScaleForSurface(m_wlSurface->resource(), PMONITOR->m_scale); g_pCompositor->setPreferredTransformForSurface(m_wlSurface->resource(), PMONITOR->m_transform); @@ -232,7 +232,7 @@ void CLayerSurface::onUnmap() { Log::logger->log(Log::DEBUG, "LayerSurface {:x} unmapped", rc(m_layerSurface.get())); g_pEventManager->postEvent(SHyprIPCEvent{.event = "closelayer", .data = m_layerSurface->m_layerNamespace}); - EMIT_HOOK_EVENT("closeLayer", m_self.lock()); + Event::bus()->m_events.layer.closed.emit(m_self.lock()); std::erase_if(g_pInputManager->m_exclusiveLSes, [this](const auto& other) { return !other || other == m_self; }); @@ -323,16 +323,18 @@ void CLayerSurface::onCommit() { if (m_layerSurface->m_current.committed != 0) { if (m_layerSurface->m_current.committed & CLayerShellResource::eCommittedState::STATE_LAYER && m_layerSurface->m_current.layer != m_layer) { + const auto NEW_LAYER = std::clamp(m_layerSurface->m_current.layer, ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY); + for (auto it = PMONITOR->m_layerSurfaceLayers[m_layer].begin(); it != PMONITOR->m_layerSurfaceLayers[m_layer].end(); it++) { if (*it == m_self) { - PMONITOR->m_layerSurfaceLayers[m_layerSurface->m_current.layer].emplace_back(*it); + PMONITOR->m_layerSurfaceLayers[NEW_LAYER].emplace_back(*it); PMONITOR->m_layerSurfaceLayers[m_layer].erase(it); break; } } - m_layer = m_layerSurface->m_current.layer; - m_aboveFullscreen = m_layerSurface->m_current.layer >= ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; + m_layer = NEW_LAYER; + m_aboveFullscreen = NEW_LAYER >= ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; // if in fullscreen, only overlay can be above. *m_alpha = PMONITOR->inFullscreenMode() ? (m_layer >= ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY ? 1.F : 0.F) : 1.F; diff --git a/src/desktop/view/Popup.cpp b/src/desktop/view/Popup.cpp index 841674e7..f6f681d5 100644 --- a/src/desktop/view/Popup.cpp +++ b/src/desktop/view/Popup.cpp @@ -9,6 +9,7 @@ #include "../../managers/animation/AnimationManager.hpp" #include "LayerSurface.hpp" #include "../../managers/input/InputManager.hpp" +#include "../../managers/eventLoop/EventLoopManager.hpp" #include "../../render/Renderer.hpp" #include "../../render/OpenGL.hpp" #include @@ -108,8 +109,12 @@ void CPopup::initAllSignals() { m_alpha->setCallbackOnEnd( [this](auto) { if (inert()) { - g_pHyprRenderer->damageBox(CBox{coordsGlobal(), size()}); - fullyDestroy(); + g_pEventLoopManager->doLater([p = m_self] { + if (!p) + return; + g_pHyprRenderer->damageBox(CBox{p->coordsGlobal(), p->size()}); + p->fullyDestroy(); + }); } }, false); @@ -194,7 +199,7 @@ void CPopup::onMap() { //unconstrain(); sendScale(); - m_resource->m_surface->m_surface->enter(PMONITOR->m_self.lock()); + m_wlSurface->resource()->breadthfirst([PMONITOR](SP s, const Vector2D& offset, void* d) { s->enter(PMONITOR->m_self.lock()); }, nullptr); if (!m_layerOwner.expired() && m_layerOwner->m_layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP) g_pHyprOpenGL->markBlurDirtyForMonitor(g_pCompositor->getMonitorFromID(m_layerOwner->m_layer)); @@ -334,8 +339,7 @@ void CPopup::reposition() { if (!PMONITOR) return; - CBox box = {PMONITOR->m_position.x, PMONITOR->m_position.y, PMONITOR->m_size.x, PMONITOR->m_size.y}; - m_resource->applyPositioning(box, COORDS); + m_resource->applyPositioning(m_windowOwner ? PMONITOR->logicalBoxMinusReserved() : PMONITOR->logicalBox(), COORDS); } SP CPopup::getT1Owner() const { @@ -399,10 +403,14 @@ void CPopup::recheckChildrenRecursive() { std::vector> cpy; std::ranges::for_each(m_children, [&cpy](const auto& el) { cpy.emplace_back(el); }); for (auto const& c : cpy) { - if (!c->visible()) + if (!c || !c->visible()) continue; - c->onCommit(true); - c->recheckChildrenRecursive(); + + // keep ref, onCommit can call onDestroy + auto x = c.lock(); + + x->onCommit(true); + x->recheckChildrenRecursive(); } } diff --git a/src/desktop/view/Window.cpp b/src/desktop/view/Window.cpp index 695ba81f..abf4da95 100644 --- a/src/desktop/view/Window.cpp +++ b/src/desktop/view/Window.cpp @@ -3,6 +3,8 @@ #include #include +#include "Group.hpp" + #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) #include #include @@ -37,12 +39,15 @@ #include "../../helpers/math/Expression.hpp" #include "../../managers/XWaylandManager.hpp" #include "../../render/Renderer.hpp" -#include "../../managers/LayoutManager.hpp" -#include "../../managers/HookSystemManager.hpp" #include "../../managers/EventManager.hpp" #include "../../managers/input/InputManager.hpp" #include "../../managers/PointerManager.hpp" #include "../../managers/animation/DesktopAnimationManager.hpp" +#include "../../layout/space/Space.hpp" +#include "../../layout/LayoutManager.hpp" +#include "../../layout/target/WindowTarget.hpp" +#include "../../layout/target/WindowGroupTarget.hpp" +#include "../../event/EventBus.hpp" #include @@ -53,6 +58,13 @@ using enum NContentType::eContentType; using namespace Desktop; using namespace Desktop::View; +// I wish I had an elven wife instead of a windowIDCounter +static uint64_t windowIDCounter = 0x18000000; + +// +#define COMMA , +// + PHLWINDOW CWindow::create(SP surface) { PHLWINDOW pWindow = SP(new CWindow(surface)); @@ -75,6 +87,8 @@ PHLWINDOW CWindow::create(SP surface) { pWindow->addWindowDeco(makeUnique(pWindow)); pWindow->addWindowDeco(makeUnique(pWindow)); + pWindow->m_target = Layout::CWindowTarget::create(pWindow); + return pWindow; } @@ -100,12 +114,14 @@ PHLWINDOW CWindow::create(SP resource) { pWindow->addWindowDeco(makeUnique(pWindow)); pWindow->addWindowDeco(makeUnique(pWindow)); + pWindow->m_target = Layout::CWindowTarget::create(pWindow); + pWindow->wlSurface()->assign(pWindow->m_xdgSurface->m_surface.lock(), pWindow); return pWindow; } -CWindow::CWindow(SP resource) : IView(CWLSurface::create()), m_xdgSurface(resource) { +CWindow::CWindow(SP resource) : IView(CWLSurface::create()), m_xdgSurface(resource), m_stableID(windowIDCounter++) { m_listeners.map = m_xdgSurface->m_events.map.listen([this] { mapWindow(); }); m_listeners.ack = m_xdgSurface->m_events.ack.listen([this](uint32_t d) { onAck(d); }); m_listeners.unmap = m_xdgSurface->m_events.unmap.listen([this] { unmapWindow(); }); @@ -115,7 +131,7 @@ CWindow::CWindow(SP resource) : IView(CWLSurface::create()) m_listeners.updateMetadata = m_xdgSurface->m_toplevel->m_events.metadataChanged.listen([this] { onUpdateMeta(); }); } -CWindow::CWindow(SP surface) : IView(CWLSurface::create()), m_xwaylandSurface(surface) { +CWindow::CWindow(SP surface) : IView(CWLSurface::create()), m_xwaylandSurface(surface), m_stableID(windowIDCounter++) { m_listeners.map = m_xwaylandSurface->m_events.map.listen([this] { mapWindow(); }); m_listeners.unmap = m_xwaylandSurface->m_events.unmap.listen([this] { unmapWindow(); }); m_listeners.destroy = m_xwaylandSurface->m_events.destroy.listen([this] { destroyWindow(); }); @@ -246,7 +262,7 @@ CBox CWindow::getFullWindowBoundingBox() const { CBox CWindow::getWindowIdealBoundingBoxIgnoreReserved() { const auto PMONITOR = m_monitor.lock(); - if (!PMONITOR) + if (!PMONITOR || !m_workspace) return {m_position, m_size}; auto POS = m_position; @@ -259,21 +275,26 @@ CBox CWindow::getWindowIdealBoundingBoxIgnoreReserved() { return CBox{sc(POS.x), sc(POS.y), sc(SIZE.x), sc(SIZE.y)}; } - if (DELTALESSTHAN(POS.y - PMONITOR->m_position.y, PMONITOR->m_reservedArea.top(), 1)) { - POS.y = PMONITOR->m_position.y; - SIZE.y += PMONITOR->m_reservedArea.top(); + // fucker fucking fuck + const auto WORKAREA = m_workspace->m_space->workArea(); + const auto& RESERVED = CReservedArea(PMONITOR->logicalBox(), WORKAREA); + + if (DELTALESSTHAN(POS.x, WORKAREA.x, 1)) { + POS.x -= RESERVED.left(); + SIZE.x += RESERVED.left(); } - if (DELTALESSTHAN(POS.x - PMONITOR->m_position.x, PMONITOR->m_reservedArea.left(), 1)) { - POS.x = PMONITOR->m_position.x; - SIZE.x += PMONITOR->m_reservedArea.left(); - } - if (DELTALESSTHAN(POS.x + SIZE.x - PMONITOR->m_position.x, PMONITOR->m_size.x - PMONITOR->m_reservedArea.right(), 1)) { - SIZE.x += PMONITOR->m_reservedArea.right(); - } - if (DELTALESSTHAN(POS.y + SIZE.y - PMONITOR->m_position.y, PMONITOR->m_size.y - PMONITOR->m_reservedArea.bottom(), 1)) { - SIZE.y += PMONITOR->m_reservedArea.bottom(); + + if (DELTALESSTHAN(POS.y, WORKAREA.y, 1)) { + POS.y -= RESERVED.top(); + SIZE.y += RESERVED.top(); } + if (DELTALESSTHAN(POS.x + SIZE.x, WORKAREA.x + WORKAREA.width, 1)) + SIZE.x += RESERVED.right(); + + if (DELTALESSTHAN(POS.y + SIZE.y, WORKAREA.y + WORKAREA.height, 1)) + SIZE.y += RESERVED.bottom(); + return CBox{sc(POS.x), sc(POS.y), sc(SIZE.x), sc(SIZE.y)}; } @@ -349,14 +370,18 @@ void CWindow::addWindowDeco(UP deco) { m_windowDecorations.emplace_back(std::move(deco)); g_pDecorationPositioner->forceRecalcFor(m_self.lock()); updateWindowDecos(); - g_pLayoutManager->getCurrentLayout()->recalculateWindow(m_self.lock()); + + if (layoutTarget()) + layoutTarget()->recalc(); } void CWindow::removeWindowDeco(IHyprWindowDecoration* deco) { m_decosToRemove.push_back(deco); g_pDecorationPositioner->forceRecalcFor(m_self.lock()); updateWindowDecos(); - g_pLayoutManager->getCurrentLayout()->recalculateWindow(m_self.lock()); + + if (layoutTarget()) + layoutTarget()->recalc(); } void CWindow::uncacheWindowDecos() { @@ -459,11 +484,13 @@ void CWindow::moveToWorkspace(PHLWORKSPACE pWorkspace) { if (TOKEN) { if (*PINITIALWSTRACKING == 2) { // persistent - SInitialWorkspaceToken token = std::any_cast(TOKEN->m_data); - if (token.primaryOwner == m_self) { - token.workspace = pWorkspace->getConfigName(); - TOKEN->m_data = token; - } + try { + SInitialWorkspaceToken token = std::any_cast(TOKEN->m_data); + if (token.primaryOwner == m_self) { + token.workspace = pWorkspace->getConfigName(); + TOKEN->m_data = token; + } + } catch (const std::bad_any_cast& e) { ; } } } } @@ -483,20 +510,12 @@ void CWindow::moveToWorkspace(PHLWORKSPACE pWorkspace) { setAnimationsToMove(); - OLDWORKSPACE->updateWindows(); - OLDWORKSPACE->updateWindowData(); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(OLDWORKSPACE->monitorID()); - - pWorkspace->updateWindows(); - pWorkspace->updateWindowData(); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitorID()); - g_pCompositor->updateAllWindowsAnimatedDecorationValues(); if (valid(pWorkspace)) { g_pEventManager->postEvent(SHyprIPCEvent{.event = "movewindow", .data = std::format("{:x},{}", rc(this), pWorkspace->m_name)}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "movewindowv2", .data = std::format("{:x},{},{}", rc(this), pWorkspace->m_id, pWorkspace->m_name)}); - EMIT_HOOK_EVENT("moveWindow", (std::vector{m_self.lock(), pWorkspace})); + Event::bus()->m_events.window.moveToWorkspace.emit(m_self.lock(), pWorkspace); } if (const auto SWALLOWED = m_swallowed.lock()) { @@ -549,9 +568,11 @@ void CWindow::onUnmap() { if (TOKEN) { if (*PINITIALWSTRACKING == 2) { // persistent token, but the first window got removed so the token is gone - SInitialWorkspaceToken token = std::any_cast(TOKEN->m_data); - if (token.primaryOwner == m_self) - g_pTokenManager->removeToken(TOKEN); + try { + SInitialWorkspaceToken token = std::any_cast(TOKEN->m_data); + if (token.primaryOwner == m_self) + g_pTokenManager->removeToken(TOKEN); + } catch (const std::bad_any_cast& e) { g_pTokenManager->removeToken(TOKEN); } } } } @@ -579,7 +600,7 @@ void CWindow::onUnmap() { m_workspace->updateWindows(); m_workspace->updateWindowData(); } - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitorID()); + g_pCompositor->updateAllWindowsAnimatedDecorationValues(); m_workspace.reset(); @@ -626,6 +647,18 @@ void CWindow::onMap() { }, false); + m_realSize->setUpdateCallback([this](auto) { + if (m_isMapped) + m_events.resize.emit(); + }); + + m_realPosition->setUpdateCallback([this](auto) { + if (m_isMapped && m_monitor != m_prevMonitor) { + m_prevMonitor = m_monitor; + m_events.monitorChanged.emit(); + } + }); + m_movingFromWorkspaceAlpha->setValueAndWarp(1.F); m_reportedSize = m_pendingReportedSize; @@ -660,6 +693,9 @@ void CWindow::onBorderAngleAnimEnd(WP pav) { void CWindow::setHidden(bool hidden) { m_hidden = hidden; + if (hidden) + m_events.hide.emit(); + if (hidden && Desktop::focusState()->window() == m_self) Desktop::focusState()->window().reset(); @@ -711,311 +747,6 @@ bool CWindow::hasPopupAt(const Vector2D& pos) { return popup && popup->wlSurface()->resource(); } -void CWindow::applyGroupRules() { - if ((m_groupRules & GROUP_SET && m_firstMap) || m_groupRules & GROUP_SET_ALWAYS) - createGroup(); - - if (m_groupData.pNextWindow.lock() && ((m_groupRules & GROUP_LOCK && m_firstMap) || m_groupRules & GROUP_LOCK_ALWAYS)) - getGroupHead()->m_groupData.locked = true; -} - -void CWindow::createGroup() { - if (m_groupData.deny) { - Log::logger->log(Log::DEBUG, "createGroup: window:{:x},title:{} is denied as a group, ignored", rc(this), this->m_title); - return; - } - - if (m_groupData.pNextWindow.expired()) { - m_groupData.pNextWindow = m_self; - m_groupData.head = true; - m_groupData.locked = false; - m_groupData.deny = false; - - addWindowDeco(makeUnique(m_self.lock())); - - if (m_workspace) { - m_workspace->updateWindows(); - m_workspace->updateWindowData(); - } - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitorID()); - g_pCompositor->updateAllWindowsAnimatedDecorationValues(); - - g_pEventManager->postEvent(SHyprIPCEvent{.event = "togglegroup", .data = std::format("1,{:x}", rc(this))}); - } - - m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE); -} - -void CWindow::destroyGroup() { - if (m_groupData.pNextWindow == m_self) { - if (m_groupRules & GROUP_SET_ALWAYS) { - Log::logger->log(Log::DEBUG, "destoryGroup: window:{:x},title:{} has rule [group set always], ignored", rc(this), this->m_title); - return; - } - m_groupData.pNextWindow.reset(); - m_groupData.head = false; - updateWindowDecos(); - if (m_workspace) { - m_workspace->updateWindows(); - m_workspace->updateWindowData(); - } - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitorID()); - g_pCompositor->updateAllWindowsAnimatedDecorationValues(); - - g_pEventManager->postEvent(SHyprIPCEvent{.event = "togglegroup", .data = std::format("0,{:x}", rc(this))}); - m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE); - return; - } - - std::string addresses; - PHLWINDOW curr = m_self.lock(); - std::vector members; - do { - const auto PLASTWIN = curr; - curr = curr->m_groupData.pNextWindow.lock(); - PLASTWIN->m_groupData.pNextWindow.reset(); - curr->setHidden(false); - members.push_back(curr); - - addresses += std::format("{:x},", rc(curr.get())); - } while (curr.get() != this); - - for (auto const& w : members) { - if (w->m_groupData.head) - g_pLayoutManager->getCurrentLayout()->onWindowRemoved(curr); - w->m_groupData.head = false; - } - - const bool GROUPSLOCKEDPREV = g_pKeybindManager->m_groupsLocked; - g_pKeybindManager->m_groupsLocked = true; - for (auto const& w : members) { - g_pLayoutManager->getCurrentLayout()->onWindowCreated(w); - w->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE); - w->updateWindowDecos(); - } - g_pKeybindManager->m_groupsLocked = GROUPSLOCKEDPREV; - - if (m_workspace) { - m_workspace->updateWindows(); - m_workspace->updateWindowData(); - } - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitorID()); - g_pCompositor->updateAllWindowsAnimatedDecorationValues(); - - if (!addresses.empty()) - addresses.pop_back(); - - m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE); - g_pEventManager->postEvent(SHyprIPCEvent{.event = "togglegroup", .data = std::format("0,{}", addresses)}); -} - -PHLWINDOW CWindow::getGroupHead() { - PHLWINDOW curr = m_self.lock(); - while (!curr->m_groupData.head) - curr = curr->m_groupData.pNextWindow.lock(); - return curr; -} - -PHLWINDOW CWindow::getGroupTail() { - PHLWINDOW curr = m_self.lock(); - while (!curr->m_groupData.pNextWindow->m_groupData.head) - curr = curr->m_groupData.pNextWindow.lock(); - return curr; -} - -PHLWINDOW CWindow::getGroupCurrent() { - PHLWINDOW curr = m_self.lock(); - while (curr->isHidden()) - curr = curr->m_groupData.pNextWindow.lock(); - return curr; -} - -int CWindow::getGroupSize() { - int size = 1; - PHLWINDOW curr = m_self.lock(); - while (curr->m_groupData.pNextWindow != m_self) { - curr = curr->m_groupData.pNextWindow.lock(); - size++; - } - return size; -} - -bool CWindow::canBeGroupedInto(PHLWINDOW pWindow) { - static auto ALLOWGROUPMERGE = CConfigValue("group:merge_groups_on_drag"); - bool isGroup = m_groupData.pNextWindow; - bool disallowDragIntoGroup = g_pInputManager->m_wasDraggingWindow && isGroup && !sc(*ALLOWGROUPMERGE); - return !g_pKeybindManager->m_groupsLocked // global group lock disengaged - && ((m_groupRules & GROUP_INVADE && m_firstMap) // window ignore local group locks, or - || (!pWindow->getGroupHead()->m_groupData.locked // target unlocked - && !(m_groupData.pNextWindow.lock() && getGroupHead()->m_groupData.locked))) // source unlocked or isn't group - && !m_groupData.deny // source is not denied entry - && !(m_groupRules & GROUP_BARRED && m_firstMap) // group rule doesn't prevent adding window - && !disallowDragIntoGroup; // config allows groups to be merged -} - -PHLWINDOW CWindow::getGroupWindowByIndex(int index) { - const int SIZE = getGroupSize(); - index = ((index % SIZE) + SIZE) % SIZE; - PHLWINDOW curr = getGroupHead(); - while (index > 0) { - curr = curr->m_groupData.pNextWindow.lock(); - index--; - } - return curr; -} - -bool CWindow::hasInGroup(PHLWINDOW w) { - PHLWINDOW curr = m_groupData.pNextWindow.lock(); - while (curr && curr != m_self) { - if (curr == w) - return true; - curr = curr->m_groupData.pNextWindow.lock(); - } - return false; -} - -void CWindow::setGroupCurrent(PHLWINDOW pWindow) { - PHLWINDOW curr = m_groupData.pNextWindow.lock(); - bool isMember = false; - while (curr.get() != this) { - if (curr == pWindow) { - isMember = true; - break; - } - curr = curr->m_groupData.pNextWindow.lock(); - } - - if (!isMember && pWindow.get() != this) - return; - - const auto PCURRENT = getGroupCurrent(); - const bool FULLSCREEN = PCURRENT->isFullscreen(); - const auto WORKSPACE = PCURRENT->m_workspace; - const auto MODE = PCURRENT->m_fullscreenState.internal; - - const auto CURRENTISFOCUS = PCURRENT == Desktop::focusState()->window(); - - const auto PWINDOWSIZE = PCURRENT->m_realSize->value(); - const auto PWINDOWPOS = PCURRENT->m_realPosition->value(); - const auto PWINDOWSIZEGOAL = PCURRENT->m_realSize->goal(); - const auto PWINDOWPOSGOAL = PCURRENT->m_realPosition->goal(); - const auto PWINDOWLASTFLOATINGSIZE = PCURRENT->m_lastFloatingSize; - const auto PWINDOWLASTFLOATINGPOSITION = PCURRENT->m_lastFloatingPosition; - - if (FULLSCREEN) - g_pCompositor->setWindowFullscreenInternal(PCURRENT, FSMODE_NONE); - - PCURRENT->setHidden(true); - pWindow->setHidden(false); // can remove m_pLastWindow - - g_pLayoutManager->getCurrentLayout()->replaceWindowDataWith(PCURRENT, pWindow); - - if (PCURRENT->m_isFloating) { - pWindow->m_realPosition->setValueAndWarp(PWINDOWPOSGOAL); - pWindow->m_realSize->setValueAndWarp(PWINDOWSIZEGOAL); - pWindow->sendWindowSize(); - } - - pWindow->m_realPosition->setValue(PWINDOWPOS); - pWindow->m_realSize->setValue(PWINDOWSIZE); - - if (FULLSCREEN) - g_pCompositor->setWindowFullscreenInternal(pWindow, MODE); - - pWindow->m_lastFloatingSize = PWINDOWLASTFLOATINGSIZE; - pWindow->m_lastFloatingPosition = PWINDOWLASTFLOATINGPOSITION; - - g_pCompositor->updateAllWindowsAnimatedDecorationValues(); - - if (CURRENTISFOCUS) - Desktop::focusState()->rawWindowFocus(pWindow); - - g_pHyprRenderer->damageWindow(pWindow); - - pWindow->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE); - m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE); - pWindow->updateWindowDecos(); -} - -void CWindow::insertWindowToGroup(PHLWINDOW pWindow) { - const auto BEGINAT = m_self.lock(); - const auto ENDAT = m_groupData.pNextWindow.lock(); - - if (!pWindow->m_groupData.pNextWindow.lock()) { - BEGINAT->m_groupData.pNextWindow = pWindow; - pWindow->m_groupData.pNextWindow = ENDAT; - pWindow->m_groupData.head = false; - pWindow->addWindowDeco(makeUnique(pWindow)); - return; - } - - const auto SHEAD = pWindow->getGroupHead(); - const auto STAIL = pWindow->getGroupTail(); - - SHEAD->m_groupData.head = false; - BEGINAT->m_groupData.pNextWindow = SHEAD; - STAIL->m_groupData.pNextWindow = ENDAT; - - pWindow->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE); - m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE); - pWindow->updateWindowDecos(); -} - -PHLWINDOW CWindow::getGroupPrevious() { - PHLWINDOW curr = m_groupData.pNextWindow.lock(); - - while (curr != m_self && curr->m_groupData.pNextWindow != m_self) - curr = curr->m_groupData.pNextWindow.lock(); - - return curr; -} - -void CWindow::switchWithWindowInGroup(PHLWINDOW pWindow) { - if (!m_groupData.pNextWindow.lock() || !pWindow->m_groupData.pNextWindow.lock()) - return; - - if (m_groupData.pNextWindow.lock() == pWindow) { // A -> this -> pWindow -> B >> A -> pWindow -> this -> B - getGroupPrevious()->m_groupData.pNextWindow = pWindow; - m_groupData.pNextWindow = pWindow->m_groupData.pNextWindow; - pWindow->m_groupData.pNextWindow = m_self; - - } else if (pWindow->m_groupData.pNextWindow == m_self) { // A -> pWindow -> this -> B >> A -> this -> pWindow -> B - pWindow->getGroupPrevious()->m_groupData.pNextWindow = m_self; - pWindow->m_groupData.pNextWindow = m_groupData.pNextWindow; - m_groupData.pNextWindow = pWindow; - - } else { // A -> this -> B | C -> pWindow -> D >> A -> pWindow -> B | C -> this -> D - std::swap(m_groupData.pNextWindow, pWindow->m_groupData.pNextWindow); - std::swap(getGroupPrevious()->m_groupData.pNextWindow, pWindow->getGroupPrevious()->m_groupData.pNextWindow); - } - - std::swap(m_groupData.head, pWindow->m_groupData.head); - std::swap(m_groupData.locked, pWindow->m_groupData.locked); - - pWindow->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE); - m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_GROUP | Desktop::Rule::RULE_PROP_ON_WORKSPACE); - pWindow->updateWindowDecos(); -} - -void CWindow::updateGroupOutputs() { - if (m_groupData.pNextWindow.expired()) - return; - - PHLWINDOW curr = m_groupData.pNextWindow.lock(); - - const auto WS = m_workspace; - - while (curr.get() != this) { - curr->m_monitor = m_monitor; - curr->moveToWorkspace(WS); - - *curr->m_realPosition = m_realPosition->goal(); - *curr->m_realSize = m_realSize->goal(); - - curr = curr->m_groupData.pNextWindow.lock(); - } -} - Vector2D CWindow::middle() { return m_realPosition->goal() + m_realSize->goal() / 2.f; } @@ -1070,9 +801,13 @@ void CWindow::updateWindowData() { } void CWindow::updateWindowData(const SWorkspaceRule& workspaceRule) { - m_ruleApplicator->borderSize().matchOptional(workspaceRule.borderSize, Desktop::Types::PRIORITY_WORKSPACE_RULE); + if (workspaceRule.noBorder.value_or(false)) + m_ruleApplicator->borderSize().matchOptional(std::optional(0), Desktop::Types::PRIORITY_WORKSPACE_RULE); + else if (workspaceRule.borderSize) + m_ruleApplicator->borderSize().matchOptional(workspaceRule.borderSize, Desktop::Types::PRIORITY_WORKSPACE_RULE); + else + m_ruleApplicator->borderSize().matchOptional(std::nullopt, Desktop::Types::PRIORITY_WORKSPACE_RULE); m_ruleApplicator->decorate().matchOptional(workspaceRule.decorate, Desktop::Types::PRIORITY_WORKSPACE_RULE); - m_ruleApplicator->borderSize().matchOptional(workspaceRule.noBorder ? std::optional(0) : std::nullopt, Desktop::Types::PRIORITY_WORKSPACE_RULE); m_ruleApplicator->rounding().matchOptional(workspaceRule.noRounding.value_or(false) ? std::optional(0) : std::nullopt, Desktop::Types::PRIORITY_WORKSPACE_RULE); m_ruleApplicator->noShadow().matchOptional(workspaceRule.noShadow, Desktop::Types::PRIORITY_WORKSPACE_RULE); } @@ -1136,7 +871,7 @@ void CWindow::setAnimationsToMove() { void CWindow::onWorkspaceAnimUpdate() { // clip box for animated offsets - if (!m_isFloating || m_pinned || isFullscreen() || m_draggingTiled) { + if (!m_isFloating || m_pinned || isFullscreen()) { m_floatingOffset = Vector2D(0, 0); return; } @@ -1300,7 +1035,7 @@ void CWindow::activate(bool force) { m_isUrgent = true; g_pEventManager->postEvent(SHyprIPCEvent{.event = "urgent", .data = std::format("{:x}", rc(this))}); - EMIT_HOOK_EVENT("urgent", m_self.lock()); + Event::bus()->m_events.window.urgent.emit(m_self.lock()); if (!force && (!m_ruleApplicator->focusOnActivate().valueOr(*PFOCUSONACTIVATE) || (m_suppressedEvents & SUPPRESS_ACTIVATE_FOCUSONLY) || (m_suppressedEvents & SUPPRESS_ACTIVATE))) @@ -1314,7 +1049,7 @@ void CWindow::activate(bool force) { if (m_isFloating) g_pCompositor->changeWindowZOrder(m_self.lock(), true); - Desktop::focusState()->fullWindowFocus(m_self.lock()); + Desktop::focusState()->fullWindowFocus(m_self.lock(), FOCUS_REASON_DESKTOP_STATE_CHANGE); warpCursor(); } @@ -1361,12 +1096,13 @@ void CWindow::onUpdateMeta() { m_title = NEWTITLE; g_pEventManager->postEvent(SHyprIPCEvent{.event = "windowtitle", .data = std::format("{:x}", rc(this))}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "windowtitlev2", .data = std::format("{:x},{}", rc(this), m_title)}); - EMIT_HOOK_EVENT("windowTitle", m_self.lock()); + Event::bus()->m_events.window.title.emit(m_self.lock()); if (m_self == Desktop::focusState()->window()) { // if it's the active, let's post an event to update others g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = m_class + "," + m_title}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = std::format("{:x}", rc(this))}); - EMIT_HOOK_EVENT("activeWindow", m_self.lock()); + + // no need for a hook event } Log::logger->log(Log::DEBUG, "Window {:x} set title to {}", rc(this), m_title); @@ -1377,10 +1113,13 @@ void CWindow::onUpdateMeta() { if (m_class != NEWCLASS) { m_class = NEWCLASS; + Event::bus()->m_events.window.class_.emit(m_self.lock()); + if (m_self == Desktop::focusState()->window()) { // if it's the active, let's post an event to update others g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindow", .data = m_class + "," + m_title}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = std::format("{:x}", rc(this))}); - EMIT_HOOK_EVENT("activeWindow", m_self.lock()); + + // no need for a hook event } Log::logger->log(Log::DEBUG, "Window {:x} set class to {}", rc(this), m_class); @@ -1460,7 +1199,7 @@ void CWindow::onX11ConfigureRequest(CBox box) { g_pHyprRenderer->damageWindow(m_self.lock()); - if (!m_isFloating || isFullscreen() || g_pInputManager->m_currentlyDraggedWindow == m_self) { + if (!m_isFloating || isFullscreen() || g_layoutManager->dragController()->target() == m_self) { sendWindowSize(true); g_pInputManager->refocus(); g_pHyprRenderer->damageWindow(m_self.lock()); @@ -1491,7 +1230,22 @@ void CWindow::onX11ConfigureRequest(CBox box) { if (!m_workspace || !m_workspace->isVisible()) return; // further things are only for visible windows - m_workspace = g_pCompositor->getMonitorFromVector(m_realPosition->goal() + m_realSize->goal() / 2.f)->m_activeWorkspace; + const auto monitorByRequestedPosition = g_pCompositor->getMonitorFromVector(m_realPosition->goal() + m_realSize->goal() / 2.f); + const auto currentMonitor = m_workspace->m_monitor.lock(); + + Log::logger->log( + Log::DEBUG, + "onX11ConfigureRequest: window '{}' ({:#x}) - workspace '{}' (special={}), currentMonitor='{}', monitorByRequestedPosition='{}', pos={:.0f},{:.0f}, size={:.0f},{:.0f}", + m_title, (uintptr_t)this, m_workspace->m_name, m_workspace->m_isSpecialWorkspace, currentMonitor ? currentMonitor->m_name : "null", + monitorByRequestedPosition ? monitorByRequestedPosition->m_name : "null", m_realPosition->goal().x, m_realPosition->goal().y, m_realSize->goal().x, m_realSize->goal().y); + + // Reassign workspace only when moving to a different monitor and not on a special workspace + // X11 apps send configure requests with positions based on XWayland's monitor layout, such as "0,0", + // which would incorrectly move windows off special workspaces + if (monitorByRequestedPosition && monitorByRequestedPosition != currentMonitor && !m_workspace->m_isSpecialWorkspace) { + Log::logger->log(Log::DEBUG, "onX11ConfigureRequest: reassigning workspace from '{}' to '{}'", m_workspace->m_name, monitorByRequestedPosition->m_activeWorkspace->m_name); + m_workspace = monitorByRequestedPosition->m_activeWorkspace; + } g_pCompositor->changeWindowZOrder(m_self.lock(), true); @@ -1584,7 +1338,8 @@ Vector2D CWindow::realToReportSize() { const auto PMONITOR = m_monitor.lock(); if (*PXWLFORCESCALEZERO && PMONITOR) - return REPORTSIZE * PMONITOR->m_scale; + // Keep X11 configure sizes integral to avoid truncation (e.g. 2879.999 -> 2879) later in xcb. + return (REPORTSIZE * PMONITOR->m_scale).round(); return REPORTSIZE; } @@ -1661,20 +1416,17 @@ void CWindow::setContentType(NContentType::eContentType contentType) { } void CWindow::deactivateGroupMembers() { - auto curr = getGroupHead(); - while (curr) { - if (curr != m_self.lock()) { + if (!m_group) + return; + for (const auto& w : m_group->windows()) { + if (w != m_self.lock()) { // we don't want to deactivate unfocused xwayland windows // because X is weird, keep the behavior for wayland windows // also its not really needed for xwayland windows // ref: #9760 #9294 - if (!curr->m_isX11 && curr->m_xdgSurface && curr->m_xdgSurface->m_toplevel) - curr->m_xdgSurface->m_toplevel->setActive(false); + if (!w->m_isX11 && w->m_xdgSurface && w->m_xdgSurface->m_toplevel) + w->m_xdgSurface->m_toplevel->setActive(false); } - - curr = curr->m_groupData.pNextWindow.lock(); - if (curr == getGroupHead()) - break; } } @@ -1799,21 +1551,13 @@ void CWindow::updateDecorationValues() { const bool IS_SHADOWED_BY_MODAL = m_xdgSurface && m_xdgSurface->m_toplevel && m_xdgSurface->m_toplevel->anyChildModal(); - // border - const auto RENDERDATA = g_pLayoutManager->getCurrentLayout()->requestRenderHints(m_self.lock()); - if (RENDERDATA.isBorderGradient) - setBorderColor(*RENDERDATA.borderGradient); - else { - const bool GROUPLOCKED = m_groupData.pNextWindow.lock() ? getGroupHead()->m_groupData.locked : false; - if (m_self == Desktop::focusState()->window()) { - const auto* const ACTIVECOLOR = - !m_groupData.pNextWindow.lock() ? (!m_groupData.deny ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL); - setBorderColor(m_ruleApplicator->activeBorderColor().valueOr(*ACTIVECOLOR)); - } else { - const auto* const INACTIVECOLOR = - !m_groupData.pNextWindow.lock() ? (!m_groupData.deny ? INACTIVECOL : NOGROUPINACTIVECOL) : (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL); - setBorderColor(m_ruleApplicator->inactiveBorderColor().valueOr(*INACTIVECOLOR)); - } + const bool GROUPLOCKED = m_group ? m_group->locked() : false; + if (m_self == Desktop::focusState()->window()) { + const auto* const ACTIVECOLOR = !m_group ? (!(m_groupRules & GROUP_DENY) ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL); + setBorderColor(m_ruleApplicator->activeBorderColor().valueOr(*ACTIVECOLOR)); + } else { + const auto* const INACTIVECOLOR = !m_group ? (!(m_groupRules & GROUP_DENY) ? INACTIVECOL : NOGROUPINACTIVECOL) : (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL); + setBorderColor(m_ruleApplicator->inactiveBorderColor().valueOr(*INACTIVECOLOR)); } // opacity @@ -1901,6 +1645,7 @@ void CWindow::mapWindow() { static auto PDIMSTRENGTH = CConfigValue("decoration:dim_strength"); static auto PNEWTAKESOVERFS = CConfigValue("misc:on_focus_under_fullscreen"); static auto PINITIALWSTRACKING = CConfigValue("misc:initial_workspace_tracking"); + static auto PAUTOGROUP = CConfigValue("group:auto_group"); const auto LAST_FOCUS_WINDOW = Desktop::focusState()->window(); const bool IS_LAST_IN_FS = LAST_FOCUS_WINDOW ? LAST_FOCUS_WINDOW->m_fullscreenState.internal != FSMODE_NONE : false; @@ -2025,8 +1770,8 @@ void CWindow::mapWindow() { requestedFSMonitor = MONITOR_INVALID; } - m_isFloating = m_ruleApplicator->static_.floating.value_or(m_isFloating); - m_isPseudotiled = m_ruleApplicator->static_.pseudo.value_or(m_isPseudotiled); + m_isFloating = m_ruleApplicator->static_.floating.value_or(m_isFloating); + m_target->setPseudo(m_ruleApplicator->static_.pseudo.value_or(m_target->isPseudo())); m_noInitialFocus = m_ruleApplicator->static_.noInitialFocus.value_or(m_noInitialFocus); m_pinned = m_ruleApplicator->static_.pin.value_or(m_pinned); @@ -2081,7 +1826,7 @@ void CWindow::mapWindow() { } else if (v == "barred") { m_groupRules |= Desktop::View::GROUP_BARRED; } else if (v == "deny") { - m_groupData.deny = true; + m_groupRules |= Desktop::View::GROUP_DENY; } else if (v == "override") { // Clear existing rules m_groupRules = Desktop::View::GROUP_OVERRIDE; @@ -2126,11 +1871,12 @@ void CWindow::mapWindow() { if (WORKSPACEARGS.contains("silent")) workspaceSilent = true; - if (WORKSPACEARGS.contains("empty") && PWORKSPACE->getWindows() <= 1) { + auto joined = WORKSPACEARGS.join(" ", 0, workspaceSilent ? WORKSPACEARGS.size() - 1 : 0); + if (joined.starts_with("empty") && PWORKSPACE->getWindows() == 0) { requestedWorkspaceID = PWORKSPACE->m_id; requestedWorkspaceName = PWORKSPACE->m_name; } else { - auto result = getWorkspaceIDNameFromString(WORKSPACEARGS.join(" ", 0, workspaceSilent ? WORKSPACEARGS.size() - 1 : 0)); + auto result = getWorkspaceIDNameFromString(joined); requestedWorkspaceID = result.id; requestedWorkspaceName = result.name; } @@ -2185,9 +1931,9 @@ void CWindow::mapWindow() { m_isFloating = true; if (PWORKSPACE->m_defaultPseudo) { - m_isPseudotiled = true; CBox desiredGeometry = g_pXWaylandManager->getGeometryForWindow(m_self.lock()); - m_pseudoSize = Vector2D(desiredGeometry.width, desiredGeometry.height); + m_target->setPseudoSize(Vector2D{desiredGeometry.width, desiredGeometry.height}); + m_target->setPseudo(true); } updateWindowData(); @@ -2200,45 +1946,33 @@ void CWindow::mapWindow() { // emit the IPC event before the layout might focus the window to avoid a focus event first g_pEventManager->postEvent(SHyprIPCEvent{"openwindow", std::format("{:x},{},{},{}", m_self.lock(), PWORKSPACE->m_name, m_class, m_title)}); - EMIT_HOOK_EVENT("openWindowEarly", m_self.lock()); + Event::bus()->m_events.window.openEarly.emit(m_self.lock()); + + if (*PAUTOGROUP // auto_group enabled + && Desktop::focusState()->window() // focused window exists + && canBeGroupedInto(Desktop::focusState()->window()->m_group) // we can group + && Desktop::focusState()->window()->m_workspace == m_workspace // workspaces match, we're not opening on another ws + && !g_pXWaylandManager->shouldBeFloated(m_self.lock()) && !isX11OverrideRedirect() // not a window that should float or X11 + && !(m_isFloating && !Desktop::focusState()->window()->m_isFloating) // do not auto-group a floated window into a tiled group + && !isModal() // no modal grouping + ) { + // add to group if we are focused on one + Desktop::focusState()->window()->m_group->add(m_self.lock()); + } else + g_layoutManager->newTarget(m_target, m_workspace->m_space); + + if (!m_group && (m_groupRules & GROUP_SET)) + m_group = CGroup::create({m_self}); if (m_isFloating) { - g_pLayoutManager->getCurrentLayout()->onWindowCreated(m_self.lock()); m_createdOverFullscreen = true; - if (!m_ruleApplicator->static_.size.empty()) { - const auto COMPUTED = calculateExpression(m_ruleApplicator->static_.size); - if (!COMPUTED) - Log::logger->log(Log::ERR, "failed to parse {} as an expression", m_ruleApplicator->static_.size); - else { - *m_realSize = *COMPUTED; - setHidden(false); - } - } - - if (!m_ruleApplicator->static_.position.empty()) { - const auto COMPUTED = calculateExpression(m_ruleApplicator->static_.position); - if (!COMPUTED) - Log::logger->log(Log::ERR, "failed to parse {} as an expression", m_ruleApplicator->static_.position); - else { - *m_realPosition = *COMPUTED + PMONITOR->m_position; - setHidden(false); - } - } - - if (m_ruleApplicator->static_.center.value_or(false)) { - const auto WORKAREA = PMONITOR->logicalBoxMinusReserved(); - *m_realPosition = WORKAREA.middle() - m_realSize->goal() / 2.f; - } - // set the pseudo size to the GOAL of our current size // because the windows are animated on RealSize - m_pseudoSize = m_realSize->goal(); + m_target->setPseudoSize(m_realSize->goal()); g_pCompositor->changeWindowZOrder(m_self.lock(), true); } else { - g_pLayoutManager->getCurrentLayout()->onWindowCreated(m_self.lock()); - bool setPseudo = false; if (!m_ruleApplicator->static_.size.empty()) { @@ -2246,14 +1980,14 @@ void CWindow::mapWindow() { if (!COMPUTED) Log::logger->log(Log::ERR, "failed to parse {} as an expression", m_ruleApplicator->static_.size); else { - setPseudo = true; - m_pseudoSize = *COMPUTED; + setPseudo = true; + m_target->setPseudoSize(*COMPUTED); setHidden(false); } } if (!setPseudo) - m_pseudoSize = m_realSize->goal() - Vector2D(10, 10); + m_target->setPseudoSize(m_realSize->goal() - Vector2D(10, 10)); } const auto PFOCUSEDWINDOWPREV = Desktop::focusState()->window(); @@ -2283,13 +2017,13 @@ void CWindow::mapWindow() { (!PFORCEFOCUS || PFORCEFOCUS == m_self.lock()) && !g_pInputManager->isConstrained()) { // this window should gain focus: if it's grouped, preserve fullscreen state. - const bool SAME_GROUP = hasInGroup(LAST_FOCUS_WINDOW); + const bool SAME_GROUP = m_group && m_group->has(LAST_FOCUS_WINDOW); if (IS_LAST_IN_FS && SAME_GROUP) { - Desktop::focusState()->rawWindowFocus(m_self.lock()); + Desktop::focusState()->rawWindowFocus(m_self.lock(), FOCUS_REASON_NEW_WINDOW); g_pCompositor->setWindowFullscreenInternal(m_self.lock(), LAST_FS_MODE); } else - Desktop::focusState()->fullWindowFocus(m_self.lock()); + Desktop::focusState()->fullWindowFocus(m_self.lock(), FOCUS_REASON_NEW_WINDOW); m_activeInactiveAlpha->setValueAndWarp(*PACTIVEALPHA); m_dimPercent->setValueAndWarp(m_ruleApplicator->noDim().valueOrDefault() ? 0.f : *PDIMSTRENGTH); @@ -2330,18 +2064,16 @@ void CWindow::mapWindow() { if (workspaceSilent) { if (validMapped(PFOCUSEDWINDOWPREV)) { - Desktop::focusState()->rawWindowFocus(PFOCUSEDWINDOWPREV); + Desktop::focusState()->rawWindowFocus(PFOCUSEDWINDOWPREV, FOCUS_REASON_NEW_WINDOW); PFOCUSEDWINDOWPREV->updateWindowDecos(); // need to for some reason i cba to find out why } else if (!PFOCUSEDWINDOWPREV) - Desktop::focusState()->rawWindowFocus(nullptr); + Desktop::focusState()->rawWindowFocus(nullptr, FOCUS_REASON_NEW_WINDOW); } // swallow if (SWALLOWER) { - g_pLayoutManager->getCurrentLayout()->onWindowRemoved(SWALLOWER); - g_pHyprRenderer->damageWindow(SWALLOWER); + g_layoutManager->removeTarget(SWALLOWER->layoutTarget()); SWALLOWER->setHidden(true); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitorID()); } m_firstMap = false; @@ -2349,12 +2081,12 @@ void CWindow::mapWindow() { Log::logger->log(Log::DEBUG, "Map request dispatched, monitor {}, window pos: {:5j}, window size: {:5j}", PMONITOR->m_name, m_realPosition->goal(), m_realSize->goal()); // emit the hook event here after basic stuff has been initialized - EMIT_HOOK_EVENT("openWindow", m_self.lock()); + Event::bus()->m_events.window.open.emit(m_self.lock()); // apply data from default decos. Borders, shadows. g_pDecorationPositioner->forceRecalcFor(m_self.lock()); updateWindowDecos(); - g_pLayoutManager->getCurrentLayout()->recalculateWindow(m_self.lock()); + layoutTarget()->recalc(); // do animations g_pDesktopAnimationManager->startAnimation(m_self.lock(), CDesktopAnimationManager::ANIMATION_TYPE_IN); @@ -2380,8 +2112,11 @@ void CWindow::mapWindow() { if (m_workspace) m_workspace->updateWindows(); - if (PMONITOR && isX11OverrideRedirect()) - m_X11SurfaceScaledBy = PMONITOR->m_scale; + if (PMONITOR && isX11OverrideRedirect()) { + static auto PXWLFORCESCALEZERO = CConfigValue("xwayland:force_zero_scaling"); + if (*PXWLFORCESCALEZERO) + m_X11SurfaceScaledBy = PMONITOR->m_scale; + } } void CWindow::unmapWindow() { @@ -2405,8 +2140,9 @@ void CWindow::unmapWindow() { m_originalClosedExtents = getFullWindowExtents(); } + m_events.unmap.emit(); g_pEventManager->postEvent(SHyprIPCEvent{"closewindow", std::format("{:x}", m_self.lock())}); - EMIT_HOOK_EVENT("closeWindow", m_self.lock()); + Event::bus()->m_events.window.close.emit(m_self.lock()); if (m_isFloating && !m_isX11 && m_ruleApplicator->persistentSize().valueOrDefault()) { Log::logger->log(Log::DEBUG, "storing floating size {}x{} for window {}::{} on close", m_realSize->value().x, m_realSize->value().y, m_class, m_title); @@ -2426,10 +2162,10 @@ void CWindow::unmapWindow() { m_swallowed->m_currentlySwallowed = false; m_swallowed->setHidden(false); - if (m_groupData.pNextWindow.lock()) + if (m_group) m_swallowed->m_groupSwallowed = true; // flag for the swallowed window to be created into the group where it belongs when auto_group = false. - g_pLayoutManager->getCurrentLayout()->onWindowCreated(m_swallowed.lock()); + g_layoutManager->newTarget(m_swallowed->layoutTarget(), m_workspace->m_space); } m_swallowed->m_groupSwallowed = false; @@ -2437,7 +2173,24 @@ void CWindow::unmapWindow() { } bool wasLastWindow = false; - PHLWINDOW nextInGroup = m_groupData.pNextWindow ? m_groupData.pNextWindow.lock() : nullptr; + PHLWINDOW nextInGroup = [this] -> PHLWINDOW { + if (!m_group) + return nullptr; + + // walk the history to find a suitable window + const auto HISTORY = Desktop::History::windowTracker()->fullHistory(); + for (const auto& w : HISTORY | std::views::reverse) { + if (!w || !w->m_isMapped || w == m_self) + continue; + + if (!m_group->has(w.lock())) + continue; + + return w.lock(); + } + + return nullptr; + }(); if (m_self.lock() == Desktop::focusState()->window()) { wasLastWindow = true; @@ -2446,7 +2199,7 @@ void CWindow::unmapWindow() { g_pInputManager->releaseAllMouseButtons(); } - if (m_self.lock() == g_pInputManager->m_currentlyDraggedWindow.lock()) + if (m_self.lock() == g_layoutManager->dragController()->target()) CKeybindManager::changeMouseBindMode(MBIND_INVALID); // remove the fullscreen window status from workspace if we closed it @@ -2455,7 +2208,10 @@ void CWindow::unmapWindow() { if (PWORKSPACE->m_hasFullscreenWindow && isFullscreen()) PWORKSPACE->m_hasFullscreenWindow = false; - g_pLayoutManager->getCurrentLayout()->onWindowRemoved(m_self.lock()); + if (m_group) + m_group->remove(m_self.lock()); + + g_layoutManager->removeTarget(m_target); g_pHyprRenderer->damageWindow(m_self.lock()); @@ -2471,17 +2227,20 @@ void CWindow::unmapWindow() { if (*FOCUSONCLOSE) candidate = (g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(), Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING)); - else - candidate = g_pLayoutManager->getCurrentLayout()->getNextWindowCandidate(m_self.lock()); + else { + const auto CAND = g_layoutManager->getNextCandidate(m_workspace->m_space, layoutTarget()); + if (CAND) + candidate = CAND->window(); + } } Log::logger->log(Log::DEBUG, "On closed window, new focused candidate is {}", candidate); if (candidate != Desktop::focusState()->window() && candidate) { if (candidate == nextInGroup) - Desktop::focusState()->rawWindowFocus(candidate); + Desktop::focusState()->rawWindowFocus(candidate, FOCUS_REASON_DESKTOP_STATE_CHANGE); else - Desktop::focusState()->fullWindowFocus(candidate); + Desktop::focusState()->fullWindowFocus(candidate, FOCUS_REASON_DESKTOP_STATE_CHANGE); if ((*PEXITRETAINSFS || candidate == nextInGroup) && CURRENTWINDOWFSSTATE) g_pCompositor->setWindowFullscreenInternal(candidate, CURRENTFSMODE); @@ -2496,7 +2255,8 @@ void CWindow::unmapWindow() { if (m_self.lock() == Desktop::focusState()->window() || !Desktop::focusState()->window()) { g_pEventManager->postEvent(SHyprIPCEvent{"activewindow", ","}); g_pEventManager->postEvent(SHyprIPCEvent{"activewindowv2", ""}); - EMIT_HOOK_EVENT("activeWindow", PHLWINDOW{nullptr}); + + Event::bus()->m_events.window.active.emit(m_self.lock(), FOCUS_REASON_OTHER); } } else { Log::logger->log(Log::DEBUG, "Unmapped was not focused, ignoring a refocus."); @@ -2525,7 +2285,16 @@ void CWindow::unmapWindow() { void CWindow::commitWindow() { if (!m_isX11 && m_xdgSurface->m_initialCommit) { - Vector2D predSize = g_pLayoutManager->getCurrentLayout()->predictSizeForNewWindow(m_self.lock()); + // try to calculate static rules already for any floats + m_ruleApplicator->readStaticRules(true); + + const Vector2D predSize = !m_ruleApplicator->static_.floating.value_or(false) // no float rule + && !m_isFloating // not floating + && !parent() // no parents + && !g_pXWaylandManager->shouldBeFloated(m_self.lock(), true) // should not be floated + ? + g_layoutManager->predictSizeForNewTiledTarget().value_or(Vector2D{}) : + Vector2D{}; Log::logger->log(Log::DEBUG, "Layout predicts size {} for {}", predSize, m_self.lock()); @@ -2552,20 +2321,7 @@ void CWindow::commitWindow() { const auto PMONITOR = m_monitor.lock(); - if (PMONITOR) - PMONITOR->debugLastPresentation(g_pSeatManager->m_isPointerFrameCommit ? "listener_commitWindow skip" : "listener_commitWindow"); - - if (g_pSeatManager->m_isPointerFrameCommit) { - g_pSeatManager->m_isPointerFrameSkipped = false; - g_pSeatManager->m_isPointerFrameCommit = false; - } else - g_pHyprRenderer->damageSurface(wlSurface()->resource(), m_realPosition->goal().x, m_realPosition->goal().y, m_isX11 ? 1.0 / m_X11SurfaceScaledBy : 1.0); - - if (g_pSeatManager->m_isPointerFrameSkipped) { - g_pPointerManager->sendStoredMovement(); - g_pSeatManager->sendPointerFrame(); - g_pSeatManager->m_isPointerFrameCommit = true; - } + g_pHyprRenderer->damageSurface(wlSurface()->resource(), m_realPosition->goal().x, m_realPosition->goal().y, m_isX11 ? 1.0 / m_X11SurfaceScaledBy : 1.0); if (!m_isX11) { m_subsurfaceHead->recheckDamageForSubsurfaces(); @@ -2599,21 +2355,21 @@ void CWindow::destroyWindow() { m_listeners = {}; - g_pLayoutManager->getCurrentLayout()->onWindowRemoved(m_self.lock()); + g_layoutManager->removeTarget(m_target); m_readyToDelete = true; m_xdgSurface.reset(); - if (!m_fadingOut) { - Log::logger->log(Log::DEBUG, "Unmapped {} removed instantly", m_self.lock()); - g_pCompositor->removeWindowFromVectorSafe(m_self.lock()); // most likely X11 unmanaged or sumn - } - m_listeners.unmap.reset(); m_listeners.destroy.reset(); m_listeners.map.reset(); m_listeners.commit.reset(); + + if (!m_fadingOut) { + Log::logger->log(Log::DEBUG, "Unmapped {} removed instantly", m_self.lock()); + g_pCompositor->removeWindowFromVectorSafe(m_self.lock()); // most likely X11 unmanaged or sumn + } } void CWindow::activateX11() { @@ -2629,7 +2385,7 @@ void CWindow::activateX11() { if (!m_xwaylandSurface->wantsFocus()) return; - Desktop::focusState()->fullWindowFocus(m_self.lock()); + Desktop::focusState()->fullWindowFocus(m_self.lock(), FOCUS_REASON_DESKTOP_STATE_CHANGE); return; } @@ -2661,21 +2417,19 @@ void CWindow::unmanagedSetGeometry() { const auto LOGICALPOS = g_pXWaylandManager->xwaylandToWaylandCoords(m_xwaylandSurface->m_geometry.pos()); - if (abs(std::floor(POS.x) - LOGICALPOS.x) > 2 || abs(std::floor(POS.y) - LOGICALPOS.y) > 2 || abs(std::floor(SIZ.x) - m_xwaylandSurface->m_geometry.width) > 2 || - abs(std::floor(SIZ.y) - m_xwaylandSurface->m_geometry.height) > 2) { + const auto PMONITOR = m_monitor.lock(); + const auto XWLSCALE = (*PXWLFORCESCALEZERO && PMONITOR) ? PMONITOR->m_scale : 1.0; + const auto LOGICALGEOSIZE = m_xwaylandSurface->m_geometry.size() / XWLSCALE; + + if (abs(std::floor(POS.x) - LOGICALPOS.x) > 2 || abs(std::floor(POS.y) - LOGICALPOS.y) > 2 || abs(std::floor(SIZ.x) - LOGICALGEOSIZE.x) > 2 || + abs(std::floor(SIZ.y) - LOGICALGEOSIZE.y) > 2) { Log::logger->log(Log::DEBUG, "Unmanaged window {} requests geometry update to {:j} {:j}", m_self.lock(), LOGICALPOS, m_xwaylandSurface->m_geometry.size()); g_pHyprRenderer->damageWindow(m_self.lock()); m_realPosition->setValueAndWarp(Vector2D(LOGICALPOS.x, LOGICALPOS.y)); - if (abs(std::floor(SIZ.x) - m_xwaylandSurface->m_geometry.w) > 2 || abs(std::floor(SIZ.y) - m_xwaylandSurface->m_geometry.h) > 2) - m_realSize->setValueAndWarp(m_xwaylandSurface->m_geometry.size()); - - if (*PXWLFORCESCALEZERO) { - if (const auto PMONITOR = m_monitor.lock(); PMONITOR) { - m_realSize->setValueAndWarp(m_realSize->goal() / PMONITOR->m_scale); - } - } + if (abs(std::floor(SIZ.x) - LOGICALGEOSIZE.x) > 2 || abs(std::floor(SIZ.y) - LOGICALGEOSIZE.y) > 2) + m_realSize->setValueAndWarp(LOGICALGEOSIZE); m_position = m_realPosition->goal(); m_size = m_realSize->goal(); @@ -2729,3 +2483,26 @@ std::optional CWindow::maxSize() { return maxSize; } + +SP CWindow::layoutTarget() { + return m_group ? m_group->m_target : m_target; +} + +bool CWindow::canBeGroupedInto(SP group) { + if (!group) + return false; + + if (isX11OverrideRedirect()) + return false; + + static auto ALLOWGROUPMERGE = CConfigValue("group:merge_groups_on_drag"); + bool isGroup = m_group; + bool disallowDragIntoGroup = g_layoutManager->dragController()->wasDraggingWindow() && isGroup && !sc(*ALLOWGROUPMERGE); + return !g_pKeybindManager->m_groupsLocked // global group lock disengaged + && ((m_groupRules & GROUP_INVADE && m_firstMap) // window ignore local group locks, or + || (!group->locked() // target unlocked + && !(m_group && m_group->locked()))) // source unlocked or isn't group + && !(m_groupRules & GROUP_DENY) // source is not denied entry + && !(m_groupRules & GROUP_BARRED && m_firstMap) // group rule doesn't prevent adding window + && !disallowDragIntoGroup; // config allows groups to be merged +} diff --git a/src/desktop/view/Window.hpp b/src/desktop/view/Window.hpp index 3c36283d..d689ae3f 100644 --- a/src/desktop/view/Window.hpp +++ b/src/desktop/view/Window.hpp @@ -26,8 +26,19 @@ struct SWorkspaceRule; class IWindowTransformer; +namespace Layout { + class ITarget; + class CWindowTarget; +} + +namespace Desktop { + enum eFocusReason : uint8_t; +} + namespace Desktop::View { + class CGroup; + enum eGroupRules : uint8_t { // effective only during first map, except for _ALWAYS variant GROUP_NONE = 0, @@ -38,6 +49,7 @@ namespace Desktop::View { GROUP_LOCK_ALWAYS = 1 << 4, GROUP_INVADE = 1 << 5, // Force enter a group, event if lock is engaged GROUP_OVERRIDE = 1 << 6, // Override other rules + GROUP_DENY = 1 << 7, // deny }; enum eGetWindowProperties : uint8_t { @@ -61,6 +73,11 @@ namespace Desktop::View { SUPPRESS_FULLSCREEN_OUTPUT = 1 << 4, }; + struct SWindowActiveEvent { + PHLWINDOW window = nullptr; + eFocusReason reason = sc(0) /* unknown */; + }; + struct SInitialWorkspaceToken { PHLWINDOWREF primaryOwner; std::string workspace; @@ -92,11 +109,17 @@ namespace Desktop::View { struct { CSignalT<> destroy; + CSignalT<> unmap; + CSignalT<> hide; + CSignalT<> resize; + CSignalT<> monitorChanged; } m_events; WP m_xdgSurface; WP m_xwaylandSurface; + SP m_target; + // this is the position and size of the "bounding box" Vector2D m_position = Vector2D(0, 0); Vector2D m_size = Vector2D(0, 0); @@ -112,30 +135,21 @@ namespace Desktop::View { std::optional> m_pendingSizeAck; std::vector> m_pendingSizeAcks; - // for restoring floating statuses - Vector2D m_lastFloatingSize; - Vector2D m_lastFloatingPosition; - // for floating window offset in workspace animations Vector2D m_floatingOffset = Vector2D(0, 0); - // this is used for pseudotiling - bool m_isPseudotiled = false; - Vector2D m_pseudoSize = Vector2D(1280, 720); - // for recovering relative cursor position Vector2D m_relativeCursorCoordsOnLastWarp = Vector2D(-1, -1); bool m_firstMap = false; // for layouts bool m_isFloating = false; - bool m_draggingTiled = false; // for dragging around tiled windows SFullscreenState m_fullscreenState = {.internal = FSMODE_NONE, .client = FSMODE_NONE}; std::string m_title = ""; std::string m_class = ""; std::string m_initialTitle = ""; std::string m_initialClass = ""; PHLWORKSPACE m_workspace; - PHLMONITORREF m_monitor; + PHLMONITORREF m_monitor, m_prevMonitor; bool m_isMapped = false; @@ -229,15 +243,13 @@ namespace Desktop::View { std::string m_initialWorkspaceToken = ""; // for groups - struct SGroupData { - PHLWINDOWREF pNextWindow; // nullptr means no grouping. Self means single group. - bool head = false; - bool locked = false; // per group lock - bool deny = false; // deny window from enter a group or made a group - } m_groupData; - uint16_t m_groupRules = Desktop::View::GROUP_NONE; + SP m_group; + uint16_t m_groupRules = Desktop::View::GROUP_NONE; - bool m_tearingHint = false; + bool m_tearingHint = false; + + // Stable ID for ext_foreign_toplevel_list + const uint64_t m_stableID = 0x2137; // ANR PHLANIMVAR m_notRespondingTint; @@ -300,21 +312,6 @@ namespace Desktop::View { bool isInCurvedCorner(double x, double y); bool hasPopupAt(const Vector2D& pos); int popupsCount(); - void applyGroupRules(); - void createGroup(); - void destroyGroup(); - PHLWINDOW getGroupHead(); - PHLWINDOW getGroupTail(); - PHLWINDOW getGroupCurrent(); - PHLWINDOW getGroupPrevious(); - PHLWINDOW getGroupWindowByIndex(int); - bool hasInGroup(PHLWINDOW); - int getGroupSize(); - bool canBeGroupedInto(PHLWINDOW pWindow); - void setGroupCurrent(PHLWINDOW pWindow); - void insertWindowToGroup(PHLWINDOW pWindow); - void updateGroupOutputs(); - void switchWithWindowInGroup(PHLWINDOW pWindow); void setAnimationsToMove(); void onWorkspaceAnimUpdate(); void onFocusAnimUpdate(); @@ -328,8 +325,6 @@ namespace Desktop::View { PHLWINDOW getSwallower(); bool isX11OverrideRedirect(); bool isModal(); - Vector2D requestedMinSize(); - Vector2D requestedMaxSize(); Vector2D realToReportSize(); Vector2D realToReportPosition(); Vector2D xwaylandSizeToReal(Vector2D size); @@ -349,6 +344,8 @@ namespace Desktop::View { std::optional calculateExpression(const std::string& s); std::optional minSize(); std::optional maxSize(); + SP layoutTarget(); + bool canBeGroupedInto(SP group); CBox getWindowMainSurfaceBox() const { return {m_realPosition->value().x, m_realPosition->value().y, m_realSize->value().x, m_realSize->value().y}; diff --git a/src/event/EventBus.cpp b/src/event/EventBus.cpp new file mode 100644 index 00000000..f06c3984 --- /dev/null +++ b/src/event/EventBus.cpp @@ -0,0 +1,8 @@ +#include "EventBus.hpp" + +using namespace Event; + +UP& Event::bus() { + static UP p = makeUnique(); + return p; +} diff --git a/src/event/EventBus.hpp b/src/event/EventBus.hpp new file mode 100644 index 00000000..a30288f0 --- /dev/null +++ b/src/event/EventBus.hpp @@ -0,0 +1,144 @@ +#pragma once + +#include "../helpers/memory/Memory.hpp" +#include "../helpers/signal/Signal.hpp" +#include "../helpers/math/Math.hpp" + +#include "../devices/IPointer.hpp" +#include "../devices/IKeyboard.hpp" +#include "../devices/Tablet.hpp" +#include "../devices/ITouch.hpp" + +#include "../desktop/DesktopTypes.hpp" + +#include "../SharedDefs.hpp" + +namespace Desktop { + enum eFocusReason : uint8_t; +} +namespace Event { + struct SCallbackInfo { + bool cancelled = false; /* on cancellable events, will cancel the event. */ + }; + + class CEventBus { + public: + CEventBus() = default; + ~CEventBus() = default; + + template + using Event = CSignalT; + + template + using Cancellable = CSignalT; + + struct { + Event<> ready; + Event<> tick; + + struct { + Event open; + Event openEarly; + Event destroy; + Event close; + Event kill; + Event active; + Event urgent; + Event title; + Event class_; + Event pin; + Event fullscreen; + Event updateRules; + Event moveToWorkspace; + } window; + + struct { + Event opened; + Event closed; + Event updateRules; + } layer; + + struct { + struct { + Cancellable move; + Cancellable button; + Cancellable axis; + } mouse; + + struct { + Cancellable key; + Event, const std::string&> layout; + Event> focus; + } keyboard; + + struct { + Cancellable axis; + Cancellable button; + Cancellable proximity; + Cancellable tip; + } tablet; + + struct { + Cancellable cancel; + Cancellable down; + Cancellable up; + Cancellable motion; + } touch; + } input; + + struct { + Event pre; + Event stage; + } render; + + struct { + Event state; + } screenshare; + + struct { + struct { + Cancellable begin; + Cancellable end; + Cancellable update; + } swipe; + + struct { + Cancellable begin; + Cancellable end; + Cancellable update; + } pinch; + } gesture; + + struct { + Event newMon; + Event preAdded; + Event added; + Event preRemoved; + Event removed; + Event preCommit; + Event focused; + + Event<> layoutChanged; + } monitor; + + struct { + Event moveToMonitor; + Event active; + Event created; + Event removed; + } workspace; + + struct { + Event<> preReload; + Event<> reloaded; + } config; + + struct { + Event submap; + } keybinds; + + } m_events; + }; + + UP& bus(); +}; diff --git a/src/helpers/AsyncDialogBox.cpp b/src/helpers/AsyncDialogBox.cpp index 4cef4252..8c2c7cd7 100644 --- a/src/helpers/AsyncDialogBox.cpp +++ b/src/helpers/AsyncDialogBox.cpp @@ -4,6 +4,8 @@ #include #include #include "../managers/eventLoop/EventLoopManager.hpp" +#include "../desktop/rule/windowRule/WindowRule.hpp" +#include "../desktop/rule/Engine.hpp" using namespace Hyprutils::OS; @@ -119,6 +121,9 @@ SP> CAsyncDialogBox::open() { m_selfReference = m_selfWeakReference.lock(); + if (!m_execRuleToken.empty()) + proc.addEnv(Desktop::Rule::EXEC_RULE_ENV_NAME, m_execRuleToken); + if (!proc.runAsync()) { Log::logger->log(Log::ERR, "CAsyncDialogBox::open: failed to run async"); wl_event_source_remove(m_readEventSource); @@ -154,3 +159,9 @@ pid_t CAsyncDialogBox::getPID() const { SP CAsyncDialogBox::lockSelf() { return m_selfWeakReference.lock(); } + +void CAsyncDialogBox::setExecRule(std::string&& s) { + auto rule = Desktop::Rule::CWindowRule::buildFromExecString(std::move(s)); + m_execRuleToken = rule->execToken(); + Desktop::Rule::ruleEngine()->registerRule(std::move(rule)); +} diff --git a/src/helpers/AsyncDialogBox.hpp b/src/helpers/AsyncDialogBox.hpp index 8db516ce..1bdeba14 100644 --- a/src/helpers/AsyncDialogBox.hpp +++ b/src/helpers/AsyncDialogBox.hpp @@ -27,6 +27,7 @@ class CAsyncDialogBox { void kill(); bool isRunning() const; pid_t getPID() const; + void setExecRule(std::string&& s); SP lockSelf(); @@ -41,7 +42,8 @@ class CAsyncDialogBox { pid_t m_dialogPid = 0; wl_event_source* m_readEventSource = nullptr; Hyprutils::OS::CFileDescriptor m_pipeReadFd; - std::string m_stdout = ""; + std::string m_stdout = ""; + std::string m_execRuleToken = ""; const std::string m_title; const std::string m_description; diff --git a/src/helpers/Drm.cpp b/src/helpers/Drm.cpp new file mode 100644 index 00000000..207b5e3d --- /dev/null +++ b/src/helpers/Drm.cpp @@ -0,0 +1,20 @@ +#include +#include "Drm.hpp" + +bool DRM::sameGpu(int fd1, int fd2) { + drmDevice* devA = nullptr; + drmDevice* devB = nullptr; + + if (drmGetDevice2(fd1, 0, &devA) != 0) + return false; + if (drmGetDevice2(fd2, 0, &devB) != 0) { + drmFreeDevice(&devA); + return false; + } + + bool same = drmDevicesEqual(devA, devB); + + drmFreeDevice(&devA); + drmFreeDevice(&devB); + return same; +} diff --git a/src/helpers/Drm.hpp b/src/helpers/Drm.hpp new file mode 100644 index 00000000..bc56b1ee --- /dev/null +++ b/src/helpers/Drm.hpp @@ -0,0 +1,5 @@ +#pragma once + +namespace DRM { + bool sameGpu(int fd1, int fd2); +} diff --git a/src/helpers/Format.cpp b/src/helpers/Format.cpp index 37f77d78..7660934e 100644 --- a/src/helpers/Format.cpp +++ b/src/helpers/Format.cpp @@ -6,158 +6,186 @@ #include #include -/* - DRM formats are LE, while OGL is BE. The two primary formats - will be flipped, so we will set flipRB which will later use swizzle - to flip the red and blue channels. - This will not work on GLES2, but I want to drop support for it one day anyways. -*/ inline const std::vector GLES3_FORMATS = { { - .drmFormat = DRM_FORMAT_ARGB8888, - .flipRB = true, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_BYTE, - .withAlpha = true, - .alphaStripped = DRM_FORMAT_XRGB8888, - .bytesPerBlock = 4, + .drmFormat = DRM_FORMAT_ARGB8888, + .glInternalFormat = GL_RGBA8, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_BYTE, + .withAlpha = true, + .alphaStripped = DRM_FORMAT_XRGB8888, + .bytesPerBlock = 4, + .swizzle = {SWIZZLE_BGRA}, }, { - .drmFormat = DRM_FORMAT_XRGB8888, - .flipRB = true, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_BYTE, - .withAlpha = false, - .alphaStripped = DRM_FORMAT_XRGB8888, - .bytesPerBlock = 4, + .drmFormat = DRM_FORMAT_XRGB8888, + .glInternalFormat = GL_RGBA8, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_BYTE, + .withAlpha = false, + .alphaStripped = DRM_FORMAT_XRGB8888, + .bytesPerBlock = 4, + .swizzle = {SWIZZLE_BGR1}, }, { - .drmFormat = DRM_FORMAT_XBGR8888, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_BYTE, - .withAlpha = false, - .alphaStripped = DRM_FORMAT_XBGR8888, - .bytesPerBlock = 4, + .drmFormat = DRM_FORMAT_XBGR8888, + .glInternalFormat = GL_RGBA8, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_BYTE, + .withAlpha = false, + .alphaStripped = DRM_FORMAT_XBGR8888, + .bytesPerBlock = 4, + .swizzle = {SWIZZLE_RGB1}, }, { - .drmFormat = DRM_FORMAT_ABGR8888, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_BYTE, - .withAlpha = true, - .alphaStripped = DRM_FORMAT_XBGR8888, - .bytesPerBlock = 4, + .drmFormat = DRM_FORMAT_ABGR8888, + .glInternalFormat = GL_RGBA8, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_BYTE, + .withAlpha = true, + .alphaStripped = DRM_FORMAT_XBGR8888, + .bytesPerBlock = 4, + .swizzle = {SWIZZLE_RGBA}, }, { - .drmFormat = DRM_FORMAT_BGR888, - .glFormat = GL_RGB, - .glType = GL_UNSIGNED_BYTE, - .withAlpha = false, - .alphaStripped = DRM_FORMAT_BGR888, - .bytesPerBlock = 3, + .drmFormat = DRM_FORMAT_BGR888, + .glInternalFormat = GL_RGB8, + .glFormat = GL_RGB, + .glType = GL_UNSIGNED_BYTE, + .withAlpha = false, + .alphaStripped = DRM_FORMAT_BGR888, + .bytesPerBlock = 3, + .swizzle = {SWIZZLE_RGB1}, }, { - .drmFormat = DRM_FORMAT_RGBX4444, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_SHORT_4_4_4_4, - .withAlpha = false, - .alphaStripped = DRM_FORMAT_RGBX4444, - .bytesPerBlock = 2, + .drmFormat = DRM_FORMAT_RGBX4444, + .glInternalFormat = GL_RGBA4, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_SHORT_4_4_4_4, + .withAlpha = false, + .alphaStripped = DRM_FORMAT_RGBX4444, + .bytesPerBlock = 2, + .swizzle = {SWIZZLE_RGB1}, }, { - .drmFormat = DRM_FORMAT_RGBA4444, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_SHORT_4_4_4_4, - .withAlpha = true, - .alphaStripped = DRM_FORMAT_RGBX4444, - .bytesPerBlock = 2, + .drmFormat = DRM_FORMAT_RGBA4444, + .glInternalFormat = GL_RGBA4, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_SHORT_4_4_4_4, + .withAlpha = true, + .alphaStripped = DRM_FORMAT_RGBX4444, + .bytesPerBlock = 2, + .swizzle = {SWIZZLE_RGBA}, }, { - .drmFormat = DRM_FORMAT_RGBX5551, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_SHORT_5_5_5_1, - .withAlpha = false, - .alphaStripped = DRM_FORMAT_RGBX5551, - .bytesPerBlock = 2, + .drmFormat = DRM_FORMAT_RGBX5551, + .glInternalFormat = GL_RGB5_A1, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_SHORT_5_5_5_1, + .withAlpha = false, + .alphaStripped = DRM_FORMAT_RGBX5551, + .bytesPerBlock = 2, + .swizzle = {SWIZZLE_RGB1}, }, { - .drmFormat = DRM_FORMAT_RGBA5551, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_SHORT_5_5_5_1, - .withAlpha = true, - .alphaStripped = DRM_FORMAT_RGBX5551, - .bytesPerBlock = 2, + .drmFormat = DRM_FORMAT_RGBA5551, + .glInternalFormat = GL_RGB5_A1, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_SHORT_5_5_5_1, + .withAlpha = true, + .alphaStripped = DRM_FORMAT_RGBX5551, + .bytesPerBlock = 2, + .swizzle = {SWIZZLE_RGBA}, }, { - .drmFormat = DRM_FORMAT_RGB565, - .glFormat = GL_RGB, - .glType = GL_UNSIGNED_SHORT_5_6_5, - .withAlpha = false, - .alphaStripped = DRM_FORMAT_RGB565, - .bytesPerBlock = 2, + .drmFormat = DRM_FORMAT_RGB565, + .glInternalFormat = GL_RGB565, + .glFormat = GL_RGB, + .glType = GL_UNSIGNED_SHORT_5_6_5, + .withAlpha = false, + .alphaStripped = DRM_FORMAT_RGB565, + .bytesPerBlock = 2, + .swizzle = {SWIZZLE_RGB1}, }, { - .drmFormat = DRM_FORMAT_XBGR2101010, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_INT_2_10_10_10_REV, - .withAlpha = false, - .alphaStripped = DRM_FORMAT_XBGR2101010, - .bytesPerBlock = 4, + .drmFormat = DRM_FORMAT_XBGR2101010, + .glInternalFormat = GL_RGB10_A2, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_INT_2_10_10_10_REV, + .withAlpha = false, + .alphaStripped = DRM_FORMAT_XBGR2101010, + .bytesPerBlock = 4, + .swizzle = {SWIZZLE_RGB1}, }, { - .drmFormat = DRM_FORMAT_ABGR2101010, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_INT_2_10_10_10_REV, - .withAlpha = true, - .alphaStripped = DRM_FORMAT_XBGR2101010, - .bytesPerBlock = 4, + .drmFormat = DRM_FORMAT_ABGR2101010, + .glInternalFormat = GL_RGB10_A2, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_INT_2_10_10_10_REV, + .withAlpha = true, + .alphaStripped = DRM_FORMAT_XBGR2101010, + .bytesPerBlock = 4, + .swizzle = {SWIZZLE_RGBA}, }, { - .drmFormat = DRM_FORMAT_XRGB2101010, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_INT_2_10_10_10_REV, - .withAlpha = false, - .alphaStripped = DRM_FORMAT_XRGB2101010, - .bytesPerBlock = 4, + .drmFormat = DRM_FORMAT_XRGB2101010, + .glInternalFormat = GL_RGB10_A2, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_INT_2_10_10_10_REV, + .withAlpha = false, + .alphaStripped = DRM_FORMAT_XRGB2101010, + .bytesPerBlock = 4, + .swizzle = {SWIZZLE_BGR1}, }, { - .drmFormat = DRM_FORMAT_ARGB2101010, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_INT_2_10_10_10_REV, - .withAlpha = true, - .alphaStripped = DRM_FORMAT_XRGB2101010, - .bytesPerBlock = 4, + .drmFormat = DRM_FORMAT_ARGB2101010, + .glInternalFormat = GL_RGB10_A2, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_INT_2_10_10_10_REV, + .withAlpha = true, + .alphaStripped = DRM_FORMAT_XRGB2101010, + .bytesPerBlock = 4, + .swizzle = {SWIZZLE_BGRA}, }, { - .drmFormat = DRM_FORMAT_XBGR16161616F, - .glFormat = GL_RGBA, - .glType = GL_HALF_FLOAT, - .withAlpha = false, - .alphaStripped = DRM_FORMAT_XBGR16161616F, - .bytesPerBlock = 8, + .drmFormat = DRM_FORMAT_XBGR16161616F, + .glInternalFormat = GL_RGBA16F, + .glFormat = GL_RGBA, + .glType = GL_HALF_FLOAT, + .withAlpha = false, + .alphaStripped = DRM_FORMAT_XBGR16161616F, + .bytesPerBlock = 8, + .swizzle = {SWIZZLE_RGB1}, }, { - .drmFormat = DRM_FORMAT_ABGR16161616F, - .glFormat = GL_RGBA, - .glType = GL_HALF_FLOAT, - .withAlpha = true, - .alphaStripped = DRM_FORMAT_XBGR16161616F, - .bytesPerBlock = 8, + .drmFormat = DRM_FORMAT_ABGR16161616F, + .glInternalFormat = GL_RGBA16F, + .glFormat = GL_RGBA, + .glType = GL_HALF_FLOAT, + .withAlpha = true, + .alphaStripped = DRM_FORMAT_XBGR16161616F, + .bytesPerBlock = 8, + .swizzle = {SWIZZLE_RGBA}, }, { - .drmFormat = DRM_FORMAT_XBGR16161616, - .glFormat = GL_RGBA16UI, - .glType = GL_UNSIGNED_SHORT, - .withAlpha = false, - .alphaStripped = DRM_FORMAT_XBGR16161616, - .bytesPerBlock = 8, + .drmFormat = DRM_FORMAT_XBGR16161616, + .glInternalFormat = GL_RGBA16UI, + .glFormat = GL_RGBA_INTEGER, + .glType = GL_UNSIGNED_SHORT, + .withAlpha = false, + .alphaStripped = DRM_FORMAT_XBGR16161616, + .bytesPerBlock = 8, + .swizzle = {SWIZZLE_RGBA}, }, { - .drmFormat = DRM_FORMAT_ABGR16161616, - .glFormat = GL_RGBA16UI, - .glType = GL_UNSIGNED_SHORT, - .withAlpha = true, - .alphaStripped = DRM_FORMAT_XBGR16161616, - .bytesPerBlock = 8, + .drmFormat = DRM_FORMAT_ABGR16161616, + .glInternalFormat = GL_RGBA16UI, + .glFormat = GL_RGBA_INTEGER, + .glType = GL_UNSIGNED_SHORT, + .withAlpha = true, + .alphaStripped = DRM_FORMAT_XBGR16161616, + .bytesPerBlock = 8, + .swizzle = {SWIZZLE_RGBA}, }, { .drmFormat = DRM_FORMAT_YVYU, @@ -170,24 +198,28 @@ inline const std::vector GLES3_FORMATS = { .blockSize = {2, 1}, }, { - .drmFormat = DRM_FORMAT_R8, - .bytesPerBlock = 1, + .drmFormat = DRM_FORMAT_R8, + .glInternalFormat = GL_R8, + .glFormat = GL_RED, + .glType = GL_UNSIGNED_BYTE, + .bytesPerBlock = 1, + .swizzle = {SWIZZLE_R001}, }, { - .drmFormat = DRM_FORMAT_GR88, - .bytesPerBlock = 2, + .drmFormat = DRM_FORMAT_GR88, + .glInternalFormat = GL_RG8, + .glFormat = GL_RG, + .glType = GL_UNSIGNED_BYTE, + .bytesPerBlock = 2, + .swizzle = {SWIZZLE_RG01}, }, { - .drmFormat = DRM_FORMAT_RGB888, - .bytesPerBlock = 3, - }, - { - .drmFormat = DRM_FORMAT_BGR888, - .bytesPerBlock = 3, - }, - { - .drmFormat = DRM_FORMAT_RGBX4444, - .bytesPerBlock = 2, + .drmFormat = DRM_FORMAT_RGB888, + .glInternalFormat = GL_RGB8, + .glFormat = GL_RGB, + .glType = GL_UNSIGNED_BYTE, + .bytesPerBlock = 3, + .swizzle = {SWIZZLE_BGR1}, }, }; @@ -229,6 +261,26 @@ const SPixelFormat* NFormatUtils::getPixelFormatFromGL(uint32_t glFormat, uint32 return nullptr; } +bool NFormatUtils::isFormatYUV(uint32_t drmFormat) { + switch (drmFormat) { + case DRM_FORMAT_YUYV: + case DRM_FORMAT_YVYU: + case DRM_FORMAT_UYVY: + case DRM_FORMAT_VYUY: + case DRM_FORMAT_AYUV: + case DRM_FORMAT_NV12: + case DRM_FORMAT_NV21: + case DRM_FORMAT_NV16: + case DRM_FORMAT_NV61: + case DRM_FORMAT_YUV410: + case DRM_FORMAT_YUV411: + case DRM_FORMAT_YUV420: + case DRM_FORMAT_YUV422: + case DRM_FORMAT_YUV444: return true; + default: return false; + } +} + bool NFormatUtils::isFormatOpaque(DRMFormat drm) { const auto FMT = NFormatUtils::getPixelFormatFromDRM(drm); if (!FMT) @@ -245,32 +297,38 @@ int NFormatUtils::minStride(const SPixelFormat* const fmt, int32_t width) { return std::ceil((width * fmt->bytesPerBlock) / pixelsPerBlock(fmt)); } -uint32_t NFormatUtils::drmFormatToGL(DRMFormat drm) { - switch (drm) { - case DRM_FORMAT_XRGB8888: - case DRM_FORMAT_XBGR8888: return GL_RGBA; // doesn't matter, opengl is gucci in this case. - case DRM_FORMAT_XRGB2101010: - case DRM_FORMAT_XBGR2101010: return GL_RGB10_A2; - default: return GL_RGBA; - } - UNREACHABLE(); - return GL_RGBA; -} - -uint32_t NFormatUtils::glFormatToType(uint32_t gl) { - return gl != GL_RGBA ? GL_UNSIGNED_INT_2_10_10_10_REV : GL_UNSIGNED_BYTE; -} - std::string NFormatUtils::drmFormatName(DRMFormat drm) { - auto n = drmGetFormatName(drm); + auto n = drmGetFormatName(drm); + + if (!n) + return "unknown"; + std::string name = n; free(n); // NOLINT(cppcoreguidelines-no-malloc,-warnings-as-errors) return name; } std::string NFormatUtils::drmModifierName(uint64_t mod) { - auto n = drmGetFormatModifierName(mod); + auto n = drmGetFormatModifierName(mod); + + if (!n) + return "unknown"; + std::string name = n; free(n); // NOLINT(cppcoreguidelines-no-malloc,-warnings-as-errors) return name; } + +DRMFormat NFormatUtils::alphaFormat(DRMFormat prevFormat) { + switch (prevFormat) { + case DRM_FORMAT_XRGB8888: return DRM_FORMAT_ARGB8888; + case DRM_FORMAT_XBGR8888: return DRM_FORMAT_ABGR8888; + case DRM_FORMAT_BGRX8888: return DRM_FORMAT_BGRA8888; + case DRM_FORMAT_RGBX8888: return DRM_FORMAT_RGBA8888; + case DRM_FORMAT_XRGB2101010: return DRM_FORMAT_ARGB2101010; + case DRM_FORMAT_XBGR2101010: return DRM_FORMAT_ABGR2101010; + case DRM_FORMAT_RGBX1010102: return DRM_FORMAT_RGBA1010102; + case DRM_FORMAT_BGRX1010102: return DRM_FORMAT_BGRA1010102; + default: return 0; + } +} diff --git a/src/helpers/Format.hpp b/src/helpers/Format.hpp index fe68f763..02925e22 100644 --- a/src/helpers/Format.hpp +++ b/src/helpers/Format.hpp @@ -2,22 +2,43 @@ #include #include +#include #include "math/Math.hpp" #include using DRMFormat = uint32_t; using SHMFormat = uint32_t; +#define SWIZZLE_A1GB {GL_ALPHA, GL_ONE, GL_GREEN, GL_BLUE} +#define SWIZZLE_ABG1 {GL_ALPHA, GL_BLUE, GL_GREEN, GL_ONE} +#define SWIZZLE_ABGR {GL_ALPHA, GL_BLUE, GL_GREEN, GL_RED} +#define SWIZZLE_ARGB {GL_ALPHA, GL_RED, GL_GREEN, GL_BLUE} +#define SWIZZLE_B1RG {GL_BLUE, GL_ONE, GL_RED, GL_GREEN} +#define SWIZZLE_BARG {GL_BLUE, GL_ALPHA, GL_RED, GL_GREEN} +#define SWIZZLE_BGR1 {GL_BLUE, GL_GREEN, GL_RED, GL_ONE} +#define SWIZZLE_BGRA {GL_BLUE, GL_GREEN, GL_RED, GL_ALPHA} +#define SWIZZLE_G1AB {GL_GREEN, GL_ONE, GL_ALPHA, GL_BLUE} +#define SWIZZLE_GBA1 {GL_GREEN, GL_BLUE, GL_ALPHA, GL_ONE} +#define SWIZZLE_GBAR {GL_GREEN, GL_BLUE, GL_ALPHA, GL_RED} +#define SWIZZLE_GRAB {GL_GREEN, GL_RED, GL_ALPHA, GL_BLUE} +#define SWIZZLE_R001 {GL_RED, GL_ZERO, GL_ZERO, GL_ONE} +#define SWIZZLE_R1BG {GL_RED, GL_ONE, GL_BLUE, GL_GREEN} +#define SWIZZLE_RABG {GL_RED, GL_ALPHA, GL_BLUE, GL_GREEN} +#define SWIZZLE_RG01 {GL_RED, GL_GREEN, GL_ZERO, GL_ONE} +#define SWIZZLE_GR01 {GL_GREEN, GL_RED, GL_ZERO, GL_ONE} +#define SWIZZLE_RGB1 {GL_RED, GL_GREEN, GL_BLUE, GL_ONE} +#define SWIZZLE_RGBA {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA} + struct SPixelFormat { - DRMFormat drmFormat = 0; /* DRM_FORMAT_INVALID */ - bool flipRB = false; - int glInternalFormat = 0; - int glFormat = 0; - int glType = 0; - bool withAlpha = true; - DRMFormat alphaStripped = 0; /* DRM_FORMAT_INVALID */ - uint32_t bytesPerBlock = 0; - Vector2D blockSize; + DRMFormat drmFormat = 0; /* DRM_FORMAT_INVALID */ + int glInternalFormat = 0; + int glFormat = 0; + int glType = 0; + bool withAlpha = true; + DRMFormat alphaStripped = 0; /* DRM_FORMAT_INVALID */ + uint32_t bytesPerBlock = 0; + Vector2D blockSize; + std::optional> swizzle = std::nullopt; }; using SDRMFormat = Aquamarine::SDRMFormat; @@ -28,11 +49,11 @@ namespace NFormatUtils { const SPixelFormat* getPixelFormatFromDRM(DRMFormat drm); const SPixelFormat* getPixelFormatFromGL(uint32_t glFormat, uint32_t glType, bool alpha); + bool isFormatYUV(uint32_t drmFormat); bool isFormatOpaque(DRMFormat drm); int pixelsPerBlock(const SPixelFormat* const fmt); int minStride(const SPixelFormat* const fmt, int32_t width); - uint32_t drmFormatToGL(DRMFormat drm); - uint32_t glFormatToType(uint32_t gl); std::string drmFormatName(DRMFormat drm); std::string drmModifierName(uint64_t mod); + DRMFormat alphaFormat(DRMFormat prevFormat); }; diff --git a/src/helpers/MainLoopExecutor.cpp b/src/helpers/MainLoopExecutor.cpp index 8632d93b..c7b5f910 100644 --- a/src/helpers/MainLoopExecutor.cpp +++ b/src/helpers/MainLoopExecutor.cpp @@ -11,10 +11,7 @@ static int onDataRead(int fd, uint32_t mask, void* data) { CMainLoopExecutor::CMainLoopExecutor(std::function&& callback) : m_fn(std::move(callback)) { int fds[2]; - pipe(fds); - - RASSERT(fds[0] != 0, "CMainLoopExecutor: failed to open a pipe"); - RASSERT(fds[1] != 0, "CMainLoopExecutor: failed to open a pipe"); + RASSERT(pipe(fds) == 0, "CMainLoopExecutor: failed to open a pipe"); m_event = wl_event_loop_add_fd(g_pEventLoopManager->m_wayland.loop, fds[0], WL_EVENT_READABLE, ::onDataRead, this); diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index 5069f5d8..07156ff1 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -2,6 +2,7 @@ #include "MiscFunctions.hpp" #include "../macros.hpp" #include "SharedDefs.hpp" +#include "../helpers/TransferFunction.hpp" #include "math/Math.hpp" #include "../protocols/ColorManagement.hpp" #include "../Compositor.hpp" @@ -22,16 +23,20 @@ #include "../protocols/core/DataDevice.hpp" #include "../render/Renderer.hpp" #include "../managers/EventManager.hpp" -#include "../managers/LayoutManager.hpp" +#include "../managers/screenshare/ScreenshareManager.hpp" #include "../managers/animation/AnimationManager.hpp" #include "../managers/animation/DesktopAnimationManager.hpp" #include "../managers/input/InputManager.hpp" #include "../hyprerror/HyprError.hpp" +#include "../layout/LayoutManager.hpp" #include "../i18n/Engine.hpp" +#include "../helpers/cm/ColorManagement.hpp" #include "sync/SyncTimeline.hpp" #include "time/Time.hpp" #include "../desktop/view/LayerSurface.hpp" #include "../desktop/state/FocusState.hpp" +#include "../event/EventBus.hpp" +#include "Drm.hpp" #include #include "debug/log/Logger.hpp" #include "debug/HyprNotificationOverlay.hpp" @@ -50,7 +55,7 @@ using namespace Hyprutils::OS; using enum NContentType::eContentType; using namespace NColorManagement; -CMonitor::CMonitor(SP output_) : m_state(this), m_output(output_) { +CMonitor::CMonitor(SP output_) : m_state(this), m_output(output_), m_imageDescription(DEFAULT_IMAGE_DESCRIPTION) { g_pAnimationManager->createAnimation(0.f, m_specialFade, g_pConfigManager->getAnimationPropertyConfig("specialWorkspaceIn"), AVARDAMAGE_NONE); m_specialFade->setUpdateCallback([this](auto) { g_pHyprRenderer->damageMonitor(m_self.lock()); }); static auto PZOOMFACTOR = CConfigValue("cursor:zoom_factor"); @@ -71,23 +76,27 @@ CMonitor::~CMonitor() { } void CMonitor::onConnect(bool noRule) { - EMIT_HOOK_EVENT("preMonitorAdded", m_self.lock()); + Event::bus()->m_events.monitor.preAdded.emit(m_self.lock()); CScopeGuard x = {[]() { g_pCompositor->arrangeMonitors(); }}; m_zoomAnimProgress->setValueAndWarp(0.F); m_zoomAnimFrameCounter = 0; - g_pEventLoopManager->doLater([] { g_pConfigManager->ensurePersistentWorkspacesPresent(); }); + g_pEventLoopManager->doLater([] { + g_pConfigManager->ensurePersistentWorkspacesPresent(); + g_pCompositor->ensureWorkspacesOnAssignedMonitors(); + }); m_listeners.frame = m_output->events.frame.listen([this] { if (m_frameScheduler) m_frameScheduler->onFrame(); }); m_listeners.commit = m_output->events.commit.listen([this] { - if (true) { // FIXME: E->state->committed & WLR_OUTPUT_STATE_BUFFER - PROTO::screencopy->onOutputCommit(m_self.lock()); - PROTO::toplevelExport->onOutputCommit(m_self.lock()); - } + m_events.commit.emit(); + + // FIXME: E->state->committed & WLR_OUTPUT_STATE_BUFFER + if (true && Screenshare::mgr()) + Screenshare::mgr()->onOutputCommit(m_self.lock()); }); m_listeners.needsFrame = m_output->events.needsFrame.listen([this] { g_pCompositor->scheduleFrameForMonitor(m_self.lock(), Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME); }); @@ -114,10 +123,12 @@ void CMonitor::onConnect(bool noRule) { ts = nullptr; } - if (!ts) - PROTO::presentation->onPresented(m_self.lock(), Time::steadyNow(), event.refresh, event.seq, event.flags); - else - PROTO::presentation->onPresented(m_self.lock(), Time::fromTimespec(event.when), event.refresh, event.seq, event.flags); + if (!ts) { + timespec mono{}; + clock_gettime(CLOCK_MONOTONIC, &mono); + PROTO::presentation->onPresented(m_self.lock(), mono, event.refresh, event.seq, event.flags & ~Aquamarine::IOutput::AQ_OUTPUT_PRESENT_HW_CLOCK); + } else + PROTO::presentation->onPresented(m_self.lock(), *ts, event.refresh, event.seq, event.flags); if (m_zoomAnimFrameCounter < 5) { m_zoomAnimFrameCounter++; @@ -282,10 +293,16 @@ void CMonitor::onConnect(bool noRule) { if (!valid(ws)) continue; - if (ws->m_lastMonitor == m_name || g_pCompositor->m_monitors.size() == 1 /* avoid lost workspaces on recover */) { + const auto CURRENTMON = ws->m_monitor.lock(); + const bool ORPHANED = !CURRENTMON || std::ranges::none_of(g_pCompositor->m_monitors, [&](const auto& mon) { return mon == CURRENTMON; }); + const bool RETURNING = ws->m_lastMonitor == m_name; + const bool RECOVERY = g_pCompositor->m_monitors.size() == 1 && ORPHANED; // temporarily recover orphaned workspaces + + if (RETURNING || RECOVERY) { g_pCompositor->moveWorkspaceToMonitor(ws, m_self.lock()); g_pDesktopAnimationManager->startAnimation(ws, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); - ws->m_lastMonitor = ""; + if (RETURNING) + ws->m_lastMonitor = ""; } } @@ -303,7 +320,7 @@ void CMonitor::onConnect(bool noRule) { Desktop::focusState()->rawMonitorFocus(m_self.lock()); g_pHyprRenderer->arrangeLayersForMonitor(m_id); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id); + g_layoutManager->recalculateMonitor(m_self.lock()); // ensure VRR (will enable if necessary) g_pConfigManager->ensureVRR(m_self.lock()); @@ -341,17 +358,17 @@ void CMonitor::onConnect(bool noRule) { g_pEventManager->postEvent(SHyprIPCEvent{"monitoradded", m_name}); g_pEventManager->postEvent(SHyprIPCEvent{"monitoraddedv2", std::format("{},{},{}", m_id, m_name, m_shortDescription)}); - EMIT_HOOK_EVENT("monitorAdded", m_self.lock()); + Event::bus()->m_events.monitor.added.emit(m_self.lock()); } void CMonitor::onDisconnect(bool destroy) { - EMIT_HOOK_EVENT("preMonitorRemoved", m_self.lock()); + Event::bus()->m_events.monitor.preRemoved.emit(m_self.lock()); CScopeGuard x = {[this]() { if (g_pCompositor->m_isShuttingDown) return; g_pEventManager->postEvent(SHyprIPCEvent{"monitorremoved", m_name}); g_pEventManager->postEvent(SHyprIPCEvent{"monitorremovedv2", std::format("{},{},{}", m_id, m_name, m_shortDescription)}); - EMIT_HOOK_EVENT("monitorRemoved", m_self.lock()); + Event::bus()->m_events.monitor.removed.emit(m_self.lock()); g_pCompositor->scheduleMonitorStateRecheck(); }}; @@ -411,7 +428,6 @@ void CMonitor::onDisconnect(bool destroy) { m_layerSurfaceLayers[i].clear(); } - std::erase_if(g_pCompositor->m_monitors, [&](PHLMONITOR& el) { return el.get() == this; }); Log::logger->log(Log::DEBUG, "Removed monitor {}!", m_name); if (!BACKUPMON) { @@ -422,19 +438,24 @@ void CMonitor::onDisconnect(bool destroy) { m_enabled = false; m_renderingInitPassed = false; + std::vector wspToMove; + for (auto const& w : g_pCompositor->getWorkspaces()) { + if (w->m_monitor == m_self || !w->m_monitor) + wspToMove.emplace_back(w.lock()); + } + + // Preserve ownership across cascaded monitor disconnects. + // The first disconnected monitor "owns" where a workspace should return. + for (auto const& w : wspToMove) { + if (w && w->m_lastMonitor.empty()) + w->m_lastMonitor = m_name; + } + if (BACKUPMON) { // snap cursor g_pCompositor->warpCursorTo(BACKUPMON->m_position + BACKUPMON->m_transformedSize / 2.F, true); - // move workspaces - std::vector wspToMove; - for (auto const& w : g_pCompositor->getWorkspaces()) { - if (w->m_monitor == m_self || !w->m_monitor) - wspToMove.emplace_back(w.lock()); - } - for (auto const& w : wspToMove) { - w->m_lastMonitor = m_name; g_pCompositor->moveWorkspaceToMonitor(w, BACKUPMON); g_pDesktopAnimationManager->startAnimation(w, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); } @@ -463,7 +484,7 @@ void CMonitor::onDisconnect(bool destroy) { PHLMONITOR pMonitorMostHz = nullptr; for (auto const& m : g_pCompositor->m_monitors) { - if (m->m_refreshRate > mostHz) { + if (m->m_refreshRate > mostHz && m != m_self) { pMonitorMostHz = m; mostHz = m->m_refreshRate; } @@ -471,78 +492,118 @@ void CMonitor::onDisconnect(bool destroy) { g_pHyprRenderer->m_mostHzMonitor = pMonitorMostHz; } + + std::erase_if(g_pCompositor->m_monitors, [&](PHLMONITOR& el) { return el.get() == this; }); } -void CMonitor::applyCMType(NCMType::eCMType cmType, int cmSdrEotf) { - auto oldImageDescription = m_imageDescription; - static auto PSDREOTF = CConfigValue("render:cm_sdr_eotf"); - auto chosenSdrEotf = cmSdrEotf == 0 ? (*PSDREOTF != 3 ? NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22 : NColorManagement::CM_TRANSFER_FUNCTION_SRGB) : - (cmSdrEotf == 1 ? NColorManagement::CM_TRANSFER_FUNCTION_SRGB : NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22); +static NColorManagement::eTransferFunction chooseTF(NTransferFunction::eTF tf) { + const auto sdrEOTF = NTransferFunction::fromConfig(); + + switch (tf) { + case NTransferFunction::TF_DEFAULT: + case NTransferFunction::TF_GAMMA22: + case NTransferFunction::TF_FORCED_GAMMA22: return NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22; + case NTransferFunction::TF_SRGB: return NColorManagement::CM_TRANSFER_FUNCTION_SRGB; + + case NTransferFunction::TF_AUTO: // use global setting + switch (sdrEOTF) { + case NTransferFunction::TF_AUTO: return NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22; + default: return chooseTF(sdrEOTF); + } + + default: UNREACHABLE(); + } +} + +void CMonitor::applyCMType(NCMType::eCMType cmType, NTransferFunction::eTF cmSdrEotf) { + auto oldImageDescription = m_imageDescription; + const auto chosenSdrEotf = chooseTF(cmSdrEotf); + + const auto masteringPrimaries = getMasteringPrimaries(); + const NColorManagement::SImageDescription::SPCMasteringLuminances masteringLuminances = getMasteringLuminances(); + + const auto maxFALL = this->maxFALL(); + const auto maxCLL = this->maxCLL(); switch (cmType) { - case NCMType::CM_SRGB: m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf}); break; // assumes SImageDescription defaults to sRGB - case NCMType::CM_WIDE: + case NCMType::CM_SRGB: m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf, - .primariesNameSet = true, - .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, - .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020)}); + .primariesNamed = NColorManagement::CM_PRIMARIES_SRGB, + .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_SRGB)}); + break; // assumes SImageDescription defaults to sRGB + case NCMType::CM_WIDE: + m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf, + .primariesNameSet = true, + .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, + .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020), + .masteringPrimaries = masteringPrimaries, + .masteringLuminances = masteringLuminances, + .maxCLL = maxCLL, + .maxFALL = maxFALL}); break; case NCMType::CM_DCIP3: - m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf, - .primariesNameSet = true, - .primariesNamed = NColorManagement::CM_PRIMARIES_DCI_P3, - .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_DCI_P3)}); + m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf, + .primariesNameSet = true, + .primariesNamed = NColorManagement::CM_PRIMARIES_DCI_P3, + .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_DCI_P3), + .masteringPrimaries = masteringPrimaries, + .masteringLuminances = masteringLuminances, + .maxCLL = maxCLL, + .maxFALL = maxFALL}); break; case NCMType::CM_DP3: - m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf, - .primariesNameSet = true, - .primariesNamed = NColorManagement::CM_PRIMARIES_DISPLAY_P3, - .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_DISPLAY_P3)}); + m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf, + .primariesNameSet = true, + .primariesNamed = NColorManagement::CM_PRIMARIES_DISPLAY_P3, + .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_DISPLAY_P3), + .masteringPrimaries = masteringPrimaries, + .masteringLuminances = masteringLuminances, + .maxCLL = maxCLL, + .maxFALL = maxFALL}); break; case NCMType::CM_ADOBE: - m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf, - .primariesNameSet = true, - .primariesNamed = NColorManagement::CM_PRIMARIES_ADOBE_RGB, - .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_ADOBE_RGB)}); + m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf, + .primariesNameSet = true, + .primariesNamed = NColorManagement::CM_PRIMARIES_ADOBE_RGB, + .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_ADOBE_RGB), + .masteringPrimaries = masteringPrimaries, + .masteringLuminances = masteringLuminances, + .maxCLL = maxCLL, + .maxFALL = maxFALL}); break; case NCMType::CM_EDID: - m_imageDescription = - CImageDescription::from({.transferFunction = chosenSdrEotf, - .primariesNameSet = false, - .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, - .primaries = { - .red = {.x = m_output->parsedEDID.chromaticityCoords->red.x, .y = m_output->parsedEDID.chromaticityCoords->red.y}, - .green = {.x = m_output->parsedEDID.chromaticityCoords->green.x, .y = m_output->parsedEDID.chromaticityCoords->green.y}, - .blue = {.x = m_output->parsedEDID.chromaticityCoords->blue.x, .y = m_output->parsedEDID.chromaticityCoords->blue.y}, - .white = {.x = m_output->parsedEDID.chromaticityCoords->white.x, .y = m_output->parsedEDID.chromaticityCoords->white.y}, - }}); + m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf, + .primariesNameSet = false, + .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, + .primaries = masteringPrimaries, + .masteringPrimaries = masteringPrimaries, + .masteringLuminances = masteringLuminances, + .maxCLL = maxCLL, + .maxFALL = maxFALL}); break; case NCMType::CM_HDR: m_imageDescription = DEFAULT_HDR_IMAGE_DESCRIPTION; break; case NCMType::CM_HDR_EDID: - m_imageDescription = - CImageDescription::from({.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ, - .primariesNameSet = false, - .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, - .primaries = m_output->parsedEDID.chromaticityCoords.has_value() ? - NColorManagement::SPCPRimaries{ - .red = {.x = m_output->parsedEDID.chromaticityCoords->red.x, .y = m_output->parsedEDID.chromaticityCoords->red.y}, - .green = {.x = m_output->parsedEDID.chromaticityCoords->green.x, .y = m_output->parsedEDID.chromaticityCoords->green.y}, - .blue = {.x = m_output->parsedEDID.chromaticityCoords->blue.x, .y = m_output->parsedEDID.chromaticityCoords->blue.y}, - .white = {.x = m_output->parsedEDID.chromaticityCoords->white.x, .y = m_output->parsedEDID.chromaticityCoords->white.y}, - } : - NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020), - .luminances = {.min = m_output->parsedEDID.hdrMetadata->desiredContentMinLuminance, - .max = m_output->parsedEDID.hdrMetadata->desiredContentMaxLuminance, - .reference = m_output->parsedEDID.hdrMetadata->desiredMaxFrameAverageLuminance}}); + m_imageDescription = CImageDescription::from( + {.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ, + .primariesNameSet = false, + .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, + .primaries = m_output->parsedEDID.chromaticityCoords.has_value() ? masteringPrimaries : NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020), + .masteringPrimaries = masteringPrimaries, + .luminances = {.min = DEFAULT_HDR_IMAGE_DESCRIPTION->value().getTFMinLuminance(), + .max = DEFAULT_HDR_IMAGE_DESCRIPTION->value().getTFMaxLuminance(), + .reference = DEFAULT_HDR_IMAGE_DESCRIPTION->value().getTFRefLuminance()}, + .masteringLuminances = masteringLuminances, + .maxCLL = maxCLL, + .maxFALL = maxFALL}); break; default: UNREACHABLE(); } - if (m_minLuminance >= 0 || m_maxLuminance >= 0 || m_maxAvgLuminance >= 0) + if ((m_minLuminance >= 0 || m_maxLuminance >= 0 || m_maxAvgLuminance >= 0) && (cmType == NCMType::CM_HDR || cmType == NCMType::CM_HDR_EDID)) m_imageDescription = m_imageDescription->with({ - .min = m_minLuminance >= 0 ? m_minLuminance : m_imageDescription->value().luminances.min, // - .max = m_maxLuminance >= 0 ? m_maxLuminance : m_imageDescription->value().luminances.max, // - .reference = m_maxAvgLuminance >= 0 ? m_maxAvgLuminance : m_imageDescription->value().luminances.reference // + .min = m_minLuminance >= 0 ? m_minLuminance : m_imageDescription->value().luminances.min, // + .max = m_maxLuminance >= 0 ? m_maxLuminance : m_imageDescription->value().luminances.max, // + .reference = m_imageDescription->value().luminances.reference // }); if (oldImageDescription != m_imageDescription) { @@ -872,29 +933,46 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { m_supportsWideColor = RULE->supportsHDR; m_supportsHDR = RULE->supportsHDR; - m_cmType = RULE->cmType; - switch (m_cmType) { - case NCMType::CM_AUTO: m_cmType = m_enabled10bit && supportsWideColor() ? NCMType::CM_WIDE : NCMType::CM_SRGB; break; - case NCMType::CM_EDID: m_cmType = m_output->parsedEDID.chromaticityCoords.has_value() ? NCMType::CM_EDID : NCMType::CM_SRGB; break; - case NCMType::CM_HDR: - case NCMType::CM_HDR_EDID: m_cmType = supportsHDR() ? m_cmType : NCMType::CM_SRGB; break; - default: break; + if (RULE->iccFile.empty()) { + // only apply explicit cm settings if we have no icc file + + m_cmType = RULE->cmType; + switch (m_cmType) { + case NCMType::CM_AUTO: m_cmType = m_enabled10bit && supportsWideColor() ? NCMType::CM_WIDE : NCMType::CM_SRGB; break; + case NCMType::CM_EDID: m_cmType = m_output->parsedEDID.chromaticityCoords.has_value() ? NCMType::CM_EDID : NCMType::CM_SRGB; break; + case NCMType::CM_HDR: + case NCMType::CM_HDR_EDID: m_cmType = supportsHDR() ? m_cmType : NCMType::CM_SRGB; break; + default: break; + } + + m_sdrEotf = RULE->sdrEotf; + + m_sdrMinLuminance = RULE->sdrMinLuminance; + m_sdrMaxLuminance = RULE->sdrMaxLuminance; + + m_minLuminance = RULE->minLuminance; + m_maxLuminance = RULE->maxLuminance; + m_maxAvgLuminance = RULE->maxAvgLuminance; + + applyCMType(m_cmType, m_sdrEotf); + + m_sdrSaturation = RULE->sdrSaturation; + m_sdrBrightness = RULE->sdrBrightness; + } else { + auto image = NColorManagement::SImageDescription::fromICC(RULE->iccFile); + if (!image) { + Log::logger->log(Log::ERR, "icc for {} ({}) failed: {}", m_name, RULE->iccFile, image.error()); + g_pConfigManager->addParseError(std::format("failed to apply icc {} to {}: {}", RULE->iccFile, m_name, image.error())); + } else { + m_imageDescription = CImageDescription::from(*image); + if (!m_imageDescription) { + Log::logger->log(Log::ERR, "icc for {} ({}) failed 2: {}", m_name, RULE->iccFile, image.error()); + g_pConfigManager->addParseError(std::format("failed to apply icc {} to {}: {}", RULE->iccFile, m_name, image.error())); + m_imageDescription = CImageDescription::from(SImageDescription{}); + } + } } - m_sdrEotf = RULE->sdrEotf; - - m_sdrMinLuminance = RULE->sdrMinLuminance; - m_sdrMaxLuminance = RULE->sdrMaxLuminance; - - m_minLuminance = RULE->minLuminance; - m_maxLuminance = RULE->maxLuminance; - m_maxAvgLuminance = RULE->maxAvgLuminance; - - applyCMType(m_cmType, m_sdrEotf); - - m_sdrSaturation = RULE->sdrSaturation; - m_sdrBrightness = RULE->sdrBrightness; - Vector2D logicalSize = m_pixelSize / m_scale; if (!*PDISABLESCALECHECKS && (logicalSize.x != std::round(logicalSize.x) || logicalSize.y != std::round(logicalSize.y))) { // invalid scale, will produce fractional pixels. @@ -976,6 +1054,8 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { m_damage.setSize(m_transformedSize); + updateVCGTRamps(); + // Set scale for all surfaces on this monitor, needed for some clients // but not on unsafe state to avoid crashes if (!g_pCompositor->m_unsafeState) { @@ -992,7 +1072,7 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { Log::logger->log(Log::DEBUG, "Monitor {} data dump: res {:X}@{:.2f}Hz, scale {:.2f}, transform {}, pos {:X}, 10b {}", m_name, m_pixelSize, m_refreshRate, m_scale, sc(m_transform), m_position, sc(m_enabled10bit)); - EMIT_HOOK_EVENT("monitorLayoutChanged", nullptr); + Event::bus()->m_events.monitor.layoutChanged.emit(); m_events.modeChanged.emit(); @@ -1026,8 +1106,10 @@ bool CMonitor::shouldSkipScheduleFrameOnMouseEvent() { static auto PMINRR = CConfigValue("cursor:min_refresh_rate"); // skip scheduling extra frames for fullsreen apps with vrr - const bool shouldSkip = inFullscreenMode() && (*PNOBREAK == 1 || (*PNOBREAK == 2 && m_activeWorkspace->getFullscreenWindow()->getContentType() == CONTENT_TYPE_GAME)) && - m_output->state->state().adaptiveSync; + const auto FS_WINDOW = getFullscreenWindow(); + const bool shouldRenderCursor = g_pHyprRenderer->shouldRenderCursor(); + const bool noBreak = FS_WINDOW && (*PNOBREAK == 1 || (*PNOBREAK == 2 && FS_WINDOW->getContentType() == CONTENT_TYPE_GAME)); + const bool shouldSkip = (!shouldRenderCursor || noBreak) && m_output->state->state().adaptiveSync; // keep requested minimum refresh rate if (shouldSkip && *PMINRR && m_lastPresentationTimer.getMillis() > 1000.0f / *PMINRR) { @@ -1096,7 +1178,7 @@ void CMonitor::setupDefaultWS(const SMonitorRule& monitorRule) { // workspace exists, move it to the newly connected monitor g_pCompositor->moveWorkspaceToMonitor(PNEWWORKSPACE, m_self.lock()); m_activeWorkspace = PNEWWORKSPACE; - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id); + g_layoutManager->recalculateMonitor(m_self.lock()); g_pDesktopAnimationManager->startAnimation(PNEWWORKSPACE, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); } else { if (newDefaultWorkspaceName.empty()) @@ -1280,7 +1362,7 @@ void CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bo // move pinned windows for (auto const& w : g_pCompositor->m_windows) { if (w->m_workspace == POLDWORKSPACE && w->m_pinned) - w->moveToWorkspace(pWorkspace); + w->layoutTarget()->assignToSpace(pWorkspace->m_space); } if (!noFocus && !Desktop::focusState()->monitor()->m_activeSpecialWorkspace && @@ -1300,17 +1382,17 @@ void CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bo pWindow = pWorkspace->getFirstWindow(); } - Desktop::focusState()->fullWindowFocus(pWindow); + Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_KEYBIND); } if (!noMouseMove) g_pInputManager->simulateMouseMovement(); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id); + g_layoutManager->recalculateMonitor(m_self.lock()); g_pEventManager->postEvent(SHyprIPCEvent{"workspace", pWorkspace->m_name}); g_pEventManager->postEvent(SHyprIPCEvent{"workspacev2", std::format("{},{}", pWorkspace->m_id, pWorkspace->m_name)}); - EMIT_HOOK_EVENT("workspace", pWorkspace); + Event::bus()->m_events.workspace.active.emit(pWorkspace); } // set all LSes as not above fullscreen on workspace changes @@ -1357,17 +1439,23 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) { g_pDesktopAnimationManager->startAnimation(m_activeSpecialWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_OUT, false); g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + m_name}); g_pEventManager->postEvent(SHyprIPCEvent{"activespecialv2", ",," + m_name}); + + // Reset layer surface state when closing special workspace + for (auto const& ls : g_pCompositor->m_layers) { + if (ls->m_monitor == m_self) + ls->m_aboveFullscreen = false; + } } m_activeSpecialWorkspace.reset(); if (POLDSPECIAL) POLDSPECIAL->m_events.activeChanged.emit(); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id); + g_layoutManager->recalculateMonitor(m_self.lock()); if (!(Desktop::focusState()->window() && Desktop::focusState()->window()->m_pinned && Desktop::focusState()->window()->m_monitor == m_self)) { if (const auto PLAST = m_activeWorkspace->getLastFocusedWindow(); PLAST) - Desktop::focusState()->fullWindowFocus(PLAST); + Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_KEYBIND); else g_pInputManager->refocus(); } @@ -1392,10 +1480,17 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) { const auto PMONITORWORKSPACEOWNER = pWorkspace->m_monitor.lock(); if (const auto PMWSOWNER = pWorkspace->m_monitor.lock(); PMWSOWNER && PMWSOWNER->m_activeSpecialWorkspace == pWorkspace) { PMWSOWNER->m_activeSpecialWorkspace.reset(); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PMWSOWNER->m_id); + g_layoutManager->recalculateMonitor(PMWSOWNER); + g_pHyprRenderer->damageMonitor(PMWSOWNER); g_pEventManager->postEvent(SHyprIPCEvent{"activespecial", "," + PMWSOWNER->m_name}); g_pEventManager->postEvent(SHyprIPCEvent{"activespecialv2", ",," + PMWSOWNER->m_name}); + // Reset layer surfaces on the old monitor when special workspace is stolen + for (auto const& ls : g_pCompositor->m_layers) { + if (ls->m_monitor == PMWSOWNER) + ls->m_aboveFullscreen = false; + } + const auto PACTIVEWORKSPACE = PMWSOWNER->m_activeWorkspace; g_pDesktopAnimationManager->setFullscreenFadeAnimation(PACTIVEWORKSPACE, PACTIVEWORKSPACE && PACTIVEWORKSPACE->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : @@ -1409,6 +1504,12 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) { m_activeSpecialWorkspace = pWorkspace; m_activeSpecialWorkspace->m_visible = true; + // Reset layer surface state when opening special workspace + for (auto const& ls : g_pCompositor->m_layers) { + if (ls->m_monitor == m_self) + ls->m_aboveFullscreen = false; + } + if (POLDSPECIAL) POLDSPECIAL->m_events.activeChanged.emit(); @@ -1439,17 +1540,16 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) { } else pos = pos - PMONFROMMIDDLE->m_position + m_position; - *w->m_realPosition = pos; - w->m_position = pos; + w->layoutTarget()->setPositionGlobal(CBox{pos, w->layoutTarget()->position().size()}); } } } - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id); + g_layoutManager->recalculateMonitor(m_self.lock()); if (!(Desktop::focusState()->window() && Desktop::focusState()->window()->m_pinned && Desktop::focusState()->window()->m_monitor == m_self)) { if (const auto PLAST = pWorkspace->getLastFocusedWindow(); PLAST) - Desktop::focusState()->fullWindowFocus(PLAST); + Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_KEYBIND); else g_pInputManager->refocus(); } @@ -1479,10 +1579,20 @@ Vector2D CMonitor::middle() { return m_position + m_size / 2.f; } +const Mat3x3& CMonitor::getTransformMatrix() { + return m_projMatrix; +} + +const Mat3x3& CMonitor::getScaleMatrix() { + return m_projOutputMatrix; +} + void CMonitor::updateMatrix() { m_projMatrix = Mat3x3::identity(); if (m_transform != WL_OUTPUT_TRANSFORM_NORMAL) m_projMatrix.translate(m_pixelSize / 2.0).transform(Math::wlTransformToHyprutils(m_transform)).translate(-m_transformedSize / 2.0); + + m_projOutputMatrix = Mat3x3::outputProjection(m_pixelSize, HYPRUTILS_TRANSFORM_NORMAL); } WORKSPACEID CMonitor::activeWorkspaceID() { @@ -1686,6 +1796,10 @@ uint8_t CMonitor::isTearingBlocked(bool full) { } } + // TODO: remove this when kernel allows tearing + hw cursor updated + if (g_pPointerManager->hasVisibleHWCursor(m_self.lock())) + reasons |= TC_HW_CURSOR; + if (m_solitaryClient.expired()) { reasons |= TC_CANDIDATE; return reasons; @@ -1727,12 +1841,6 @@ uint16_t CMonitor::isDSBlocked(bool full) { } } - if (m_tearingState.activelyTearing) { - reasons |= DS_BLOCK_TEARING; - if (!full) - return reasons; - } - if (!m_mirrors.empty() || isMirror()) { reasons |= DS_BLOCK_MIRROR; if (!full) @@ -1771,21 +1879,27 @@ uint16_t CMonitor::isDSBlocked(bool full) { // we can't scanout shm buffers. const auto params = PSURFACE->m_current.buffer->dmabuf(); - if (!params.success || !PSURFACE->m_current.texture->m_eglImage /* dmabuf */) { + if (!params.success || !PSURFACE->m_current.texture->isDMA() /* dmabuf */) { reasons |= DS_BLOCK_DMA; if (!full) return reasons; } - const bool surfaceIsHDR = PSURFACE->m_colorManagement.valid() && (PSURFACE->m_colorManagement->isHDR() || PSURFACE->m_colorManagement->isWindowsScRGB()); - if (needsCM() && *PNONSHADER != CM_NS_IGNORE && !canNoShaderCM() && ((inHDR() && (*PPASS == 0 || !surfaceIsHDR)) || (!inHDR() && (*PPASS != 1 || surfaceIsHDR)))) + const bool surfaceIsHDR = PSURFACE->m_colorManagement.valid() && PSURFACE->m_colorManagement->isHDR(); + const bool surfaceIsScRGB = surfaceIsHDR && PSURFACE->m_colorManagement->isWindowsScRGB(); + + if (needsCM() && (*PNONSHADER != CM_NS_IGNORE || surfaceIsScRGB) && !canNoShaderCM() && + ((inHDR() && (*PPASS == 0 || !surfaceIsHDR || surfaceIsScRGB)) || (!inHDR() && (*PPASS != 1 || surfaceIsHDR)))) reasons |= DS_BLOCK_CM; return reasons; } bool CMonitor::attemptDirectScanout() { - const auto blockedReason = isDSBlocked(); + static const auto PSAME = CConfigValue("debug:ds_handle_same_buffer"); + static const auto PSAMEFIFO = CConfigValue("debug:ds_handle_same_buffer_fifo"); + + const auto blockedReason = isDSBlocked(); if (blockedReason) return false; @@ -1799,7 +1913,7 @@ bool CMonitor::attemptDirectScanout() { auto PBUFFER = PSURFACE->m_current.buffer.m_buffer; // #TODO this entire bit needs figuring out, vrr goes down the drain without it - if (PBUFFER == m_output->state->state().buffer) { + if (PBUFFER == m_output->state->state().buffer && *PSAME) { PSURFACE->presentFeedback(Time::steadyNow(), m_self.lock()); if (m_scanoutNeedsCursorUpdate) { @@ -1818,7 +1932,7 @@ bool CMonitor::attemptDirectScanout() { } //#TODO this entire bit is bootleg deluxe, above bit is to not make vrr go down the drain, returning early here means fifo gets forever locked. - if (PSURFACE->m_fifo) + if (PSURFACE->m_fifo && !m_tearingState.activelyTearing && *PSAMEFIFO) PSURFACE->m_stateQueue.unlockFirst(LOCK_REASON_FIFO); return true; @@ -1849,7 +1963,18 @@ bool CMonitor::attemptDirectScanout() { PSURFACE->presentFeedback(Time::steadyNow(), m_self.lock()); m_output->state->addDamage(PSURFACE->m_current.accumulateBufferDamage()); - m_output->state->resetExplicitFences(); + + // multigpu needs a fence to trigger fence syncing blits and also committing with the recreated dgpu fence + if (!DRM::sameGpu(m_output->getBackend()->preferredAllocator()->drmFD(), g_pCompositor->m_drm.fd) && g_pHyprOpenGL->explicitSyncSupported()) { + auto sync = CEGLSync::create(); + + if (sync->fd().isValid()) { + m_inFence = sync->takeFd(); + m_output->state->setExplicitInFence(m_inFence.get()); + } else + m_output->state->resetExplicitFences(); // good luck. + } else + m_output->state->resetExplicitFences(); // no need to do explicit sync here as surface current can only ever be ready to read @@ -2004,6 +2129,14 @@ int CMonitor::maxAvgLuminance(int defaultValue) { (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredMaxFrameAverageLuminance : defaultValue); } +float CMonitor::maxFALL() { + return m_maxAvgLuminance >= 0 ? m_maxAvgLuminance : (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredMaxFrameAverageLuminance : 0); +} + +float CMonitor::maxCLL() { + return m_maxLuminance >= 0 ? m_maxLuminance : (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredContentMaxLuminance : 0); +} + bool CMonitor::wantsWideColor() { return supportsWideColor() && (wantsHDR() || m_imageDescription->value().primariesNamed == CM_PRIMARIES_BT2020); } @@ -2017,22 +2150,52 @@ bool CMonitor::inHDR() { } bool CMonitor::inFullscreenMode() { + // Check special workspace first since it renders on top of regular workspaces + if (m_activeSpecialWorkspace && m_activeSpecialWorkspace->m_hasFullscreenWindow && m_activeSpecialWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN) + return true; return m_activeWorkspace && m_activeWorkspace->m_hasFullscreenWindow && m_activeWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN; } +PHLWINDOW CMonitor::getFullscreenWindow() { + // Check special workspace first since it renders on top of regular workspaces + if (m_activeSpecialWorkspace && m_activeSpecialWorkspace->m_hasFullscreenWindow && m_activeSpecialWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN) + return m_activeSpecialWorkspace->getFullscreenWindow(); + if (m_activeWorkspace && m_activeWorkspace->m_hasFullscreenWindow && m_activeWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN) + return m_activeWorkspace->getFullscreenWindow(); + return nullptr; +} + std::optional CMonitor::getFSImageDescription() { if (!inFullscreenMode()) return {}; - const auto FS_WINDOW = m_activeWorkspace->getFullscreenWindow(); + const auto FS_WINDOW = getFullscreenWindow(); if (!FS_WINDOW) - return {}; // should be unreachable + return {}; const auto ROOT_SURF = FS_WINDOW->wlSurface()->resource(); const auto SURF = ROOT_SURF->findWithCM(); return SURF ? NColorManagement::CImageDescription::from(SURF->m_colorManagement->imageDescription()) : DEFAULT_IMAGE_DESCRIPTION; } +NColorManagement::SPCPRimaries CMonitor::getMasteringPrimaries() { + return m_output->parsedEDID.chromaticityCoords.has_value() ? + NColorManagement::SPCPRimaries{ + .red = {.x = m_output->parsedEDID.chromaticityCoords->red.x, .y = m_output->parsedEDID.chromaticityCoords->red.y}, + .green = {.x = m_output->parsedEDID.chromaticityCoords->green.x, .y = m_output->parsedEDID.chromaticityCoords->green.y}, + .blue = {.x = m_output->parsedEDID.chromaticityCoords->blue.x, .y = m_output->parsedEDID.chromaticityCoords->blue.y}, + .white = {.x = m_output->parsedEDID.chromaticityCoords->white.x, .y = m_output->parsedEDID.chromaticityCoords->white.y}, + } : + NColorManagement::SPCPRimaries{}; +} + +NColorManagement::SImageDescription::SPCMasteringLuminances CMonitor::getMasteringLuminances() { + return { + .min = m_minLuminance >= 0 ? m_minLuminance : (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredContentMinLuminance : 0), + .max = m_maxLuminance >= 0 ? m_maxLuminance : (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredContentMaxLuminance : 0), + }; +} + bool CMonitor::needsCM() { const auto SRC_DESC = getFSImageDescription(); return SRC_DESC.has_value() && SRC_DESC.value() != m_imageDescription; @@ -2053,24 +2216,91 @@ bool CMonitor::canNoShaderCM() { const auto SRC_DESC_VALUE = SRC_DESC.value()->value(); - if (SRC_DESC_VALUE.icc.fd >= 0 || m_imageDescription->value().icc.fd >= 0) - return false; // no ICC support + if (m_imageDescription->value().icc.present) + return false; - static auto PSDREOTF = CConfigValue("render:cm_sdr_eotf"); + const auto sdrEOTF = NTransferFunction::fromConfig(); // only primaries differ - return ((SRC_DESC_VALUE.transferFunction == m_imageDescription->value().transferFunction || - (*PSDREOTF == 2 && SRC_DESC_VALUE.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_SRGB && - m_imageDescription->value().transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22)) && - SRC_DESC_VALUE.transferFunctionPower == m_imageDescription->value().transferFunctionPower && - (!inHDR() || SRC_DESC_VALUE.luminances == m_imageDescription->value().luminances) && - SRC_DESC_VALUE.masteringLuminances == m_imageDescription->value().masteringLuminances && SRC_DESC_VALUE.maxCLL == m_imageDescription->value().maxCLL && - SRC_DESC_VALUE.maxFALL == m_imageDescription->value().maxFALL); + return ( + (SRC_DESC_VALUE.transferFunction == m_imageDescription->value().transferFunction || + (sdrEOTF == NTransferFunction::TF_FORCED_GAMMA22 && SRC_DESC_VALUE.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_SRGB && + m_imageDescription->value().transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22)) && + SRC_DESC_VALUE.transferFunctionPower == m_imageDescription->value().transferFunctionPower && + (!inHDR() || SRC_DESC_VALUE.luminances == m_imageDescription->value().luminances) + // not used by shaders atm + // && SRC_DESC_VALUE.masteringLuminances == m_imageDescription->value().masteringLuminances && SRC_DESC_VALUE.maxCLL == m_imageDescription->value().maxCLL && SRC_DESC_VALUE.maxFALL == m_imageDescription->value().maxFALL + ); } bool CMonitor::doesNoShaderCM() { return m_noShaderCTM; } +static std::vector resampleInterleavedToKms(const SVCGTTable16& t, size_t gammaSize) { + std::vector out; + out.resize(gammaSize * 3); + + // + auto sample = [&](int c, float x) -> uint16_t { + const float maxX = t.entries - 1; + x = std::clamp(x, 0.F, maxX); + + const size_t i0 = (size_t)std::floor(x); + const size_t i1 = std::min(i0 + 1, (size_t)t.entries - 1); + const float f = x - sc(i0); + + const float v0 = sc(t.ch[c][i0]); + const float v1 = sc(t.ch[c][i1]); + const float v = v0 + ((v1 - v0) * f); + + int64_t vi = std::round(v); + vi = std::clamp(vi, sc(0), sc(65535)); + return sc(vi); + }; + + for (size_t i = 0; i < gammaSize; ++i) { + float x = sc(i) * sc(t.entries - 1) / sc(gammaSize - 1); + + const uint16_t r = sample(0, x); + const uint16_t g = sample(1, x); + const uint16_t b = sample(2, x); + + out[i * 3 + 0] = r; + out[i * 3 + 1] = g; + out[i * 3 + 2] = b; + } + + return out; +} + +void CMonitor::updateVCGTRamps() { + auto gammaSize = m_output->getGammaSize(); + + if (gammaSize <= 10) { + Log::logger->log(Log::DEBUG, "CMonitor::updateVCGTRamps: skipping, no gamma ramp for output"); + return; + } + + if (!m_imageDescription->value().icc.vcgt) { + if (m_vcgtRampsSet) + m_output->state->setGammaLut({}); + + m_vcgtRampsSet = false; + return; + } + + // build table + auto table = resampleInterleavedToKms(*m_imageDescription->value().icc.vcgt, gammaSize); + + m_output->state->setGammaLut(table); + + m_vcgtRampsSet = true; +} + +bool CMonitor::gammaRampsInUse() { + return m_vcgtRampsSet; +} + CMonitorState::CMonitorState(CMonitor* owner) : m_owner(owner) { ; } @@ -2098,7 +2328,7 @@ bool CMonitorState::commit() { if (!updateSwapchain()) return false; - EMIT_HOOK_EVENT("preMonitorCommit", m_owner->m_self.lock()); + Event::bus()->m_events.monitor.preCommit.emit(m_owner->m_self.lock()); ensureBufferPresent(); diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index 98d672e6..7467467a 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -16,13 +16,15 @@ #include "math/Math.hpp" #include "../desktop/reserved/ReservedArea.hpp" #include -#include "../protocols/types/ColorManagement.hpp" +#include "cm/ColorManagement.hpp" #include "signal/Signal.hpp" #include "DamageRing.hpp" #include #include #include +#include "../helpers/TransferFunction.hpp" + class CMonitorFrameScheduler; // Enum for the different types of auto directions, e.g. auto-left, auto-up. @@ -50,10 +52,11 @@ struct SMonitorRule { std::string mirrorOf = ""; bool enable10bit = false; NCMType::eCMType cmType = NCMType::CM_SRGB; - int sdrEotf = 0; + NTransferFunction::eTF sdrEotf = NTransferFunction::TF_DEFAULT; float sdrSaturation = 1.0f; // SDR -> HDR float sdrBrightness = 1.0f; // SDR -> HDR Desktop::CReservedArea reservedArea; + std::string iccFile; int supportsWideColor = 0; // 0 - auto, 1 - force enable, -1 - force disable int supportsHDR = 0; // 0 - auto, 1 - force enable, -1 - force disable @@ -124,7 +127,7 @@ class CMonitor { bool m_scheduledRecalc = false; wl_output_transform m_transform = WL_OUTPUT_TRANSFORM_NORMAL; float m_xwaylandScale = 1.f; - Mat3x3 m_projMatrix; + std::optional m_forceSize; SP m_currentMode; SP m_cursorSwapchain; @@ -137,7 +140,7 @@ class CMonitor { bool m_vrrActive = false; // this can be TRUE even if VRR is not active in the case that this display does not support it. bool m_enabled10bit = false; // as above, this can be TRUE even if 10 bit failed. NCMType::eCMType m_cmType = NCMType::CM_SRGB; - int m_sdrEotf = 0; + NTransferFunction::eTF m_sdrEotf = NTransferFunction::TF_DEFAULT; float m_sdrSaturation = 1.0f; float m_sdrBrightness = 1.0f; float m_sdrMinLuminance = 0.2f; @@ -177,6 +180,7 @@ class CMonitor { // for direct scanout PHLWINDOWREF m_lastScanout; + bool m_directScanoutIsActive = false; // for cleanup logic. m_lastScanout.expired() can become true before the DS cleanup if client crashes/exits while DS is active. bool m_scanoutNeedsCursorUpdate = false; // for special fade/blur @@ -207,6 +211,7 @@ class CMonitor { } m_tearingState; struct { + CSignalT<> commit; CSignalT<> destroy; CSignalT<> connect; CSignalT<> disconnect; @@ -232,9 +237,8 @@ class CMonitor { DS_BLOCK_SURFACE = (1 << 8), DS_BLOCK_TRANSFORM = (1 << 9), DS_BLOCK_DMA = (1 << 10), - DS_BLOCK_TEARING = (1 << 11), - DS_BLOCK_FAILED = (1 << 12), - DS_BLOCK_CM = (1 << 13), + DS_BLOCK_FAILED = (1 << 11), + DS_BLOCK_CM = (1 << 12), DS_CHECKS_COUNT = 14, }; @@ -275,14 +279,15 @@ class CMonitor { TC_SUPPORT = (1 << 4), TC_CANDIDATE = (1 << 5), TC_WINDOW = (1 << 6), + TC_HW_CURSOR = (1 << 7), - TC_CHECKS_COUNT = 7, + TC_CHECKS_COUNT = 8, }; // methods void onConnect(bool noRule); void onDisconnect(bool destroy = false); - void applyCMType(NCMType::eCMType cmType, int cmSdrEotf); + void applyCMType(NCMType::eCMType cmType, NTransferFunction::eTF cmSdrEotf); bool applyMonitorRule(SMonitorRule* pMonitorRule, bool force = false); void addDamage(const pixman_region32_t* rg); void addDamage(const CRegion& rg); @@ -298,7 +303,6 @@ class CMonitor { void setSpecialWorkspace(const WORKSPACEID& id); void moveTo(const Vector2D& pos); Vector2D middle(); - void updateMatrix(); WORKSPACEID activeWorkspaceID(); WORKSPACEID activeSpecialWorkspaceID(); CBox logicalBox(); @@ -321,17 +325,29 @@ class CMonitor { float minLuminance(float defaultValue = 0); int maxLuminance(int defaultValue = 80); int maxAvgLuminance(int defaultValue = 80); + float maxFALL(); + float maxCLL(); bool wantsWideColor(); bool wantsHDR(); bool inHDR(); + bool gammaRampsInUse(); - /// Has an active workspace with a real fullscreen window - bool inFullscreenMode(); - std::optional getFSImageDescription(); + // + const Mat3x3& getTransformMatrix(); + const Mat3x3& getScaleMatrix(); - bool needsCM(); + /// Has an active workspace with a real fullscreen window (includes special workspace) + bool inFullscreenMode(); + /// Get fullscreen window from active or special workspace + PHLWINDOW getFullscreenWindow(); + std::optional getFSImageDescription(); + + NColorManagement::SPCPRimaries getMasteringPrimaries(); + NColorManagement::SImageDescription::SPCMasteringLuminances getMasteringLuminances(); + + bool needsCM(); /// Can do CM without shader bool canNoShaderCM(); bool doesNoShaderCM(); @@ -342,8 +358,8 @@ class CMonitor { PHLWINDOWREF m_previousFSWindow; bool m_needsHDRupdate = false; - NColorManagement::PImageDescription m_imageDescription; - bool m_noShaderCTM = false; // sets drm CTM, restore needed + NColorManagement::PImageDescription m_imageDescription = NColorManagement::CImageDescription::from(NColorManagement::SImageDescription{}); + bool m_noShaderCTM = false; // sets drm CTM, restore needed // For the list lookup @@ -351,12 +367,19 @@ class CMonitor { return m_position == rhs.m_position && m_size == rhs.m_size && m_name == rhs.m_name; } + Mat3x3 m_projMatrix; + private: + void updateMatrix(); + Mat3x3 m_projOutputMatrix; + void setupDefaultWS(const SMonitorRule&); WORKSPACEID findAvailableDefaultWS(); void commitDPMSState(bool state); + void updateVCGTRamps(); bool m_doneScheduled = false; + bool m_vcgtRampsSet = false; std::stack m_prevWorkSpaces; struct { diff --git a/src/helpers/MonitorFrameScheduler.cpp b/src/helpers/MonitorFrameScheduler.cpp index 804eec1e..648e6dec 100644 --- a/src/helpers/MonitorFrameScheduler.cpp +++ b/src/helpers/MonitorFrameScheduler.cpp @@ -11,7 +11,7 @@ CMonitorFrameScheduler::CMonitorFrameScheduler(PHLMONITOR m) : m_monitor(m) { bool CMonitorFrameScheduler::newSchedulingEnabled() { static auto PENABLENEW = CConfigValue("render:new_render_scheduling"); - return *PENABLENEW && g_pHyprOpenGL->explicitSyncSupported(); + return *PENABLENEW && g_pHyprOpenGL->explicitSyncSupported() && m_monitor && !m_monitor->m_directScanoutIsActive; } void CMonitorFrameScheduler::onSyncFired() { diff --git a/src/helpers/SdDaemon.cpp b/src/helpers/SdDaemon.cpp index d914eecf..b6c207d8 100644 --- a/src/helpers/SdDaemon.cpp +++ b/src/helpers/SdDaemon.cpp @@ -38,10 +38,10 @@ int NSystemd::sdNotify(int unsetEnvironment, const char* state) { if (!addr) return 0; - // address length must be at most this; see man 7 unix - size_t addrLen = strnlen(addr, 107); + struct sockaddr_un unixAddr = {0}; + + size_t addrLen = strnlen(addr, sizeof(unixAddr.sun_path) - 1); - struct sockaddr_un unixAddr; unixAddr.sun_family = AF_UNIX; strncpy(unixAddr.sun_path, addr, addrLen); if (unixAddr.sun_path[0] == '@') diff --git a/src/helpers/TransferFunction.cpp b/src/helpers/TransferFunction.cpp new file mode 100644 index 00000000..074f4b19 --- /dev/null +++ b/src/helpers/TransferFunction.cpp @@ -0,0 +1,38 @@ +#include "TransferFunction.hpp" +#include "../config/ConfigValue.hpp" +#include "../event/EventBus.hpp" +#include +#include +#include + +using namespace NTransferFunction; + +static std::unordered_map const table = {{"default", TF_DEFAULT}, {"0", TF_DEFAULT}, {"auto", TF_AUTO}, {"srgb", TF_SRGB}, + {"3", TF_SRGB}, {"gamma22", TF_GAMMA22}, {"1", TF_GAMMA22}, {"gamma22force", TF_FORCED_GAMMA22}, + {"2", TF_FORCED_GAMMA22}}; + +eTF NTransferFunction::fromString(const std::string tfName) { + auto it = table.find(tfName); + if (it == table.end()) + return TF_DEFAULT; + return it->second; +} + +std::string NTransferFunction::toString(eTF tf) { + for (const auto& [key, value] : table) { + if (value == tf) + return key; + } + return ""; +} + +eTF NTransferFunction::fromConfig(bool useICC) { + if (useICC) + return TF_SRGB; + + static auto PSDREOTF = CConfigValue("render:cm_sdr_eotf"); + static auto sdrEOTF = NTransferFunction::fromString(*PSDREOTF); + static auto P = Event::bus()->m_events.config.reloaded.listen([]() { sdrEOTF = NTransferFunction::fromString(*PSDREOTF); }); + + return sdrEOTF; +} diff --git a/src/helpers/TransferFunction.hpp b/src/helpers/TransferFunction.hpp new file mode 100644 index 00000000..ae575158 --- /dev/null +++ b/src/helpers/TransferFunction.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +namespace NTransferFunction { + enum eTF : uint8_t { + TF_DEFAULT = 0, + TF_AUTO = 1, + TF_SRGB = 2, + TF_GAMMA22 = 3, + TF_FORCED_GAMMA22 = 4, + }; + + eTF fromString(const std::string tfName); + std::string toString(eTF tf); + + eTF fromConfig(bool useICC = false); +} diff --git a/src/helpers/cm/ColorManagement.cpp b/src/helpers/cm/ColorManagement.cpp new file mode 100644 index 00000000..bac9f25a --- /dev/null +++ b/src/helpers/cm/ColorManagement.cpp @@ -0,0 +1,193 @@ +#include "ColorManagement.hpp" +#include "../../macros.hpp" +#include +#include +#include + +using namespace NColorManagement; + +namespace NColorManagement { + // expected to be small + static std::vector> knownPrimaries; + static std::vector> knownDescriptions; + static std::map, Hyprgraphics::CMatrix3> primariesConversion; +} + +const SPCPRimaries& NColorManagement::getPrimaries(ePrimaries name) { + switch (name) { + case CM_PRIMARIES_SRGB: return NColorPrimaries::BT709; + case CM_PRIMARIES_BT2020: return NColorPrimaries::BT2020; + case CM_PRIMARIES_PAL_M: return NColorPrimaries::PAL_M; + case CM_PRIMARIES_PAL: return NColorPrimaries::PAL; + case CM_PRIMARIES_NTSC: return NColorPrimaries::NTSC; + case CM_PRIMARIES_GENERIC_FILM: return NColorPrimaries::GENERIC_FILM; + case CM_PRIMARIES_CIE1931_XYZ: return NColorPrimaries::CIE1931_XYZ; + case CM_PRIMARIES_DCI_P3: return NColorPrimaries::DCI_P3; + case CM_PRIMARIES_DISPLAY_P3: return NColorPrimaries::DISPLAY_P3; + case CM_PRIMARIES_ADOBE_RGB: return NColorPrimaries::ADOBE_RGB; + default: return NColorPrimaries::DEFAULT_PRIMARIES; + } +} + +CPrimaries::CPrimaries(const SPCPRimaries& primaries, const uint32_t primariesId) : m_id(primariesId), m_primaries(primaries) { + m_primaries2XYZ = m_primaries.toXYZ(); +} + +WP CPrimaries::from(const SPCPRimaries& primaries) { + for (const auto& known : knownPrimaries) { + if (known->value() == primaries) + return known; + } + + knownPrimaries.emplace_back(CUniquePointer(new CPrimaries(primaries, knownPrimaries.size() + 1))); + return knownPrimaries.back(); +} + +WP CPrimaries::from(const ePrimaries name) { + return from(getPrimaries(name)); +} + +WP CPrimaries::from(const uint32_t primariesId) { + ASSERT(primariesId <= knownPrimaries.size()); + return knownPrimaries[primariesId - 1]; +} + +const SPCPRimaries& CPrimaries::value() const { + return m_primaries; +} + +uint CPrimaries::id() const { + return m_id; +} + +const Hyprgraphics::CMatrix3& CPrimaries::toXYZ() const { + return m_primaries2XYZ; +} + +const Hyprgraphics::CMatrix3& CPrimaries::convertMatrix(const WP dst) const { + const auto cacheKey = std::make_pair(m_id, dst->m_id); + if (!primariesConversion.contains(cacheKey)) + primariesConversion.insert(std::make_pair(cacheKey, m_primaries.convertMatrix(dst->m_primaries))); + + return primariesConversion[cacheKey]; +} + +CImageDescription::CImageDescription(const SImageDescription& imageDescription, const uint32_t imageDescriptionId) : + m_id(imageDescriptionId), m_imageDescription(imageDescription) { + m_primariesId = CPrimaries::from(m_imageDescription.getPrimaries())->id(); +} + +PImageDescription CImageDescription::from(const SImageDescription& imageDescription) { + for (const auto& known : knownDescriptions) { + if (known->value() == imageDescription) + return known; + } + + knownDescriptions.emplace_back(UP(new CImageDescription(imageDescription, knownDescriptions.size() + 1))); + return knownDescriptions.back(); +} + +PImageDescription CImageDescription::from(const uint32_t imageDescriptionId) { + ASSERT(imageDescriptionId <= knownDescriptions.size()); + return knownDescriptions[imageDescriptionId - 1]; +} + +PImageDescription CImageDescription::with(const SImageDescription::SPCLuminances& luminances) const { + auto desc = m_imageDescription; + desc.luminances = luminances; + return CImageDescription::from(desc); +} + +const SImageDescription& CImageDescription::value() const { + return m_imageDescription; +} + +uint CImageDescription::id() const { + return m_id; +} + +WP CImageDescription::getPrimaries() const { + return CPrimaries::from(m_primariesId); +} + +static Mat3x3 diag3(const std::array& s) { + return Mat3x3{std::array{s[0], 0, 0, 0, s[1], 0, 0, 0, s[2]}}; +} + +static std::optional invertMat3(const Mat3x3& m) { + const auto ARR = m.getMatrix(); + const double a = ARR[0], b = ARR[1], c = ARR[2]; + const double d = ARR[3], e = ARR[4], f = ARR[5]; + const double g = ARR[6], h = ARR[7], i = ARR[8]; + + const double A = (e * i - f * h); + const double B = -(d * i - f * g); + const double C = (d * h - e * g); + const double D = -(b * i - c * h); + const double E = (a * i - c * g); + const double F = -(a * h - b * g); + const double G = (b * f - c * e); + const double H = -(a * f - c * d); + const double I = (a * e - b * d); + + const double det = a * A + b * B + c * C; + if (std::abs(det) < 1e-18) + return std::nullopt; + + const double invDet = 1.0 / det; + Mat3x3 inv{std::array{ + A * invDet, + D * invDet, + G * invDet, // + B * invDet, + E * invDet, + H * invDet, // + C * invDet, + F * invDet, + I * invDet, // + }}; + return inv; +} + +static std::array matByVec(const Mat3x3& M, const std::array& v) { + const auto ARR = M.getMatrix(); + return {ARR[0] * v[0] + ARR[1] * v[1] + ARR[2] * v[2], ARR[3] * v[0] + ARR[4] * v[1] + ARR[5] * v[2], ARR[6] * v[0] + ARR[7] * v[1] + ARR[8] * v[2]}; +} + +std::optional NColorManagement::rgbToXYZFromPrimaries(SPCPRimaries pr) { + const auto R = Hyprgraphics::xy2xyz(pr.red); + const auto G = Hyprgraphics::xy2xyz(pr.green); + const auto B = Hyprgraphics::xy2xyz(pr.blue); + const auto W = Hyprgraphics::xy2xyz(pr.white); + + // P has columns R,G,B + Mat3x3 P{std::array{R.x, G.x, B.x, R.y, G.y, B.y, R.z, G.z, B.z}}; + + auto invP = invertMat3(P); + if (!invP) + return std::nullopt; + + const auto S = matByVec(*invP, {W.x, W.y, W.z}); + + P.multiply(diag3(S)); // RGB->XYZ + + return P; +} + +Mat3x3 NColorManagement::adaptBradford(Hyprgraphics::CColor::xy srcW, Hyprgraphics::CColor::xy dstW) { + static const Mat3x3 Bradford{std::array{0.8951f, 0.2664f, -0.1614f, -0.7502f, 1.7135f, 0.0367f, 0.0389f, -0.0685f, 1.0296f}}; + static const Mat3x3 BradfordInv = invertMat3(Bradford).value(); + + const auto srcXYZ = Hyprgraphics::xy2xyz(srcW); + const auto dstXYZ = Hyprgraphics::xy2xyz(dstW); + + const auto srcLMS = matByVec(Bradford, {srcXYZ.x, srcXYZ.y, srcXYZ.z}); + const auto dstLMS = matByVec(Bradford, {dstXYZ.x, dstXYZ.y, dstXYZ.z}); + + const std::array scale{dstLMS[0] / srcLMS[0], dstLMS[1] / srcLMS[1], dstLMS[2] / srcLMS[2]}; + + Mat3x3 result = BradfordInv; + result.multiply(diag3(scale)).multiply(Bradford); + + return result; +} \ No newline at end of file diff --git a/src/protocols/types/ColorManagement.hpp b/src/helpers/cm/ColorManagement.hpp similarity index 78% rename from src/protocols/types/ColorManagement.hpp rename to src/helpers/cm/ColorManagement.hpp index 01077704..0103e2a4 100644 --- a/src/protocols/types/ColorManagement.hpp +++ b/src/helpers/cm/ColorManagement.hpp @@ -3,6 +3,11 @@ #include "color-management-v1.hpp" #include #include "../../helpers/memory/Memory.hpp" +#include "../../helpers/math/Math.hpp" + +#include +#include +#include #define SDR_MIN_LUMINANCE 0.2 #define SDR_MAX_LUMINANCE 80.0 @@ -12,6 +17,8 @@ #define HDR_REF_LUMINANCE 203.0 #define HLG_MAX_LUMINANCE 1000.0 +class ITexture; + namespace NColorManagement { enum eNoShader : uint8_t { CM_NS_DISABLE = 0, @@ -67,7 +74,6 @@ namespace NColorManagement { using SPCPRimaries = Hyprgraphics::SPCPRimaries; namespace NColorPrimaries { - static const auto DEFAULT_PRIMARIES = SPCPRimaries{}; static const auto BT709 = SPCPRimaries{ .red = {.x = 0.64, .y = 0.33}, @@ -76,6 +82,8 @@ namespace NColorManagement { .white = {.x = 0.3127, .y = 0.3290}, }; + static const auto DEFAULT_PRIMARIES = BT709; + static const auto PAL_M = SPCPRimaries{ .red = {.x = 0.67, .y = 0.33}, .green = {.x = 0.21, .y = 0.71}, @@ -140,7 +148,16 @@ namespace NColorManagement { }; } - const SPCPRimaries& getPrimaries(ePrimaries name); + struct SVCGTTable16 { + uint16_t channels = 0; + uint16_t entries = 0; + uint16_t entrySize = 0; + std::array, 3> ch; + }; + + const SPCPRimaries& getPrimaries(ePrimaries name); + std::optional rgbToXYZFromPrimaries(SPCPRimaries pr); + Mat3x3 adaptBradford(Hyprgraphics::CColor::xy srcW, Hyprgraphics::CColor::xy dstW); class CPrimaries { public: @@ -163,22 +180,17 @@ namespace NColorManagement { }; struct SImageDescription { - struct SIccFile { - int fd = -1; - uint32_t length = 0; - uint32_t offset = 0; - bool operator==(const SIccFile& i2) const { - return fd == i2.fd; - } - } icc; + static std::expected fromICC(const std::filesystem::path& file); - bool windowsScRGB = false; + // + std::vector rawICC; - eTransferFunction transferFunction = CM_TRANSFER_FUNCTION_GAMMA22; - float transferFunctionPower = 1.0f; + eTransferFunction transferFunction = CM_TRANSFER_FUNCTION_GAMMA22; + float transferFunctionPower = 1.0f; + bool windowsScRGB = false; - bool primariesNameSet = false; - ePrimaries primariesNamed = CM_PRIMARIES_SRGB; + bool primariesNameSet = false; + ePrimaries primariesNamed = CM_PRIMARIES_SRGB; // primaries are stored as FP values with the same scale as standard defines (0.0 - 1.0) // wayland protocol expects int32_t values multiplied by 1000000 // drm expects uint16_t values multiplied by 50000 @@ -202,11 +214,23 @@ namespace NColorManagement { } } masteringLuminances; + // Matrix data from ICC + struct SICCData { + bool present = false; + size_t lutSize = 33; + std::vector lutDataPacked; + SP lutTexture; + std::optional vcgt; + } icc; + uint32_t maxCLL = 0; uint32_t maxFALL = 0; bool operator==(const SImageDescription& d2) const { - return icc == d2.icc && windowsScRGB == d2.windowsScRGB && transferFunction == d2.transferFunction && transferFunctionPower == d2.transferFunctionPower && + if (icc.present || d2.icc.present) + return false; + + return windowsScRGB == d2.windowsScRGB && transferFunction == d2.transferFunction && transferFunctionPower == d2.transferFunctionPower && (primariesNameSet == d2.primariesNameSet && (primariesNameSet ? primariesNamed == d2.primariesNamed : primaries == d2.primaries)) && masteringPrimaries == d2.masteringPrimaries && luminances == d2.luminances && masteringLuminances == d2.masteringLuminances && maxCLL == d2.maxCLL && maxFALL == d2.maxFALL; @@ -223,9 +247,9 @@ namespace NColorManagement { case CM_TRANSFER_FUNCTION_EXT_LINEAR: return 0; case CM_TRANSFER_FUNCTION_ST2084_PQ: case CM_TRANSFER_FUNCTION_HLG: return HDR_MIN_LUMINANCE; + case CM_TRANSFER_FUNCTION_BT1886: return 0.01; case CM_TRANSFER_FUNCTION_GAMMA22: case CM_TRANSFER_FUNCTION_GAMMA28: - case CM_TRANSFER_FUNCTION_BT1886: case CM_TRANSFER_FUNCTION_ST240: case CM_TRANSFER_FUNCTION_LOG_100: case CM_TRANSFER_FUNCTION_LOG_316: @@ -243,9 +267,9 @@ namespace NColorManagement { return SDR_MAX_LUMINANCE; // assume Windows scRGB. white color range 1.0 - 125.0 maps to SDR_MAX_LUMINANCE (80) - HDR_MAX_LUMINANCE (10000) case CM_TRANSFER_FUNCTION_ST2084_PQ: return HDR_MAX_LUMINANCE; case CM_TRANSFER_FUNCTION_HLG: return HLG_MAX_LUMINANCE; + case CM_TRANSFER_FUNCTION_BT1886: return 100; case CM_TRANSFER_FUNCTION_GAMMA22: case CM_TRANSFER_FUNCTION_GAMMA28: - case CM_TRANSFER_FUNCTION_BT1886: case CM_TRANSFER_FUNCTION_ST240: case CM_TRANSFER_FUNCTION_LOG_100: case CM_TRANSFER_FUNCTION_LOG_316: @@ -262,9 +286,9 @@ namespace NColorManagement { case CM_TRANSFER_FUNCTION_EXT_LINEAR: case CM_TRANSFER_FUNCTION_ST2084_PQ: case CM_TRANSFER_FUNCTION_HLG: return HDR_REF_LUMINANCE; + case CM_TRANSFER_FUNCTION_BT1886: return 100; case CM_TRANSFER_FUNCTION_GAMMA22: case CM_TRANSFER_FUNCTION_GAMMA28: - case CM_TRANSFER_FUNCTION_BT1886: case CM_TRANSFER_FUNCTION_ST240: case CM_TRANSFER_FUNCTION_LOG_100: case CM_TRANSFER_FUNCTION_LOG_316: @@ -280,39 +304,48 @@ namespace NColorManagement { class CImageDescription { public: static WP from(const SImageDescription& imageDescription); - static WP from(const uint imageDescriptionId); + static WP from(const uint32_t imageDescriptionId); WP with(const SImageDescription::SPCLuminances& luminances) const; const SImageDescription& value() const; - uint id() const; + uint32_t id() const; WP getPrimaries() const; private: CImageDescription(const SImageDescription& imageDescription, const uint imageDescriptionId); - uint m_id; - uint m_primariesId; + uint32_t m_id = 0; + uint32_t m_primariesId = 0; SImageDescription m_imageDescription; }; using PImageDescription = WP; - static const auto DEFAULT_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{}); - static const auto DEFAULT_HDR_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{.transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ, - .primariesNameSet = true, - .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, - .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020), - .luminances = {.min = 0, .max = 10000, .reference = 203}}); - ; + static const auto DEFAULT_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{ + .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22, + .primariesNameSet = true, + .primariesNamed = NColorManagement::CM_PRIMARIES_SRGB, + .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_SRGB), + .luminances = {.min = SDR_MIN_LUMINANCE, .max = 80, .reference = 80}, + }); + + static const auto DEFAULT_HDR_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{ + .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ, + .primariesNameSet = true, + .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, + .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020), + .luminances = {.min = HDR_MIN_LUMINANCE, .max = 10000, .reference = 203}, + }); + static const auto SCRGB_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{ - .windowsScRGB = true, .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_EXT_LINEAR, + .windowsScRGB = true, .primariesNameSet = true, .primariesNamed = NColorManagement::CM_PRIMARIES_SRGB, .primaries = NColorPrimaries::BT709, .luminances = {.reference = 203}, }); - ; -} \ No newline at end of file + static const auto LINEAR_IMAGE_DESCRIPTION = SCRGB_IMAGE_DESCRIPTION; // TODO any reason to use something different? +} diff --git a/src/helpers/cm/ICC.cpp b/src/helpers/cm/ICC.cpp new file mode 100644 index 00000000..00140c62 --- /dev/null +++ b/src/helpers/cm/ICC.cpp @@ -0,0 +1,278 @@ +#include "ColorManagement.hpp" +#include "../math/Math.hpp" +#include +#include + +#include "../../debug/log/Logger.hpp" +#include "../../render/Texture.hpp" +#include "../../render/Renderer.hpp" + +#include +using namespace Hyprutils::Utils; + +#include + +using namespace NColorManagement; + +static std::vector readBinary(const std::filesystem::path& file) { + std::ifstream ifs(file, std::ios::binary); + if (!ifs.good()) + return {}; + + ifs.seekg(0, std::ios::end); + size_t len = ifs.tellg(); + ifs.seekg(0, std::ios::beg); + + if (len <= 0) + return {}; + + std::vector buf; + buf.resize(len); + ifs.read(reinterpret_cast(buf.data()), len); + + return buf; +} + +static uint16_t bigEndianU16(const uint8_t* p) { + return (uint16_t)((uint16_t)p[0] << 8 | (uint16_t)p[1]); +} + +static uint32_t bigEndianU32(const uint8_t* p) { + return (uint32_t)p[0] << 24 | (uint32_t)p[1] << 16 | (uint32_t)p[2] << 8 | (uint32_t)p[3]; +} + +static constexpr cmsTagSignature makeSig(char a, char b, char c, char d) { + return sc(sc(a) << 24 | sc(b) << 16 | sc(c) << 8 | sc(d)); +} + +static constexpr cmsTagSignature VCGT_SIG = makeSig('v', 'c', 'g', 't'); + +// + +static std::expected, std::string> readVCGT16(cmsHPROFILE prof) { + if (!cmsIsTag(prof, VCGT_SIG)) + return std::nullopt; + + cmsUInt32Number n = cmsReadRawTag(prof, VCGT_SIG, nullptr, 0); + if (n < 8 + 4 + 2 + 2 + 2 + 2) // header + type + table header + return std::unexpected("Malformed vcgt tag"); + + std::vector raw(n); + if (cmsReadRawTag(prof, VCGT_SIG, raw.data(), n) != n) + return std::unexpected("Malformed vcgt tag"); + + // raw layout: + // 0 ... 3: 'vcgt' + // 4 ... 7: reserved + // 8 ... 11: gammaType (0 = table) + uint32_t gammaType = bigEndianU32(raw.data() + 8); + if (gammaType != 0) + return std::unexpected("VCGT formula type is not supported by Hyprland"); + + SVCGTTable16 table; + table.channels = bigEndianU16(raw.data() + 12); + table.entries = bigEndianU16(raw.data() + 14); + table.entrySize = bigEndianU16(raw.data() + 16); + // raw+18: reserved u16 + + Log::logger->log(Log::DEBUG, "readVCGT16: table has {} channels, {} entries, and entry size of {}", table.channels, table.entries, table.entrySize); + + if (table.channels != 3 || table.entrySize != 2 || table.entries == 0) + return std::unexpected("invalid vcgt table size"); + + size_t tableBytes = (size_t)table.channels * (size_t)table.entries * (size_t)table.entrySize; + + // VCGT is a piece of shit and some absolute fucking mongoloid idiots + // decided it'd be great to have both 18 and 20 + // FUCK YOU + size_t tableOff = 20; + + auto readTable = [&] -> void { + for (int c = 0; c < 3; ++c) { + table.ch[c].resize(table.entries); + for (uint16_t i = 0; i < table.entries; ++i) { + const uint8_t* p = raw.data() + tableOff + static_cast((c * table.entries + i) * 2); + table.ch[c][i] = bigEndianU16(p); // 0 ... 65535 + } + } + }; + + if (raw.size() < tableOff + tableBytes) { + tableOff = 18; + + if (raw.size() < tableOff + tableBytes) { + Log::logger->log(Log::ERR, "readVCGT16: table is too short, tag is invalid"); + return std::unexpected("table is too short"); + } + + Log::logger->log(Log::DEBUG, "readVCGT16: table is too short, but off = 18 fits. Attempting offset = 18"); + + readTable(); + } else { + readTable(); + + // if the table's last entry is suspiciously low, we more than likely read an 18 as a 20. + if (table.ch[0][table.entries - 1] < 30000) { + Log::logger->log(Log::DEBUG, "readVCGT16: table is likely offset 18 not 20, re-reading"); + + tableOff = 18; + + readTable(); + } + } + + if (table.ch[0][table.entries - 1] < 30000) { + Log::logger->log(Log::ERR, "readVCGT16: table is malformed, last value of a gamma ramp can't be {}", table.ch[0][table.entries - 1]); + return std::unexpected("invalid table values"); + } + + Log::logger->log(Log::DEBUG, "readVCGT16: red channel: [{}, {}, ... {}, {}]", table.ch[0][0], table.ch[0][1], table.ch[0][table.entries - 2], table.ch[0][table.entries - 1]); + Log::logger->log(Log::DEBUG, "readVCGT16: green channel: [{}, {}, ... {}, {}]", table.ch[1][0], table.ch[1][1], table.ch[1][table.entries - 2], table.ch[1][table.entries - 1]); + Log::logger->log(Log::DEBUG, "readVCGT16: blue channel: [{}, {}, ... {}, {}]", table.ch[2][0], table.ch[2][1], table.ch[2][table.entries - 2], table.ch[2][table.entries - 1]); + + return table; +} + +struct CmsProfileDeleter { + void operator()(cmsHPROFILE p) const { + if (p) + cmsCloseProfile(p); + } +}; +struct CmsTransformDeleter { + void operator()(cmsHTRANSFORM t) const { + if (t) + cmsDeleteTransform(t); + } +}; + +using UniqueProfile = std::unique_ptr, CmsProfileDeleter>; +using UniqueTransform = std::unique_ptr, CmsTransformDeleter>; + +static UniqueProfile createLinearSRGBProfile() { + cmsCIExyYTRIPLE prim{}; + // sRGB / Rec.709 primaries + prim.Red.x = 0.6400; + prim.Red.y = 0.3300; + prim.Red.Y = 1.0; + prim.Green.x = 0.3000; + prim.Green.y = 0.6000; + prim.Green.Y = 1.0; + prim.Blue.x = 0.1500; + prim.Blue.y = 0.0600; + prim.Blue.Y = 1.0; + + cmsCIExyY wp{}; + wp.x = 0.3127; + wp.y = 0.3290; + wp.Y = 1.0; // D65 + + cmsToneCurve* lin = cmsBuildGamma(nullptr, 1.0); + cmsToneCurve* curves[3] = {lin, lin, lin}; + + cmsHPROFILE p = cmsCreateRGBProfile(&wp, &prim, curves); + + cmsFreeToneCurve(lin); + return UniqueProfile{p}; +} + +static std::expected buildIcc3DLut(cmsHPROFILE profile, SImageDescription& image) { + UniqueProfile src = createLinearSRGBProfile(); + if (!src) + return std::unexpected("Failed to create linear sRGB profile"); + + // Rendering intent: RELATIVE_COLORIMETRIC is common for displays; add BPC to be safe. + const int intent = INTENT_RELATIVE_COLORIMETRIC; + const cmsUInt32Number flags = cmsFLAGS_BLACKPOINTCOMPENSATION | cmsFLAGS_HIGHRESPRECALC; // good quality precalc in LCMS + + // float->float transform (linear input, encoded output in dst device space) + UniqueTransform xform{cmsCreateTransform(src.get(), TYPE_RGB_FLT, profile, TYPE_RGB_FLT, intent, flags)}; + if (!xform) + return std::unexpected("Failed to create ICC transform"); + + Log::logger->log(Log::DEBUG, "Building a {}³ 3D LUT", image.icc.lutSize); + + image.icc.present = true; + image.icc.lutDataPacked.resize(image.icc.lutSize * image.icc.lutSize * image.icc.lutSize * 3); + + auto idx = [&image](int r, int g, int b) -> size_t { + // + return ((size_t)b * image.icc.lutSize * image.icc.lutSize + (size_t)g * image.icc.lutSize + (size_t)r) * 3; + }; + + for (size_t bz = 0; bz < image.icc.lutSize; ++bz) { + for (size_t gy = 0; gy < image.icc.lutSize; ++gy) { + for (size_t rx = 0; rx < image.icc.lutSize; ++rx) { + float in[3] = { + rx / float(image.icc.lutSize - 1), + gy / float(image.icc.lutSize - 1), + bz / float(image.icc.lutSize - 1), + }; + float outRGB[3]; + cmsDoTransform(xform.get(), in, outRGB, 1); + + outRGB[0] = std::clamp(outRGB[0], 0.F, 1.F); + outRGB[1] = std::clamp(outRGB[1], 0.F, 1.F); + outRGB[2] = std::clamp(outRGB[2], 0.F, 1.F); + + const size_t o = idx(rx, gy, bz); + image.icc.lutDataPacked[o + 0] = outRGB[0]; + image.icc.lutDataPacked[o + 1] = outRGB[1]; + image.icc.lutDataPacked[o + 2] = outRGB[2]; + } + } + } + + Log::logger->log(Log::DEBUG, "3D LUT constructed, size {}", image.icc.lutDataPacked.size()); + + // upload + image.icc.lutTexture = g_pHyprRenderer->createTexture(image.icc.lutDataPacked, image.icc.lutSize); + + return {}; +} + +std::expected SImageDescription::fromICC(const std::filesystem::path& file) { + static auto PVCGTENABLED = CConfigValue("render:icc_vcgt_enabled"); + + std::error_code ec; + if (!std::filesystem::exists(file, ec) || ec) + return std::unexpected("Invalid file"); + + SImageDescription image; + image.rawICC = readBinary(file); + + if (image.rawICC.empty()) + return std::unexpected("Failed to read file"); + + cmsHPROFILE prof = cmsOpenProfileFromFile(file.string().c_str(), "r"); + if (!prof) + return std::unexpected("CMS failed to open icc file"); + + CScopeGuard x([&prof] { cmsCloseProfile(prof); }); + + // only handle RGB (typical display profiles) + if (cmsGetColorSpace(prof) != cmsSigRgbData) + return std::unexpected("Only RGB display profiles are supported"); + + Log::logger->log(Log::DEBUG, "============= Begin ICC load ============="); + Log::logger->log(Log::DEBUG, "ICC size: {} bytes", image.rawICC.size()); + + if (const auto RET = buildIcc3DLut(prof, image); !RET) + return std::unexpected(RET.error()); + + if (*PVCGTENABLED) { + auto vcgtRes = readVCGT16(prof); + if (!vcgtRes) + return std::unexpected(vcgtRes.error()); + + image.icc.vcgt = *vcgtRes; + + if (!*vcgtRes) + Log::logger->log(Log::DEBUG, "ICC profile has no VCGT data"); + } else + Log::logger->log(Log::DEBUG, "Skipping VCGT load, disabled by config"); + + Log::logger->log(Log::DEBUG, "============= End ICC load ============="); + + return image; +} \ No newline at end of file diff --git a/src/helpers/math/Direction.cpp b/src/helpers/math/Direction.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/helpers/math/Direction.hpp b/src/helpers/math/Direction.hpp new file mode 100644 index 00000000..9905db4f --- /dev/null +++ b/src/helpers/math/Direction.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +namespace Math { + enum eDirection : int8_t { + DIRECTION_DEFAULT = -1, + DIRECTION_UP, + DIRECTION_RIGHT, + DIRECTION_DOWN, + DIRECTION_LEFT + }; + + inline eDirection fromChar(char x) { + switch (x) { + case 'r': return DIRECTION_RIGHT; + case 'l': return DIRECTION_LEFT; + case 't': + case 'u': return DIRECTION_UP; + case 'b': + case 'd': return DIRECTION_DOWN; + default: return DIRECTION_DEFAULT; + } + } + + inline const char* toString(eDirection d) { + switch (d) { + case DIRECTION_UP: return "up"; + case DIRECTION_DOWN: return "down"; + case DIRECTION_LEFT: return "left"; + case DIRECTION_RIGHT: return "right"; + default: return "default"; + } + } +}; \ No newline at end of file diff --git a/src/helpers/time/Time.cpp b/src/helpers/time/Time.cpp index 791f5ea1..f454b784 100644 --- a/src/helpers/time/Time.cpp +++ b/src/helpers/time/Time.cpp @@ -5,7 +5,6 @@ using s_ns = std::pair; -// HAS to be a > b static s_ns timediff(const s_ns& a, const s_ns& b) { s_ns d; @@ -13,7 +12,7 @@ static s_ns timediff(const s_ns& a, const s_ns& b) { if (a.second >= b.second) d.second = a.second - b.second; else { - d.second = b.second - a.second; + d.second = (TIMESPEC_NSEC_PER_SEC + a.second) - b.second; d.first -= 1; } @@ -46,9 +45,9 @@ uint64_t Time::millis(const steady_tp& tp) { } s_ns Time::secNsec(const steady_tp& tp) { - const uint64_t sec = chr::duration_cast(tp.time_since_epoch()).count(); - const chr::steady_clock::duration nsecdur = tp - chr::steady_clock::time_point(chr::seconds(sec)); - return std::make_pair<>(sec, chr::duration_cast(nsecdur).count()); + const uint64_t sec = chr::duration_cast(tp.time_since_epoch()).count(); + const auto nsecdur = tp - chr::steady_clock::time_point(chr::seconds(sec)); + return {sec, chr::duration_cast(nsecdur).count()}; } uint64_t Time::millis(const system_tp& tp) { @@ -56,9 +55,9 @@ uint64_t Time::millis(const system_tp& tp) { } s_ns Time::secNsec(const system_tp& tp) { - const uint64_t sec = chr::duration_cast(tp.time_since_epoch()).count(); - const chr::steady_clock::duration nsecdur = tp - chr::system_clock::time_point(chr::seconds(sec)); - return std::make_pair<>(sec, chr::duration_cast(nsecdur).count()); + const uint64_t sec = chr::duration_cast(tp.time_since_epoch()).count(); + const auto nsecdur = tp - chr::system_clock::time_point(chr::seconds(sec)); + return {sec, chr::duration_cast(nsecdur).count()}; } // TODO: this is a mess, but C++ doesn't define what steady_clock is. @@ -69,12 +68,12 @@ s_ns Time::secNsec(const system_tp& tp) { // In general, this may shift the time around by a couple hundred ns. Doesn't matter, realistically. Time::steady_tp Time::fromTimespec(const timespec* ts) { - struct timespec mono, real; + timespec mono{}, real{}; clock_gettime(CLOCK_MONOTONIC, &mono); clock_gettime(CLOCK_REALTIME, &real); - Time::steady_tp now = Time::steadyNow(); - Time::system_tp nowSys = Time::systemNow(); - s_ns stdSteady, stdReal; + auto now = Time::steadyNow(); + auto nowSys = Time::systemNow(); + s_ns stdSteady, stdReal; stdSteady = Time::secNsec(now); stdReal = Time::secNsec(nowSys); @@ -84,7 +83,7 @@ Time::steady_tp Time::fromTimespec(const timespec* ts) { if (real.tv_nsec >= mono.tv_nsec) diff.second = real.tv_nsec - mono.tv_nsec; else { - diff.second = mono.tv_nsec - real.tv_nsec; + diff.second = TIMESPEC_NSEC_PER_SEC + real.tv_nsec - mono.tv_nsec; diff.first -= 1; } @@ -104,7 +103,7 @@ Time::steady_tp Time::fromTimespec(const timespec* ts) { } struct timespec Time::toTimespec(const steady_tp& tp) { - struct timespec mono, real; + timespec mono{}, real{}; clock_gettime(CLOCK_MONOTONIC, &mono); clock_gettime(CLOCK_REALTIME, &real); Time::steady_tp now = Time::steadyNow(); @@ -119,7 +118,7 @@ struct timespec Time::toTimespec(const steady_tp& tp) { if (real.tv_nsec >= mono.tv_nsec) diff.second = real.tv_nsec - mono.tv_nsec; else { - diff.second = mono.tv_nsec - real.tv_nsec; + diff.second = TIMESPEC_NSEC_PER_SEC + real.tv_nsec - mono.tv_nsec; diff.first -= 1; } @@ -137,3 +136,10 @@ struct timespec Time::toTimespec(const steady_tp& tp) { auto sum = timeadd(tpTime, diffFinal); return timespec{.tv_sec = sum.first, .tv_nsec = sum.second}; } + +Time::steady_dur Time::till(const timespec& ts) { + timespec mono{}; + clock_gettime(CLOCK_MONOTONIC, &mono); + const auto delay = (ts.tv_sec - mono.tv_sec) * 1000000000 + (ts.tv_nsec - mono.tv_nsec); + return std::chrono::nanoseconds(delay); +} diff --git a/src/helpers/time/Time.hpp b/src/helpers/time/Time.hpp index eb3b5771..ce99982b 100644 --- a/src/helpers/time/Time.hpp +++ b/src/helpers/time/Time.hpp @@ -17,6 +17,7 @@ namespace Time { steady_tp fromTimespec(const timespec*); struct timespec toTimespec(const steady_tp& tp); + steady_dur till(const timespec& ts); uint64_t millis(const steady_tp& tp); uint64_t millis(const system_tp& tp); diff --git a/src/hyprerror/HyprError.cpp b/src/hyprerror/HyprError.cpp index c8125e5b..60bf0a78 100644 --- a/src/hyprerror/HyprError.cpp +++ b/src/hyprerror/HyprError.cpp @@ -6,8 +6,8 @@ #include "../render/pass/TexPassElement.hpp" #include "../managers/animation/AnimationManager.hpp" #include "../render/Renderer.hpp" -#include "../managers/HookSystemManager.hpp" #include "../desktop/state/FocusState.hpp" +#include "../event/EventBus.hpp" #include using namespace Hyprutils::Animation; @@ -15,7 +15,7 @@ using namespace Hyprutils::Animation; CHyprError::CHyprError() { g_pAnimationManager->createAnimation(0.f, m_fadeOpacity, g_pConfigManager->getAnimationPropertyConfig("fadeIn"), AVARDAMAGE_NONE); - static auto P = g_pHookSystem->hookDynamic("focusedMon", [&](void* self, SCallbackInfo& info, std::any param) { + static auto P = Event::bus()->m_events.monitor.focused.listen([&](PHLMONITOR mon) { if (!m_isCreated) return; @@ -23,15 +23,13 @@ CHyprError::CHyprError() { m_monitorChanged = true; }); - static auto P2 = g_pHookSystem->hookDynamic("preRender", [&](void* self, SCallbackInfo& info, std::any param) { + static auto P2 = Event::bus()->m_events.render.pre.listen([&](PHLMONITOR mon) { if (!m_isCreated) return; if (m_fadeOpacity->isBeingAnimated() || m_monitorChanged) g_pHyprRenderer->damageBox(m_damageBox); }); - - m_texture = makeShared(); } void CHyprError::queueCreate(std::string message, const CHyprColor& color) { @@ -40,8 +38,8 @@ void CHyprError::queueCreate(std::string message, const CHyprColor& color) { } void CHyprError::createQueued() { - if (m_isCreated) - m_texture->destroyTexture(); + if (m_isCreated && m_texture) + m_texture.reset(); m_fadeOpacity->setConfig(g_pConfigManager->getAnimationPropertyConfig("fadeIn")); @@ -83,7 +81,8 @@ void CHyprError::createQueued() { const double X = PAD; const double Y = TOPBAR ? PAD : PMONITOR->m_pixelSize.y - HEIGHT - PAD; - m_damageBox = {0, 0, sc(PMONITOR->m_pixelSize.x), sc(HEIGHT) + sc(PAD) * 2}; + m_damageBox = {sc(PMONITOR->m_position.x), sc(PMONITOR->m_position.y + (TOPBAR ? 0 : PMONITOR->m_pixelSize.y - (HEIGHT + PAD * 2))), sc(PMONITOR->m_pixelSize.x), + sc(HEIGHT + PAD * 2)}; cairo_new_sub_path(CAIRO); cairo_arc(CAIRO, X + WIDTH - RADIUS, Y + RADIUS, RADIUS, -90 * DEGREES, 0 * DEGREES); @@ -111,6 +110,8 @@ void CHyprError::createQueued() { pango_font_description_set_style(pangoFD, PANGO_STYLE_NORMAL); pango_font_description_set_weight(pangoFD, PANGO_WEIGHT_NORMAL); pango_layout_set_font_description(layoutText, pangoFD); + pango_layout_set_width(layoutText, (WIDTH - 2 * (1 + RADIUS)) * PANGO_SCALE); + pango_layout_set_ellipsize(layoutText, PANGO_ELLIPSIZE_END); float yoffset = TOPBAR ? 0 : Y - PAD; int renderedcnt = 0; @@ -132,9 +133,8 @@ void CHyprError::createQueued() { pango_layout_set_text(layoutText, moreString.c_str(), -1); pango_cairo_show_layout(CAIRO, layoutText); } - m_queued = ""; - m_lastHeight = yoffset + PAD + 1 - (TOPBAR ? 0 : Y - PAD); + m_lastHeight = HEIGHT; pango_font_description_free(pangoFD); g_object_unref(layoutText); @@ -143,12 +143,13 @@ void CHyprError::createQueued() { // copy the data to an OpenGL texture we have const auto DATA = cairo_image_surface_get_data(CAIROSURFACE); - m_texture->allocate(); - m_texture->bind(); - m_texture->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST); - m_texture->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST); - m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE); - m_texture->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED); + auto tex = texture(); + tex->allocate(PMONITOR->m_pixelSize); + tex->bind(); + tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST); + tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST); + tex->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE); + tex->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA); @@ -166,7 +167,8 @@ void CHyprError::createQueued() { m->m_reservedArea.resetType(Desktop::RESERVED_DYNAMIC_TYPE_ERROR_BAR); } - PMONITOR->m_reservedArea.addType(Desktop::RESERVED_DYNAMIC_TYPE_ERROR_BAR, Vector2D{0.0, *BAR_POSITION == 0 ? HEIGHT : 0.0}, Vector2D{0.0, *BAR_POSITION != 0 ? HEIGHT : 0.0}); + const auto RESERVED = (HEIGHT + PAD) / SCALE; + PMONITOR->m_reservedArea.addType(Desktop::RESERVED_DYNAMIC_TYPE_ERROR_BAR, Vector2D{0.0, TOPBAR ? RESERVED : 0.0}, Vector2D{0.0, !TOPBAR ? RESERVED : 0.0}); for (const auto& m : g_pCompositor->m_monitors) { g_pHyprRenderer->arrangeLayersForMonitor(m->m_id); @@ -184,12 +186,14 @@ void CHyprError::draw() { if (!m_fadeOpacity->isBeingAnimated()) { if (m_fadeOpacity->value() == 0.f) { m_queuedDestroy = false; - m_texture->destroyTexture(); + if (m_texture) + m_texture.reset(); m_isCreated = false; m_queued = ""; for (auto& m : g_pCompositor->m_monitors) { g_pHyprRenderer->arrangeLayersForMonitor(m->m_id); + m->m_reservedArea.resetType(Desktop::RESERVED_DYNAMIC_TYPE_ERROR_BAR); } return; @@ -200,12 +204,13 @@ void CHyprError::draw() { } } - const auto PMONITOR = g_pHyprOpenGL->m_renderData.pMonitor; + const auto PMONITOR = g_pHyprOpenGL->m_renderData.pMonitor; - CBox monbox = {0, 0, PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y}; + CBox monbox = {0, 0, PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y}; - m_damageBox.x = sc(PMONITOR->m_position.x); - m_damageBox.y = sc(PMONITOR->m_position.y); + static auto BAR_POSITION = CConfigValue("debug:error_position"); + m_damageBox.x = sc(PMONITOR->m_position.x); + m_damageBox.y = sc(PMONITOR->m_position.y + (*BAR_POSITION == 0 ? 0 : PMONITOR->m_pixelSize.y - m_damageBox.height)); if (m_fadeOpacity->isBeingAnimated() || m_monitorChanged) g_pHyprRenderer->damageBox(m_damageBox); @@ -213,7 +218,7 @@ void CHyprError::draw() { m_monitorChanged = false; CTexPassElement::SRenderData data; - data.tex = m_texture; + data.tex = texture(); data.box = monbox; data.a = m_fadeOpacity->value(); @@ -234,3 +239,9 @@ bool CHyprError::active() { float CHyprError::height() { return m_lastHeight; } + +SP CHyprError::texture() { + if (!m_texture) + m_texture = g_pHyprRenderer->createTexture(); + return m_texture; +} \ No newline at end of file diff --git a/src/hyprerror/HyprError.hpp b/src/hyprerror/HyprError.hpp index f4bc43d8..48b9e805 100644 --- a/src/hyprerror/HyprError.hpp +++ b/src/hyprerror/HyprError.hpp @@ -18,13 +18,16 @@ class CHyprError { bool active(); float height(); // logical + // + SP texture(); + private: void createQueued(); std::string m_queued = ""; CHyprColor m_queuedColor; bool m_queuedDestroy = false; bool m_isCreated = false; - SP m_texture; + SP m_texture; PHLANIMVAR m_fadeOpacity; CBox m_damageBox = {0, 0, 0, 0}; float m_lastHeight = 0.F; diff --git a/src/i18n/Engine.cpp b/src/i18n/Engine.cpp index 0325a3ce..c68400eb 100644 --- a/src/i18n/Engine.cpp +++ b/src/i18n/Engine.cpp @@ -60,6 +60,57 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("be_BY", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Не атрымалася перазагрузіць шэйдар CM, аварыйна ўжываецца rgba/rgbx."); huEngine->registerEntry("be_BY", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Манітор {name}: пашыраны каляровы дыяпазон уключаны, але экран не ў рэжыме 10-біт."); + // bn_BD (Bengali) + huEngine->registerEntry("bn_BD", TXT_KEY_ANR_TITLE, "অ্যাপ্লিকেশন সাড়া দিচ্ছে না"); + huEngine->registerEntry("bn_BD", TXT_KEY_ANR_CONTENT, "অ্যাপ্লিকেশন {title} - {class} সাড়া দিচ্ছে না।\nআপনি এটি নিয়ে কি করতে চান?"); + huEngine->registerEntry("bn_BD", TXT_KEY_ANR_OPTION_TERMINATE, "বন্ধ করুন"); + huEngine->registerEntry("bn_BD", TXT_KEY_ANR_OPTION_WAIT, "অপেক্ষা করুন"); + huEngine->registerEntry("bn_BD", TXT_KEY_ANR_PROP_UNKNOWN, "(অজানা)"); + + huEngine->registerEntry("bn_BD", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "একটি অ্যাপ্লিকেশন {app} একটি অজানা অনুমতির অনুরোধ করছে।"); + huEngine->registerEntry("bn_BD", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "একটি অ্যাপ্লিকেশন {app} আপনার স্ক্রিন রেকর্ড করার চেষ্টা করছে।\n\nআপনি কি এটি অনুমতি দিতে চান?"); + huEngine->registerEntry("bn_BD", TXT_KEY_PERMISSION_REQUEST_PLUGIN, + "একটি অ্যাপ্লিকেশন {app} একটি প্লাগইন লোড করার চেষ্টা করছে: {plugin}।\n\nআপনি কি এটি অনুমতি দিতে চান?"); + huEngine->registerEntry("bn_BD", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "একটি নতুন কীবোর্ড সনাক্ত করা হয়েছে: {keyboard}।\n\nআপনি কি এটি কাজ করতে অনুমতি দিতে চান?"); + huEngine->registerEntry("bn_BD", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(অজানা)"); + huEngine->registerEntry("bn_BD", TXT_KEY_PERMISSION_TITLE, "অনুমতির অনুরোধ"); + huEngine->registerEntry("bn_BD", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "টিপ: আপনি Hyprland কনফিগারেশন ফাইলে এর জন্য স্থায়ী নিয়ম সেট করতে পারেন।"); + huEngine->registerEntry("bn_BD", TXT_KEY_PERMISSION_ALLOW, "অনুমতি দিন"); + huEngine->registerEntry("bn_BD", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "অনুমতি দিন এবং মনে রাখুন"); + huEngine->registerEntry("bn_BD", TXT_KEY_PERMISSION_ALLOW_ONCE, "একবার অনুমতি দিন"); + huEngine->registerEntry("bn_BD", TXT_KEY_PERMISSION_DENY, "প্রত্যাখ্যান করুন"); + huEngine->registerEntry("bn_BD", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "অজানা অ্যাপ্লিকেশন (wayland ক্লায়েন্ট ID {wayland_id})"); + + huEngine->registerEntry( + "bn_BD", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "আপনার XDG_CURRENT_DESKTOP পরিবেশ পরিবর্তনশীল বাহ্যিকভাবে পরিচালিত হচ্ছে বলে মনে হচ্ছে, বর্তমান মান: {value}।\nএটি সমস্যা সৃষ্টি করতে পারে যদি না এটি ইচ্ছাকৃত হয়।"); + huEngine->registerEntry("bn_BD", TXT_KEY_NOTIF_NO_GUIUTILS, "আপনার সিস্টেমে hyprland-guiutils ইনস্টল নেই যা কিছু ডায়ালগের জন্য ব্যবহৃত হয়। এটি ইনস্টল করার কথা বিবেচনা করুন।"); + huEngine->registerEntry("bn_BD", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Hyprland {count}টি প্রয়োজনীয় সম্পদ লোড করতে ব্যর্থ হয়েছে, খারাপ প্যাকেজিং কাজের জন্য আপনার ডিস্ট্রো প্যাকেজারদের দোষ দিন!"; + return "Hyprland {count}টি প্রয়োজনীয় সম্পদ লোড করতে ব্যর্থ হয়েছে, খারাপ প্যাকেজিং কাজের জন্য আপনার ডিস্ট্রো প্যাকেজারদের দোষ দিন!"; + }); + huEngine->registerEntry("bn_BD", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "আপনার মনিটর লেআউট ভুলভাবে কনফিগার করা হয়েছে। মনিটর {name} লেআউটে অন্য মনিটর(গুলি) এর সাথে ওভারল্যাপ করছে।\nবিস্তারিত জানতে wiki (Monitors page) দেখুন। " + "এটি অবশ্যই সমস্যা সৃষ্টি করবে।"); + huEngine->registerEntry("bn_BD", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "মনিটর {name} কোনো অনুরোধকৃত মোড সেট করতে পারেনি, মোড {mode} এ ফিরে যাচ্ছে।"); + huEngine->registerEntry("bn_BD", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "মনিটর {name} এর জন্য অবৈধ স্কেল পাঠানো হয়েছে: {scale}, প্রস্তাবিত স্কেল ব্যবহার করা হচ্ছে: {fixed_scale}"); + huEngine->registerEntry("bn_BD", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "প্লাগইন {name} লোড করতে ব্যর্থ: {error}"); + huEngine->registerEntry("bn_BD", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM শেডার পুনরায় লোড করতে ব্যর্থ, rgba/rgbx এ ফিরে যাচ্ছে।"); + huEngine->registerEntry("bn_BD", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "মনিটর {name}: ওয়াইড কালার গ্যামুট সক্রিয় কিন্তু স্ক্রিন 10-বিট মোডে নেই।"); + huEngine->registerEntry("bn_BD", TXT_KEY_NOTIF_NO_WATCHDOG, "Hyprland start-hyprland ছাড়া চালু করা হয়েছে। এটি অত্যন্ত সুপারিশকৃত নয় যদি না আপনি ডিবাগিং পরিবেশে থাকেন।"); + + huEngine->registerEntry("bn_BD", TXT_KEY_SAFE_MODE_TITLE, "নিরাপদ মোড"); + huEngine->registerEntry( + "bn_BD", TXT_KEY_SAFE_MODE_DESCRIPTION, + "Hyprland নিরাপদ মোডে চালু করা হয়েছে, যার মানে আপনার শেষ সেশন ক্র্যাশ হয়েছিল।\nনিরাপদ মোড আপনার কনফিগ লোড হওয়া থেকে প্রতিরোধ করে। আপনি " + "এই পরিবেশে সমস্যা সমাধান করতে পারেন, অথবা নিচের বাটন দিয়ে আপনার কনফিগ লোড করতে পারেন।\nডিফল্ট কীবাইন্ড প্রযোজ্য: kitty এর জন্য SUPER+Q, মৌলিক রানারের জন্য SUPER+R, " + "প্রস্থান করতে SUPER+M।\nHyprland পুনরায় চালু করলে আবার স্বাভাবিক মোডে চালু হবে।"); + huEngine->registerEntry("bn_BD", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, "কনফিগ লোড করুন"); + huEngine->registerEntry("bn_BD", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, "ক্র্যাশ রিপোর্ট ডিরেক্টরি খুলুন"); + huEngine->registerEntry("bn_BD", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, "ঠিক আছে, এটি বন্ধ করুন"); + // da_DK (Danish) huEngine->registerEntry("da_DK", TXT_KEY_ANR_TITLE, "Applikationen Svarer Ikke"); huEngine->registerEntry("da_DK", TXT_KEY_ANR_CONTENT, "En applikation {title} - {class} svarer ikke.\nHvad vil du gøre ved det?"); @@ -109,6 +160,8 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "An application {app} is requesting an unknown permission."); huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "An application {app} is trying to capture your screen.\n\nDo you want to allow it to?"); + huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_REQUEST_CURSOR_POS, + "An application {app} is trying to capture your cursor position.\n\nDo you want to allow it to?"); huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "An application {app} is trying to load a plugin: {plugin}.\n\nDo you want to allow it to?"); huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "A new keyboard has been detected: {keyboard}.\n\nDo you want to allow it to operate?"); huEngine->registerEntry("en_US", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(unknown)"); @@ -1091,6 +1144,65 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("zh_CN", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "无法加载插件 {name}:{error}"); huEngine->registerEntry("zh_CN", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "无法重新加载CM着色器,将使用rgba/rgbx兜底。"); huEngine->registerEntry("zh_CN", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "显示器 {name}:宽色域被启用了,但是显示器并不在10-bit模式。"); + huEngine->registerEntry("zh_CN", TXT_KEY_NOTIF_NO_WATCHDOG, "Hyprland 启动时未使用 start-hyprland。除非你处于调试环境,否则极度不推荐这样做。"); + + huEngine->registerEntry("zh_CN", TXT_KEY_SAFE_MODE_TITLE, "安全模式"); + huEngine->registerEntry("zh_CN", TXT_KEY_SAFE_MODE_DESCRIPTION, + "Hyprland " + "已在安全模式下启动,这意味着你上次会话崩溃了。\n安全模式会阻止加载你的配置。你可以在此环境中进行故障排除,或者使用下方按钮加载你的配置。\n默认快" + "捷键适用:SUPER+Q 打开 Kitty,SUPER+R 打开简易启动器,SUPER+M 退出。\n重新启动 " + "Hyprland 将再次进入正常模式。"); + huEngine->registerEntry("zh_CN", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, "加载配置"); + huEngine->registerEntry("zh_CN", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, "打开崩溃报告目录"); + huEngine->registerEntry("zh_CN", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, "好的,关闭窗口"); + + // zh_TW (Traditional Chinese) + huEngine->registerEntry("zh_TW", TXT_KEY_ANR_TITLE, "應用程式沒有回應"); + huEngine->registerEntry("zh_TW", TXT_KEY_ANR_CONTENT, "應用程式 {title} - {class} 沒有回應。\n您想要怎麼做?"); + huEngine->registerEntry("zh_TW", TXT_KEY_ANR_OPTION_TERMINATE, "強制結束"); + huEngine->registerEntry("zh_TW", TXT_KEY_ANR_OPTION_WAIT, "等待"); + huEngine->registerEntry("zh_TW", TXT_KEY_ANR_PROP_UNKNOWN, "(未知)"); + + huEngine->registerEntry("zh_TW", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "應用程式 {app} 正在請求未知的權限。"); + huEngine->registerEntry("zh_TW", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "應用程式 {app} 試圖擷取您的螢幕畫面。\n\n您是否允許?"); + huEngine->registerEntry("zh_TW", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "應用程式 {app} 試圖載入外掛:{plugin}。\n\n您是否允許?"); + huEngine->registerEntry("zh_TW", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "偵測到新鍵盤:{keyboard}。\n\n您是否允許它進行操作?"); + huEngine->registerEntry("zh_TW", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(未知)"); + huEngine->registerEntry("zh_TW", TXT_KEY_PERMISSION_TITLE, "權限請求"); + huEngine->registerEntry("zh_TW", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "提示:您可以在 Hyprland 設定檔中為此建立永久規則。"); + huEngine->registerEntry("zh_TW", TXT_KEY_PERMISSION_ALLOW, "允許"); + huEngine->registerEntry("zh_TW", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "總是允許"); + huEngine->registerEntry("zh_TW", TXT_KEY_PERMISSION_ALLOW_ONCE, "僅允許一次"); + huEngine->registerEntry("zh_TW", TXT_KEY_PERMISSION_DENY, "拒絕"); + huEngine->registerEntry("zh_TW", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "未知的應用程式 (Wayland 用戶端 ID {wayland_id})"); + + huEngine->registerEntry("zh_TW", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "您的 XDG_CURRENT_DESKTOP 環境變數似乎由外部管理,目前的值為 {value}。\n除非您有意為之,否則這可能會導致問題。"); + huEngine->registerEntry("zh_TW", TXT_KEY_NOTIF_NO_GUIUTILS, "您的系統未安裝 hyprland-guiutils。這是部分對話視窗的執行期依賴元件。建議您安裝它。"); + huEngine->registerEntry("zh_TW", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo <= 1) + return "Hyprland 無法載入 {count} 個必要資源,去怪那個把發行版打包成這副德性的維護者!"; + return "Hyprland 無法載入 {count} 個必要資源,去怪那個把發行版打包成這副德性的維護者!"; + }); + huEngine->registerEntry("zh_TW", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "您的螢幕配置設定不正確。螢幕 {name} 與配置中的其他螢幕重疊了。\n請參閱 Wiki(螢幕頁面)以了解詳情。這絕對會導致問題。"); + huEngine->registerEntry("zh_TW", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "螢幕 {name} 無法設定為任何請求的模式,將改用模式 {mode}。"); + huEngine->registerEntry("zh_TW", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "傳遞給螢幕 {name} 的縮放比例無效:{scale},將使用建議的比例:{fixed_scale}"); + huEngine->registerEntry("zh_TW", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "無法載入外掛 {name}:{error}"); + huEngine->registerEntry("zh_TW", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "CM 著色器重新載入失敗,將退回使用 rgba/rgbx。"); + huEngine->registerEntry("zh_TW", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "螢幕 {name}:已啟用廣色域,但顯示器並非處於 10-bit 模式。"); + huEngine->registerEntry("zh_TW", TXT_KEY_NOTIF_NO_WATCHDOG, "Hyprland 啟動時未使用 start-hyprland wrapper。除非您處於除錯環境,否則極度不建議這麼做。"); + + huEngine->registerEntry("zh_TW", TXT_KEY_SAFE_MODE_TITLE, "安全模式"); + huEngine->registerEntry("zh_TW", TXT_KEY_SAFE_MODE_DESCRIPTION, + "Hyprland " + "已在安全模式下啟動,這代表您的上個工作階段當機。\n安全模式會阻止載入您的設定檔。您可以在此環境中進行故障排除,或使用下方按鈕載入您的設定。\n預設快" + "捷鍵適用:SUPER+Q 開啟 Kitty,SUPER+R 開啟簡易啟動器,SUPER+M 退出。\n重新啟動 " + "Hyprland 將再次進入正常模式。"); + huEngine->registerEntry("zh_TW", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, "載入設定檔"); + huEngine->registerEntry("zh_TW", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, "開啟當機報告目錄"); + huEngine->registerEntry("zh_TW", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, "好,關閉視窗"); // ar (Arabic - Modern Standard) huEngine->registerEntry("ar", TXT_KEY_ANR_TITLE, "التطبيق لا يستجيب"); @@ -1141,6 +1253,62 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("ar", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, "افتح مجلد تقرير الانهيار"); huEngine->registerEntry("ar", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, "حسنًا، أغلق هذا"); + // ro_RO (Romanian) + huEngine->registerEntry("ro_RO", TXT_KEY_ANR_TITLE, "Aplicația Nu Răspunde"); + huEngine->registerEntry("ro_RO", TXT_KEY_ANR_CONTENT, "O aplicație {title} - {class} nu răspunde.\nCe vrei să faci cu ea?"); + huEngine->registerEntry("ro_RO", TXT_KEY_ANR_OPTION_TERMINATE, "Închide"); + huEngine->registerEntry("ro_RO", TXT_KEY_ANR_OPTION_WAIT, "Așteaptă"); + huEngine->registerEntry("ro_RO", TXT_KEY_ANR_PROP_UNKNOWN, "(necunoscut)"); + + huEngine->registerEntry("ro_RO", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "O aplicație {app} solicită o permisiune necunoscută."); + huEngine->registerEntry("ro_RO", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "O aplicație {app} încearcă să captureze ecranul.\n\nDorești să îi permiți acest lucru?"); + huEngine->registerEntry("ro_RO", TXT_KEY_PERMISSION_REQUEST_PLUGIN, + "O aplicație {app} încearcă să încarce un plugin: {plugin}.\n\nDorești să îi permiți acest lucru?"); + huEngine->registerEntry("ro_RO", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "A fost detectată o tastatură nouă: {keyboard}.\n\nDorești să îi permiți să funcționeze?"); + huEngine->registerEntry("ro_RO", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(necunoscut)"); + huEngine->registerEntry("ro_RO", TXT_KEY_PERMISSION_TITLE, "Cerere de permisiune"); + huEngine->registerEntry("ro_RO", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Indiciu: poți seta reguli persistente pentru acestea în fișierul de configurare Hyprland."); + huEngine->registerEntry("ro_RO", TXT_KEY_PERMISSION_ALLOW, "Permite"); + huEngine->registerEntry("ro_RO", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Permite și reține"); + huEngine->registerEntry("ro_RO", TXT_KEY_PERMISSION_ALLOW_ONCE, "Permite o dată"); + huEngine->registerEntry("ro_RO", TXT_KEY_PERMISSION_DENY, "Respinge"); + huEngine->registerEntry("ro_RO", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Aplicație necunoscută (ID client wayland {wayland_id})"); + + huEngine->registerEntry("ro_RO", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Se pare că mediul tău XDG_CURRENT_DESKTOP este gestionat extern, iar valoarea curentă este {value}.\nAcest lucru ar putea cauza probleme, cu excepția " + "cazului în care este intenționat."); + huEngine->registerEntry( + "ro_RO", TXT_KEY_NOTIF_NO_GUIUTILS, + "Sistemul tău nu are instalat hyprland-guiutils. Aceasta este o dependență de execuție pentru anumite dialoguri. Ia în considerare instalarea acesteia."); + huEngine->registerEntry("ro_RO", TXT_KEY_NOTIF_FAILED_ASSETS, [](const Hyprutils::I18n::translationVarMap& vars) { + int assetsNo = std::stoi(vars.at("count")); + if (assetsNo == 1) + return "Hyprland nu a reușit să încarce un element esențial. Dă vina pe packager-ul distro-ului tău că a făcut o treabă proastă la ambalare!"; + return "Hyprland nu a reușit să încarce {count} elemente esențiale. Dă vina pe packager-ul distro-ului tău că a făcut o treabă proastă la ambalare!"; + }); + huEngine->registerEntry("ro_RO", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Configurația monitorului este incorectă. Monitorul {name} se suprapune cu alte monitoare.\nConsultați wiki-ul (pagina Monitoare) pentru " + "mai multe informații. Acest lucru va cauza probleme."); + huEngine->registerEntry("ro_RO", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Monitorul {name} nu a reușit să seteze niciun mod solicitat, revenind la modul {mode}."); + huEngine->registerEntry("ro_RO", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Scară nevalidă transmisă monitorului {name}: {scale}, se utilizează scara sugerată: {fixed_scale}"); + huEngine->registerEntry("ro_RO", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Nu s-a putut încărca pluginul {name}: {error}"); + huEngine->registerEntry("ro_RO", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Reîncărcarea shaderului CM a eșuat, revenind la rgba/rgbx."); + huEngine->registerEntry("ro_RO", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Monitor {name}: gama largă de culori este activată, dar afișajul nu este în modul pe 10 biți."); + huEngine->registerEntry("ro_RO", TXT_KEY_NOTIF_NO_WATCHDOG, + "Hyprland a fost pornit fără start-hyprland. Acest lucru nu este recomandat decât dacă te afli într-un mediu de depanare."); + + huEngine->registerEntry("ro_RO", TXT_KEY_SAFE_MODE_TITLE, "Modul de Siguranță"); + huEngine->registerEntry( + "ro_RO", TXT_KEY_SAFE_MODE_DESCRIPTION, + "Hyprland a fost lansat în modul de siguranță, ceea ce înseamnă că ultima sesiune s-a blocat.\nModul de siguranță împiedică încărcarea configurației. Poți " + "depana în acest mediu sau să încarci configurația cu butonul de mai jos.\nSe aplică combinațiile de taste implicite: SUPER+Q pentru kitty, SUPER+R pentru un runner de " + "bază." + "SUPER+M pentru ieșire.\nLa repornire " + "Hyprland se va lansa din nou în modul normal."); + huEngine->registerEntry("ro_RO", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, "Încarcă configurația"); + huEngine->registerEntry("ro_RO", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, "Deschide locația rapoartelor de crash-uri"); + huEngine->registerEntry("ro_RO", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, "Ok, închide"); + // ru_RU (Russian) huEngine->registerEntry("ru_RU", TXT_KEY_ANR_TITLE, "Приложение не отвечает"); huEngine->registerEntry("ru_RU", TXT_KEY_ANR_CONTENT, "Приложение {title} - {class} не отвечает.\nЧто вы хотите сделать?"); @@ -1179,6 +1347,17 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("ru_RU", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Не удалось загрузить плагин {name}: {error}"); huEngine->registerEntry("ru_RU", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Не удалось перезагрузить CM shader, используется rgba/rgbx."); huEngine->registerEntry("ru_RU", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Монитор {name}: расширенный цветовой охват включён, но дисплей не в 10-bit режиме."); + huEngine->registerEntry("ru_RU", TXT_KEY_NOTIF_NO_WATCHDOG, "Hyprland был запущен без start-hyprland. Это крайне не рекомендуется, если только вы не в отладочной среде."); + + huEngine->registerEntry("ru_RU", TXT_KEY_SAFE_MODE_TITLE, "Безопасный режим"); + huEngine->registerEntry( + "ru_RU", TXT_KEY_SAFE_MODE_DESCRIPTION, + "Hyprland запущен в безопасном режиме, это значит, что ваш прошлый сеанс завершился сбоем.\nБезопасный режим не загружает ваш конфиг. Вы можете " + "исправить проблему в этом окружении или загрузить конфиг кнопкой ниже.\nДействуют стандартные бинды: SUPER+Q запускает kitty, SUPER+R открывает лаунчер, " + "SUPER+M для выхода.\nПосле перезапуска Hyprland снова запустится в обычном режиме."); + huEngine->registerEntry("ru_RU", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, "Загрузить конфиг"); + huEngine->registerEntry("ru_RU", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, "Открыть каталог отчётов о сбоях"); + huEngine->registerEntry("ru_RU", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, "Ок, закрыть"); // sl_SI (Slovenian) huEngine->registerEntry("sl_SI", TXT_KEY_ANR_TITLE, "Program se ne odziva"); @@ -1417,6 +1596,53 @@ I18n::CI18nEngine::CI18nEngine() { huEngine->registerEntry("uk_UA", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Не вдалося перезавантажити шейдер CM, повернення до rgba/rgbx."); huEngine->registerEntry("uk_UA", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Монітор {name}: широка кольорова гама увімкнена, але дисплей не працює в 10-бітному режимі."); + // vi_VN (Vietnamese) + huEngine->registerEntry("vi_VN", TXT_KEY_ANR_TITLE, "Ứng dụng không phản hồi"); + huEngine->registerEntry("vi_VN", TXT_KEY_ANR_CONTENT, "Ứng dụng {title} - {class} đang bị treo.\nBạn muốn xử lý thế nào?"); + huEngine->registerEntry("vi_VN", TXT_KEY_ANR_OPTION_TERMINATE, "Buộc dừng"); + huEngine->registerEntry("vi_VN", TXT_KEY_ANR_OPTION_WAIT, "Chờ"); + huEngine->registerEntry("vi_VN", TXT_KEY_ANR_PROP_UNKNOWN, "(không xác định)"); + + huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_REQUEST_UNKNOWN, "Ứng dụng {app} đang yêu cầu một quyền không xác định."); + huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, "Ứng dụng {app} đang cố gắng ghi hình màn hình của bạn.\n\nBạn muốn cho phép không?"); + huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_REQUEST_CURSOR_POS, "Ứng dụng {app} đang cố gắng đọc vị trí chuột của bạn.\n\nBạn muốn cho phép không?"); + huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_REQUEST_PLUGIN, "Ứng dụng {app} đang cố gắng tải plugin: {plugin}.\n\nBạn muốn cho phép không?"); + huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_REQUEST_KEYBOARD, "Phát hiện bàn phím mới: {keyboard}.\n\nBạn muốn cho phép bàn phím này hoạt động không?"); + huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_UNKNOWN_NAME, "(không xác định)"); + huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_TITLE, "Yêu cầu cấp quyền"); + huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_PERSISTENCE_HINT, "Gợi ý: bạn có thể thiết lập các quyền này trong tệp cấu hình Hyprland."); + huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_ALLOW, "Cho phép"); + huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_ALLOW_AND_REMEMBER, "Cho phép và ghi nhớ"); + huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_ALLOW_ONCE, "Chỉ một lần"); + huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_DENY, "Từ chối"); + huEngine->registerEntry("vi_VN", TXT_KEY_PERMISSION_UNKNOWN_WAYLAND_APP, "Ứng dụng không xác định (wayland client ID {wayland_id})"); + + huEngine->registerEntry( + "vi_VN", TXT_KEY_NOTIF_EXTERNAL_XDG_DESKTOP, + "Biến môi trường XDG_CURRENT_DESKTOP dường như đang được thiết lập từ bên ngoài với giá trị là {value}.\nViệc này có thể gây ra lỗi trừ khi đó là chủ ý của bạn."); + huEngine->registerEntry("vi_VN", TXT_KEY_NOTIF_NO_GUIUTILS, "Hệ thống chưa cài hyprland-guiutils. Một số hộp thoại sẽ không hiển thị nếu thiếu nó."); + huEngine->registerEntry("vi_VN", TXT_KEY_NOTIF_FAILED_ASSETS, + "Hyprland không thể tải {count} tài nguyên quan trọng. Vui lòng báo lỗi cho người đóng gói (packager) của bản phân phối (distro) mà bạn dùng!"); + huEngine->registerEntry("vi_VN", TXT_KEY_NOTIF_INVALID_MONITOR_LAYOUT, + "Bố cục màn hình không hợp lệ. Màn hình {name} đang bị đè lên các màn hình khác.\nVui lòng xem trang Monitors trên wiki để " + "khắc phục, nếu không chắc chắn sẽ có lỗi xảy ra."); + huEngine->registerEntry("vi_VN", TXT_KEY_NOTIF_MONITOR_MODE_FAIL, "Màn hình {name} không thể áp dụng chế độ nào được yêu cầu, đang dùng tạm chế độ {mode}."); + huEngine->registerEntry("vi_VN", TXT_KEY_NOTIF_MONITOR_AUTO_SCALE, "Tỉ lệ {scale} cho màn hình {name} không hợp lệ, chuyển sang tỷ lệ gợi ý: {fixed_scale}"); + huEngine->registerEntry("vi_VN", TXT_KEY_NOTIF_FAILED_TO_LOAD_PLUGIN, "Lỗi tải plugin {name}: {error}"); + huEngine->registerEntry("vi_VN", TXT_KEY_NOTIF_CM_RELOAD_FAILED, "Tải lại CM shader thất bại, đang dùng tạm rgba/rgbx."); + huEngine->registerEntry("vi_VN", TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, "Màn hình {name}: dải màu rộng (wide color gamut) khả dụng nhưng màn hình không ở chế độ 10-bit."); + huEngine->registerEntry("vi_VN", TXT_KEY_NOTIF_NO_WATCHDOG, + "Hyprland đã được khởi động mà không thông qua start-hyprland. Việc này không được khuyến khích trừ khi dùng cho mục đích gỡ lỗi."); + + huEngine->registerEntry("vi_VN", TXT_KEY_SAFE_MODE_TITLE, "Chế độ An toàn (Safe Mode)"); + huEngine->registerEntry("vi_VN", TXT_KEY_SAFE_MODE_DESCRIPTION, + "Phiên hoạt động trước đó đã bị sập (crash).\nHyprland hiện đang chạy ở chế độ an toàn và không tải tệp cấu hình của bạn. Bạn có thể " + "khắc phục sự cố trong môi trường này, hoặc bấm nút bên dưới để thử tải lại cấu hình.\nCác phím tắt mặc định: SUPER+Q (kitty), SUPER+R (runner), " + "SUPER+M (exit).\nHyprland sẽ về chế độ bình thường sau khi khởi động lại."); + huEngine->registerEntry("vi_VN", TXT_KEY_SAFE_MODE_BUTTON_LOAD_CONFIG, "Tải cấu hình"); + huEngine->registerEntry("vi_VN", TXT_KEY_SAFE_MODE_BUTTON_OPEN_CRASH_REPORT_DIR, "Mở thư mục báo cáo lỗi (crash report)"); + huEngine->registerEntry("vi_VN", TXT_KEY_SAFE_MODE_BUTTON_UNDERSTOOD, "OK, đã hiểu"); + // cs_CZ (Czech) huEngine->registerEntry("cs_CZ", TXT_KEY_ANR_TITLE, "Aplikace Neodpovídá"); huEngine->registerEntry("cs_CZ", TXT_KEY_ANR_CONTENT, "Aplikace {title} - {class} neodpovídá.\nCo s ní chcete udělat?"); diff --git a/src/i18n/Engine.hpp b/src/i18n/Engine.hpp index c3892546..79ec86f8 100644 --- a/src/i18n/Engine.hpp +++ b/src/i18n/Engine.hpp @@ -16,6 +16,7 @@ namespace I18n { TXT_KEY_PERMISSION_REQUEST_UNKNOWN, TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, + TXT_KEY_PERMISSION_REQUEST_CURSOR_POS, TXT_KEY_PERMISSION_REQUEST_PLUGIN, TXT_KEY_PERMISSION_REQUEST_KEYBOARD, TXT_KEY_PERMISSION_UNKNOWN_NAME, @@ -54,4 +55,4 @@ namespace I18n { }; SP i18nEngine(); -}; \ No newline at end of file +}; diff --git a/src/layout/DwindleLayout.cpp b/src/layout/DwindleLayout.cpp deleted file mode 100644 index 70d052ea..00000000 --- a/src/layout/DwindleLayout.cpp +++ /dev/null @@ -1,1190 +0,0 @@ -#include "DwindleLayout.hpp" -#include "../Compositor.hpp" -#include "../config/ConfigValue.hpp" -#include "../config/ConfigManager.hpp" -#include "../render/decorations/CHyprGroupBarDecoration.hpp" -#include "../render/Renderer.hpp" -#include "../managers/input/InputManager.hpp" -#include "../managers/LayoutManager.hpp" -#include "../managers/EventManager.hpp" -#include "../desktop/state/FocusState.hpp" -#include "xwayland/XWayland.hpp" - -void SDwindleNodeData::recalcSizePosRecursive(bool force, bool horizontalOverride, bool verticalOverride) { - if (children[0]) { - static auto PSMARTSPLIT = CConfigValue("dwindle:smart_split"); - static auto PPRESERVESPLIT = CConfigValue("dwindle:preserve_split"); - static auto PFLMULT = CConfigValue("dwindle:split_width_multiplier"); - - if (*PPRESERVESPLIT == 0 && *PSMARTSPLIT == 0) - splitTop = box.h * *PFLMULT > box.w; - - if (verticalOverride) - splitTop = true; - else if (horizontalOverride) - splitTop = false; - - const auto SPLITSIDE = !splitTop; - - if (SPLITSIDE) { - // split left/right - const float FIRSTSIZE = box.w / 2.0 * splitRatio; - children[0]->box = CBox{box.x, box.y, FIRSTSIZE, box.h}.noNegativeSize(); - children[1]->box = CBox{box.x + FIRSTSIZE, box.y, box.w - FIRSTSIZE, box.h}.noNegativeSize(); - } else { - // split top/bottom - const float FIRSTSIZE = box.h / 2.0 * splitRatio; - children[0]->box = CBox{box.x, box.y, box.w, FIRSTSIZE}.noNegativeSize(); - children[1]->box = CBox{box.x, box.y + FIRSTSIZE, box.w, box.h - FIRSTSIZE}.noNegativeSize(); - } - - children[0]->recalcSizePosRecursive(force); - children[1]->recalcSizePosRecursive(force); - } else { - layout->applyNodeDataToWindow(self.lock(), force); - } -} - -void SDwindleNodeData::applyRootBox() { - box = layout->workAreaOnWorkspace(g_pCompositor->getWorkspaceByID(workspaceID)); -} - -int CHyprDwindleLayout::getNodesOnWorkspace(const WORKSPACEID& id) { - int no = 0; - for (auto const& n : m_dwindleNodesData) { - if (n->workspaceID == id && n->valid) - ++no; - } - return no; -} - -SP CHyprDwindleLayout::getFirstNodeOnWorkspace(const WORKSPACEID& id) { - for (auto& n : m_dwindleNodesData) { - if (n->workspaceID == id && validMapped(n->pWindow)) - return n; - } - return nullptr; -} - -SP CHyprDwindleLayout::getClosestNodeOnWorkspace(const WORKSPACEID& id, const Vector2D& point) { - SP res = nullptr; - double distClosest = -1; - for (auto& n : m_dwindleNodesData) { - if (n->workspaceID == id && validMapped(n->pWindow)) { - auto distAnother = vecToRectDistanceSquared(point, n->box.pos(), n->box.pos() + n->box.size()); - if (!res || distAnother < distClosest) { - res = n; - distClosest = distAnother; - } - } - } - return res; -} - -SP CHyprDwindleLayout::getNodeFromWindow(PHLWINDOW pWindow) { - for (auto& n : m_dwindleNodesData) { - if (n->pWindow.lock() == pWindow && !n->isNode) - return n; - } - - return nullptr; -} - -SP CHyprDwindleLayout::getMasterNodeOnWorkspace(const WORKSPACEID& id) { - for (auto& n : m_dwindleNodesData) { - if (!n->pParent && n->workspaceID == id) - return n; - } - return nullptr; -} - -void CHyprDwindleLayout::applyNodeDataToWindow(SP pNode, bool force) { - // Don't set nodes, only windows. - if (pNode->isNode) - return; - - PHLMONITOR PMONITOR = nullptr; - - const auto WS = g_pCompositor->getWorkspaceByID(pNode->workspaceID); - - if (g_pCompositor->isWorkspaceSpecial(pNode->workspaceID)) { - for (auto const& m : g_pCompositor->m_monitors) { - if (m->activeSpecialWorkspaceID() == pNode->workspaceID) { - PMONITOR = m; - break; - } - } - } else if (WS) - PMONITOR = WS->m_monitor.lock(); - - if (!PMONITOR || !WS) { - Log::logger->log(Log::ERR, "Orphaned Node {}!!", pNode); - return; - } - - // for gaps outer - const auto MONITOR_WORKAREA = workAreaOnWorkspace(WS); - const bool DISPLAYLEFT = STICKS(pNode->box.x, MONITOR_WORKAREA.x); - const bool DISPLAYRIGHT = STICKS(pNode->box.x + pNode->box.w, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w); - const bool DISPLAYTOP = STICKS(pNode->box.y, MONITOR_WORKAREA.y); - const bool DISPLAYBOTTOM = STICKS(pNode->box.y + pNode->box.h, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h); - - const auto PWINDOW = pNode->pWindow.lock(); - // get specific gaps and rules for this workspace, - // if user specified them in config - const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(g_pCompositor->getWorkspaceByID(pNode->workspaceID)); - - if (!validMapped(PWINDOW)) { - Log::logger->log(Log::ERR, "Node {} holding invalid {}!!", pNode, PWINDOW); - onWindowRemovedTiling(PWINDOW); - return; - } - - if (PWINDOW->isFullscreen() && !pNode->ignoreFullscreenChecks) - return; - - PWINDOW->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT); - PWINDOW->updateWindowData(); - - static auto PGAPSINDATA = CConfigValue("general:gaps_in"); - auto* const PGAPSIN = sc((PGAPSINDATA.ptr())->getData()); - - auto gapsIn = WORKSPACERULE.gapsIn.value_or(*PGAPSIN); - CBox nodeBox = pNode->box; - nodeBox.round(); - - PWINDOW->m_size = nodeBox.size(); - PWINDOW->m_position = nodeBox.pos(); - - PWINDOW->updateWindowDecos(); - - auto calcPos = PWINDOW->m_position; - auto calcSize = PWINDOW->m_size; - - const static auto REQUESTEDRATIO = CConfigValue("dwindle:single_window_aspect_ratio"); - const static auto REQUESTEDRATIOTOLERANCE = CConfigValue("dwindle:single_window_aspect_ratio_tolerance"); - - Vector2D ratioPadding; - - if ((*REQUESTEDRATIO).y != 0 && !pNode->pParent) { - const Vector2D originalSize = MONITOR_WORKAREA.size(); - - const double requestedRatio = (*REQUESTEDRATIO).x / (*REQUESTEDRATIO).y; - const double originalRatio = originalSize.x / originalSize.y; - - if (requestedRatio > originalRatio) { - double padding = originalSize.y - (originalSize.x / requestedRatio); - - if (padding / 2 > (*REQUESTEDRATIOTOLERANCE) * originalSize.y) - ratioPadding = Vector2D{0., padding}; - } else if (requestedRatio < originalRatio) { - double padding = originalSize.x - (originalSize.y * requestedRatio); - - if (padding / 2 > (*REQUESTEDRATIOTOLERANCE) * originalSize.x) - ratioPadding = Vector2D{padding, 0.}; - } - } - - const auto GAPOFFSETTOPLEFT = Vector2D(sc(DISPLAYLEFT ? 0 : gapsIn.m_left), sc(DISPLAYTOP ? 0 : gapsIn.m_top)); - - const auto GAPOFFSETBOTTOMRIGHT = Vector2D(sc(DISPLAYRIGHT ? 0 : gapsIn.m_right), sc(DISPLAYBOTTOM ? 0 : gapsIn.m_bottom)); - - calcPos = calcPos + GAPOFFSETTOPLEFT + ratioPadding / 2; - calcSize = calcSize - GAPOFFSETTOPLEFT - GAPOFFSETBOTTOMRIGHT - ratioPadding; - - if (PWINDOW->m_isPseudotiled) { - // Calculate pseudo - float scale = 1; - - // adjust if doesn't fit - if (PWINDOW->m_pseudoSize.x > calcSize.x || PWINDOW->m_pseudoSize.y > calcSize.y) { - if (PWINDOW->m_pseudoSize.x > calcSize.x) { - scale = calcSize.x / PWINDOW->m_pseudoSize.x; - } - - if (PWINDOW->m_pseudoSize.y * scale > calcSize.y) { - scale = calcSize.y / PWINDOW->m_pseudoSize.y; - } - - auto DELTA = calcSize - PWINDOW->m_pseudoSize * scale; - calcSize = PWINDOW->m_pseudoSize * scale; - calcPos = calcPos + DELTA / 2.f; // center - } else { - auto DELTA = calcSize - PWINDOW->m_pseudoSize; - calcPos = calcPos + DELTA / 2.f; // center - calcSize = PWINDOW->m_pseudoSize; - } - } - - const auto RESERVED = PWINDOW->getFullWindowReservedArea(); - calcPos = calcPos + RESERVED.topLeft; - calcSize = calcSize - (RESERVED.topLeft + RESERVED.bottomRight); - - Vector2D availableSpace = calcSize; - - static auto PCLAMP_TILED = CConfigValue("misc:size_limits_tiled"); - - if (*PCLAMP_TILED) { - const auto borderSize = PWINDOW->getRealBorderSize(); - Vector2D monitorAvailable = MONITOR_WORKAREA.size() - Vector2D{2.0 * borderSize, 2.0 * borderSize}; - - Vector2D minSize = PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}).clamp(Vector2D{0, 0}, monitorAvailable); - Vector2D maxSize = PWINDOW->isFullscreen() ? Vector2D{INFINITY, INFINITY} : - PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY}).clamp(Vector2D{0, 0}, monitorAvailable); - calcSize = calcSize.clamp(minSize, maxSize); - - calcPos += (availableSpace - calcSize) / 2.0; - - calcPos.x = std::clamp(calcPos.x, MONITOR_WORKAREA.x + borderSize, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w - calcSize.x - borderSize); - calcPos.y = std::clamp(calcPos.y, MONITOR_WORKAREA.y + borderSize, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h - calcSize.y - borderSize); - } - - if (PWINDOW->onSpecialWorkspace() && !PWINDOW->isFullscreen()) { - // if special, we adjust the coords a bit - static auto PSCALEFACTOR = CConfigValue("dwindle:special_scale_factor"); - - CBox wb = {calcPos + (calcSize - calcSize * *PSCALEFACTOR) / 2.f, calcSize * *PSCALEFACTOR}; - wb.round(); // avoid rounding mess - - *PWINDOW->m_realPosition = wb.pos(); - *PWINDOW->m_realSize = wb.size(); - } else { - CBox wb = {calcPos, calcSize}; - wb.round(); // avoid rounding mess - - *PWINDOW->m_realSize = wb.size(); - *PWINDOW->m_realPosition = wb.pos(); - } - - if (force) { - g_pHyprRenderer->damageWindow(PWINDOW); - - PWINDOW->m_realPosition->warp(); - PWINDOW->m_realSize->warp(); - - g_pHyprRenderer->damageWindow(PWINDOW); - } - - PWINDOW->updateWindowDecos(); -} - -void CHyprDwindleLayout::onWindowCreatedTiling(PHLWINDOW pWindow, eDirection direction) { - if (pWindow->m_isFloating) - return; - - const auto PNODE = m_dwindleNodesData.emplace_back(makeShared()); - PNODE->self = PNODE; - - const auto PMONITOR = pWindow->m_monitor.lock(); - - static auto PUSEACTIVE = CConfigValue("dwindle:use_active_for_splits"); - static auto PDEFAULTSPLIT = CConfigValue("dwindle:default_split_ratio"); - - if (direction != DIRECTION_DEFAULT && m_overrideDirection == DIRECTION_DEFAULT) - m_overrideDirection = direction; - - // Populate the node with our window's data - PNODE->workspaceID = pWindow->workspaceID(); - PNODE->pWindow = pWindow; - PNODE->isNode = false; - PNODE->layout = this; - - SP OPENINGON; - - const auto MOUSECOORDS = m_overrideFocalPoint.value_or(g_pInputManager->getMouseCoordsInternal()); - const auto MONFROMCURSOR = g_pCompositor->getMonitorFromVector(MOUSECOORDS); - - if (PMONITOR->m_id == MONFROMCURSOR->m_id && - (PNODE->workspaceID == PMONITOR->activeWorkspaceID() || (g_pCompositor->isWorkspaceSpecial(PNODE->workspaceID) && PMONITOR->m_activeSpecialWorkspace)) && !*PUSEACTIVE) { - OPENINGON = getNodeFromWindow( - g_pCompositor->vectorToWindowUnified(MOUSECOORDS, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::SKIP_FULLSCREEN_PRIORITY)); - - if (!OPENINGON && g_pCompositor->isPointOnReservedArea(MOUSECOORDS, PMONITOR)) - OPENINGON = getClosestNodeOnWorkspace(PNODE->workspaceID, MOUSECOORDS); - - } else if (*PUSEACTIVE) { - if (Desktop::focusState()->window() && !Desktop::focusState()->window()->m_isFloating && Desktop::focusState()->window() != pWindow && - Desktop::focusState()->window()->m_workspace == pWindow->m_workspace && Desktop::focusState()->window()->m_isMapped) { - OPENINGON = getNodeFromWindow(Desktop::focusState()->window()); - } else { - OPENINGON = getNodeFromWindow(g_pCompositor->vectorToWindowUnified(MOUSECOORDS, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS)); - } - - if (!OPENINGON && g_pCompositor->isPointOnReservedArea(MOUSECOORDS, PMONITOR)) - OPENINGON = getClosestNodeOnWorkspace(PNODE->workspaceID, MOUSECOORDS); - - } else - OPENINGON = getFirstNodeOnWorkspace(pWindow->workspaceID()); - - Log::logger->log(Log::DEBUG, "OPENINGON: {}, Monitor: {}", OPENINGON, PMONITOR->m_id); - - if (OPENINGON && OPENINGON->workspaceID != PNODE->workspaceID) { - // special workspace handling - OPENINGON = getFirstNodeOnWorkspace(PNODE->workspaceID); - } - - // first, check if OPENINGON isn't too big. - const auto PREDSIZEMAX = OPENINGON ? Vector2D(OPENINGON->box.w, OPENINGON->box.h) : PMONITOR->m_size; - if (const auto MAXSIZE = pWindow->maxSize().value_or(Math::VECTOR2D_MAX); MAXSIZE.x < PREDSIZEMAX.x || MAXSIZE.y < PREDSIZEMAX.y) { - // we can't continue. make it floating. - pWindow->m_isFloating = true; - std::erase(m_dwindleNodesData, PNODE); - g_pLayoutManager->getCurrentLayout()->onWindowCreatedFloating(pWindow); - return; - } - - // last fail-safe to avoid duplicate fullscreens - if ((!OPENINGON || OPENINGON->pWindow.lock() == pWindow) && getNodesOnWorkspace(PNODE->workspaceID) > 1) { - for (auto& node : m_dwindleNodesData) { - if (node->workspaceID == PNODE->workspaceID && node->pWindow.lock() && node->pWindow.lock() != pWindow) { - OPENINGON = node; - break; - } - } - } - - // if it's the first, it's easy. Make it fullscreen. - if (!OPENINGON || OPENINGON->pWindow.lock() == pWindow) { - PNODE->applyRootBox(); - applyNodeDataToWindow(PNODE); - return; - } - - // get the node under our cursor - - const auto NEWPARENT = m_dwindleNodesData.emplace_back(makeShared()); - - // make the parent have the OPENINGON's stats - NEWPARENT->box = OPENINGON->box; - NEWPARENT->workspaceID = OPENINGON->workspaceID; - NEWPARENT->pParent = OPENINGON->pParent; - NEWPARENT->isNode = true; // it is a node - NEWPARENT->splitRatio = std::clamp(*PDEFAULTSPLIT, 0.1f, 1.9f); - NEWPARENT->layout = this; - - static auto PWIDTHMULTIPLIER = CConfigValue("dwindle:split_width_multiplier"); - - // if cursor over first child, make it first, etc - const auto SIDEBYSIDE = NEWPARENT->box.w > NEWPARENT->box.h * *PWIDTHMULTIPLIER; - NEWPARENT->splitTop = !SIDEBYSIDE; - - static auto PFORCESPLIT = CConfigValue("dwindle:force_split"); - static auto PERMANENTDIRECTIONOVERRIDE = CConfigValue("dwindle:permanent_direction_override"); - static auto PSMARTSPLIT = CConfigValue("dwindle:smart_split"); - static auto PSPLITBIAS = CConfigValue("dwindle:split_bias"); - - bool horizontalOverride = false; - bool verticalOverride = false; - - // let user select position -> top, right, bottom, left - if (m_overrideDirection != DIRECTION_DEFAULT) { - - // this is horizontal - if (m_overrideDirection % 2 == 0) - verticalOverride = true; - else - horizontalOverride = true; - - // 0 -> top and left | 1,2 -> right and bottom - if (m_overrideDirection % 3 == 0) { - NEWPARENT->children[1] = OPENINGON; - NEWPARENT->children[0] = PNODE; - } else { - NEWPARENT->children[0] = OPENINGON; - NEWPARENT->children[1] = PNODE; - } - - // whether or not the override persists after opening one window - if (*PERMANENTDIRECTIONOVERRIDE == 0) - m_overrideDirection = DIRECTION_DEFAULT; - } else if (*PSMARTSPLIT == 1) { - const auto PARENT_CENTER = NEWPARENT->box.pos() + NEWPARENT->box.size() / 2; - const auto PARENT_PROPORTIONS = NEWPARENT->box.h / NEWPARENT->box.w; - const auto DELTA = MOUSECOORDS - PARENT_CENTER; - const auto DELTA_SLOPE = DELTA.y / DELTA.x; - - if (abs(DELTA_SLOPE) < PARENT_PROPORTIONS) { - if (DELTA.x > 0) { - // right - NEWPARENT->splitTop = false; - NEWPARENT->children[0] = OPENINGON; - NEWPARENT->children[1] = PNODE; - } else { - // left - NEWPARENT->splitTop = false; - NEWPARENT->children[0] = PNODE; - NEWPARENT->children[1] = OPENINGON; - } - } else { - if (DELTA.y > 0) { - // bottom - NEWPARENT->splitTop = true; - NEWPARENT->children[0] = OPENINGON; - NEWPARENT->children[1] = PNODE; - } else { - // top - NEWPARENT->splitTop = true; - NEWPARENT->children[0] = PNODE; - NEWPARENT->children[1] = OPENINGON; - } - } - } else if (*PFORCESPLIT == 0 || !pWindow->m_firstMap) { - if ((SIDEBYSIDE && - VECINRECT(MOUSECOORDS, NEWPARENT->box.x, NEWPARENT->box.y / *PWIDTHMULTIPLIER, NEWPARENT->box.x + NEWPARENT->box.w / 2.f, NEWPARENT->box.y + NEWPARENT->box.h)) || - (!SIDEBYSIDE && - VECINRECT(MOUSECOORDS, NEWPARENT->box.x, NEWPARENT->box.y / *PWIDTHMULTIPLIER, NEWPARENT->box.x + NEWPARENT->box.w, NEWPARENT->box.y + NEWPARENT->box.h / 2.f))) { - // we are hovering over the first node, make PNODE first. - NEWPARENT->children[1] = OPENINGON; - NEWPARENT->children[0] = PNODE; - } else { - // we are hovering over the second node, make PNODE second. - NEWPARENT->children[0] = OPENINGON; - NEWPARENT->children[1] = PNODE; - } - } else { - if (*PFORCESPLIT == 1) { - NEWPARENT->children[1] = OPENINGON; - NEWPARENT->children[0] = PNODE; - } else { - NEWPARENT->children[0] = OPENINGON; - NEWPARENT->children[1] = PNODE; - } - } - - // split in favor of a specific window - if (*PSPLITBIAS && NEWPARENT->children[0] == PNODE) - NEWPARENT->splitRatio = 2.f - NEWPARENT->splitRatio; - - // and update the previous parent if it exists - if (OPENINGON->pParent) { - if (OPENINGON->pParent->children[0] == OPENINGON) { - OPENINGON->pParent->children[0] = NEWPARENT; - } else { - OPENINGON->pParent->children[1] = NEWPARENT; - } - } - - // Update the children - if (!verticalOverride && (NEWPARENT->box.w * *PWIDTHMULTIPLIER > NEWPARENT->box.h || horizontalOverride)) { - // split left/right -> forced - OPENINGON->box = {NEWPARENT->box.pos(), Vector2D(NEWPARENT->box.w / 2.f, NEWPARENT->box.h)}; - PNODE->box = {Vector2D(NEWPARENT->box.x + NEWPARENT->box.w / 2.f, NEWPARENT->box.y), Vector2D(NEWPARENT->box.w / 2.f, NEWPARENT->box.h)}; - } else { - // split top/bottom - OPENINGON->box = {NEWPARENT->box.pos(), Vector2D(NEWPARENT->box.w, NEWPARENT->box.h / 2.f)}; - PNODE->box = {Vector2D(NEWPARENT->box.x, NEWPARENT->box.y + NEWPARENT->box.h / 2.f), Vector2D(NEWPARENT->box.w, NEWPARENT->box.h / 2.f)}; - } - - OPENINGON->pParent = NEWPARENT; - PNODE->pParent = NEWPARENT; - - NEWPARENT->recalcSizePosRecursive(false, horizontalOverride, verticalOverride); - - recalculateMonitor(pWindow->monitorID()); - pWindow->m_workspace->updateWindows(); -} - -void CHyprDwindleLayout::onWindowRemovedTiling(PHLWINDOW pWindow) { - - const auto PNODE = getNodeFromWindow(pWindow); - - if (!PNODE) { - Log::logger->log(Log::ERR, "onWindowRemovedTiling node null?"); - return; - } - - pWindow->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT); - pWindow->updateWindowData(); - - if (pWindow->isFullscreen()) - g_pCompositor->setWindowFullscreenInternal(pWindow, FSMODE_NONE); - - const auto PPARENT = PNODE->pParent; - - if (!PPARENT) { - Log::logger->log(Log::DEBUG, "Removing last node (dwindle)"); - std::erase(m_dwindleNodesData, PNODE); - return; - } - - const auto PSIBLING = PPARENT->children[0] == PNODE ? PPARENT->children[1] : PPARENT->children[0]; - - PSIBLING->box = PPARENT->box; - PSIBLING->pParent = PPARENT->pParent; - - if (PPARENT->pParent != nullptr) { - if (PPARENT->pParent->children[0] == PPARENT) { - PPARENT->pParent->children[0] = PSIBLING; - } else { - PPARENT->pParent->children[1] = PSIBLING; - } - } - - PPARENT->valid = false; - PNODE->valid = false; - - if (PSIBLING->pParent) - PSIBLING->pParent->recalcSizePosRecursive(); - else - PSIBLING->recalcSizePosRecursive(); - - std::erase(m_dwindleNodesData, PPARENT); - std::erase(m_dwindleNodesData, PNODE); - pWindow->m_workspace->updateWindows(); -} - -void CHyprDwindleLayout::recalculateMonitor(const MONITORID& monid) { - const auto PMONITOR = g_pCompositor->getMonitorFromID(monid); - - if (!PMONITOR || !PMONITOR->m_activeWorkspace) - return; // ??? - - g_pHyprRenderer->damageMonitor(PMONITOR); - - if (PMONITOR->m_activeSpecialWorkspace) - calculateWorkspace(PMONITOR->m_activeSpecialWorkspace); - - calculateWorkspace(PMONITOR->m_activeWorkspace); - -#ifndef NO_XWAYLAND - CBox box = g_pCompositor->calculateX11WorkArea(); - if (!g_pXWayland || !g_pXWayland->m_wm) - return; - g_pXWayland->m_wm->updateWorkArea(box.x, box.y, box.w, box.h); -#endif -} - -void CHyprDwindleLayout::calculateWorkspace(const PHLWORKSPACE& pWorkspace) { - const auto PMONITOR = pWorkspace->m_monitor.lock(); - - if (!PMONITOR) - return; - - if (pWorkspace->m_hasFullscreenWindow) { - // massive hack from the fullscreen func - const auto PFULLWINDOW = pWorkspace->getFullscreenWindow(); - - if (pWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN) { - *PFULLWINDOW->m_realPosition = PMONITOR->m_position; - *PFULLWINDOW->m_realSize = PMONITOR->m_size; - } else if (pWorkspace->m_fullscreenMode == FSMODE_MAXIMIZED) { - SP fakeNode = makeShared(); - fakeNode->self = fakeNode; - fakeNode->pWindow = PFULLWINDOW; - fakeNode->box = workAreaOnWorkspace(pWorkspace); - fakeNode->workspaceID = pWorkspace->m_id; - PFULLWINDOW->m_position = fakeNode->box.pos(); - PFULLWINDOW->m_size = fakeNode->box.size(); - fakeNode->ignoreFullscreenChecks = true; - fakeNode->layout = this; - - applyNodeDataToWindow(fakeNode); - } - - // if has fullscreen, don't calculate the rest - return; - } - - const auto TOPNODE = getMasterNodeOnWorkspace(pWorkspace->m_id); - - if (TOPNODE) { - TOPNODE->applyRootBox(); - TOPNODE->recalcSizePosRecursive(); - } -} - -bool CHyprDwindleLayout::isWindowTiled(PHLWINDOW pWindow) { - return getNodeFromWindow(pWindow) != nullptr; -} - -void CHyprDwindleLayout::onBeginDragWindow() { - m_pseudoDragFlags.started = false; - m_pseudoDragFlags.pseudo = false; - IHyprLayout::onBeginDragWindow(); -} - -void CHyprDwindleLayout::resizeActiveWindow(const Vector2D& pixResize, eRectCorner corner, PHLWINDOW pWindow) { - - const auto PWINDOW = pWindow ? pWindow : Desktop::focusState()->window(); - - if (!validMapped(PWINDOW)) - return; - - const auto PNODE = getNodeFromWindow(PWINDOW); - - if (!PNODE) { - *PWINDOW->m_realSize = (PWINDOW->m_realSize->goal() + pixResize) - .clamp(PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}), - PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY})); - PWINDOW->updateWindowDecos(); - return; - } - - static auto PANIMATE = CConfigValue("misc:animate_manual_resizes"); - static auto PSMARTRESIZING = CConfigValue("dwindle:smart_resizing"); - - // get some data about our window - const auto PMONITOR = PWINDOW->m_monitor.lock(); - const auto MONITOR_WORKAREA = PMONITOR->logicalBoxMinusReserved(); - const bool DISPLAYLEFT = STICKS(PWINDOW->m_position.x, MONITOR_WORKAREA.x); - const bool DISPLAYRIGHT = STICKS(PWINDOW->m_position.x + PWINDOW->m_size.x, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w); - const bool DISPLAYTOP = STICKS(PWINDOW->m_position.y, MONITOR_WORKAREA.y); - const bool DISPLAYBOTTOM = STICKS(PWINDOW->m_position.y + PWINDOW->m_size.y, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h); - - if (PWINDOW->m_isPseudotiled) { - if (!m_pseudoDragFlags.started) { - m_pseudoDragFlags.started = true; - - const auto pseudoSize = PWINDOW->m_realSize->goal(); - const auto mouseOffset = g_pInputManager->getMouseCoordsInternal() - (PNODE->box.pos() + ((PNODE->box.size() / 2) - (pseudoSize / 2))); - - if (mouseOffset.x > 0 && mouseOffset.x < pseudoSize.x && mouseOffset.y > 0 && mouseOffset.y < pseudoSize.y) { - m_pseudoDragFlags.pseudo = true; - m_pseudoDragFlags.xExtent = mouseOffset.x > pseudoSize.x / 2; - m_pseudoDragFlags.yExtent = mouseOffset.y > pseudoSize.y / 2; - - PWINDOW->m_pseudoSize = pseudoSize; - } else { - m_pseudoDragFlags.pseudo = false; - } - } - - if (m_pseudoDragFlags.pseudo) { - if (m_pseudoDragFlags.xExtent) - PWINDOW->m_pseudoSize.x += pixResize.x * 2; - else - PWINDOW->m_pseudoSize.x -= pixResize.x * 2; - if (m_pseudoDragFlags.yExtent) - PWINDOW->m_pseudoSize.y += pixResize.y * 2; - else - PWINDOW->m_pseudoSize.y -= pixResize.y * 2; - - CBox wbox = PNODE->box; - wbox.round(); - - Vector2D minSize = PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D{30.0, 30.0}); - Vector2D maxSize = PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY}); - Vector2D upperBound = Vector2D{std::min(maxSize.x, wbox.w), std::min(maxSize.y, wbox.h)}; - - PWINDOW->m_pseudoSize = PWINDOW->m_pseudoSize.clamp(minSize, upperBound); - - PWINDOW->m_lastFloatingSize = PWINDOW->m_pseudoSize; - PNODE->recalcSizePosRecursive(*PANIMATE == 0); - - return; - } - } - - // construct allowed movement - Vector2D allowedMovement = pixResize; - if (DISPLAYLEFT && DISPLAYRIGHT) - allowedMovement.x = 0; - - if (DISPLAYBOTTOM && DISPLAYTOP) - allowedMovement.y = 0; - - if (*PSMARTRESIZING == 1) { - // Identify inner and outer nodes for both directions - SP PVOUTER = nullptr; - SP PVINNER = nullptr; - SP PHOUTER = nullptr; - SP PHINNER = nullptr; - - const auto LEFT = corner == CORNER_TOPLEFT || corner == CORNER_BOTTOMLEFT || DISPLAYRIGHT; - const auto TOP = corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT || DISPLAYBOTTOM; - const auto RIGHT = corner == CORNER_TOPRIGHT || corner == CORNER_BOTTOMRIGHT || DISPLAYLEFT; - const auto BOTTOM = corner == CORNER_BOTTOMLEFT || corner == CORNER_BOTTOMRIGHT || DISPLAYTOP; - const auto NONE = corner == CORNER_NONE; - - for (auto PCURRENT = PNODE; PCURRENT && PCURRENT->pParent; PCURRENT = PCURRENT->pParent.lock()) { - const auto PPARENT = PCURRENT->pParent; - - if (!PVOUTER && PPARENT->splitTop && (NONE || (TOP && PPARENT->children[1] == PCURRENT) || (BOTTOM && PPARENT->children[0] == PCURRENT))) - PVOUTER = PCURRENT; - else if (!PVOUTER && !PVINNER && PPARENT->splitTop) - PVINNER = PCURRENT; - else if (!PHOUTER && !PPARENT->splitTop && (NONE || (LEFT && PPARENT->children[1] == PCURRENT) || (RIGHT && PPARENT->children[0] == PCURRENT))) - PHOUTER = PCURRENT; - else if (!PHOUTER && !PHINNER && !PPARENT->splitTop) - PHINNER = PCURRENT; - - if (PVOUTER && PHOUTER) - break; - } - - if (PHOUTER) { - PHOUTER->pParent->splitRatio = std::clamp(PHOUTER->pParent->splitRatio + allowedMovement.x * 2.f / PHOUTER->pParent->box.w, 0.1, 1.9); - - if (PHINNER) { - const auto ORIGINAL = PHINNER->box.w; - PHOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0); - if (PHINNER->pParent->children[0] == PHINNER) - PHINNER->pParent->splitRatio = std::clamp((ORIGINAL - allowedMovement.x) / PHINNER->pParent->box.w * 2.f, 0.1, 1.9); - else - PHINNER->pParent->splitRatio = std::clamp(2 - (ORIGINAL + allowedMovement.x) / PHINNER->pParent->box.w * 2.f, 0.1, 1.9); - PHINNER->pParent->recalcSizePosRecursive(*PANIMATE == 0); - } else - PHOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0); - } - - if (PVOUTER) { - PVOUTER->pParent->splitRatio = std::clamp(PVOUTER->pParent->splitRatio + allowedMovement.y * 2.f / PVOUTER->pParent->box.h, 0.1, 1.9); - - if (PVINNER) { - const auto ORIGINAL = PVINNER->box.h; - PVOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0); - if (PVINNER->pParent->children[0] == PVINNER) - PVINNER->pParent->splitRatio = std::clamp((ORIGINAL - allowedMovement.y) / PVINNER->pParent->box.h * 2.f, 0.1, 1.9); - else - PVINNER->pParent->splitRatio = std::clamp(2 - (ORIGINAL + allowedMovement.y) / PVINNER->pParent->box.h * 2.f, 0.1, 1.9); - PVINNER->pParent->recalcSizePosRecursive(*PANIMATE == 0); - } else - PVOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0); - } - } else { - // get the correct containers to apply splitratio to - const auto PPARENT = PNODE->pParent; - - if (!PPARENT) - return; // the only window on a workspace, ignore - - const bool PARENTSIDEBYSIDE = !PPARENT->splitTop; - - // Get the parent's parent - auto PPARENT2 = PPARENT->pParent; - - // No parent means we have only 2 windows, and thus one axis of freedom - if (!PPARENT2) { - if (PARENTSIDEBYSIDE) { - allowedMovement.x *= 2.f / PPARENT->box.w; - PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.x, 0.1, 1.9); - PPARENT->recalcSizePosRecursive(*PANIMATE == 0); - } else { - allowedMovement.y *= 2.f / PPARENT->box.h; - PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.y, 0.1, 1.9); - PPARENT->recalcSizePosRecursive(*PANIMATE == 0); - } - - return; - } - - // Get first parent with other split - while (PPARENT2 && PPARENT2->splitTop == !PARENTSIDEBYSIDE) - PPARENT2 = PPARENT2->pParent; - - // no parent, one axis of freedom - if (!PPARENT2) { - if (PARENTSIDEBYSIDE) { - allowedMovement.x *= 2.f / PPARENT->box.w; - PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.x, 0.1, 1.9); - PPARENT->recalcSizePosRecursive(*PANIMATE == 0); - } else { - allowedMovement.y *= 2.f / PPARENT->box.h; - PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.y, 0.1, 1.9); - PPARENT->recalcSizePosRecursive(*PANIMATE == 0); - } - - return; - } - - // 2 axes of freedom - const auto SIDECONTAINER = PARENTSIDEBYSIDE ? PPARENT : PPARENT2; - const auto TOPCONTAINER = PARENTSIDEBYSIDE ? PPARENT2 : PPARENT; - - allowedMovement.x *= 2.f / SIDECONTAINER->box.w; - allowedMovement.y *= 2.f / TOPCONTAINER->box.h; - - SIDECONTAINER->splitRatio = std::clamp(SIDECONTAINER->splitRatio + allowedMovement.x, 0.1, 1.9); - TOPCONTAINER->splitRatio = std::clamp(TOPCONTAINER->splitRatio + allowedMovement.y, 0.1, 1.9); - SIDECONTAINER->recalcSizePosRecursive(*PANIMATE == 0); - TOPCONTAINER->recalcSizePosRecursive(*PANIMATE == 0); - } -} - -void CHyprDwindleLayout::fullscreenRequestForWindow(PHLWINDOW pWindow, const eFullscreenMode CURRENT_EFFECTIVE_MODE, const eFullscreenMode EFFECTIVE_MODE) { - const auto PMONITOR = pWindow->m_monitor.lock(); - const auto PWORKSPACE = pWindow->m_workspace; - - // save position and size if floating - if (pWindow->m_isFloating && CURRENT_EFFECTIVE_MODE == FSMODE_NONE) { - pWindow->m_lastFloatingSize = pWindow->m_realSize->goal(); - pWindow->m_lastFloatingPosition = pWindow->m_realPosition->goal(); - pWindow->m_position = pWindow->m_realPosition->goal(); - pWindow->m_size = pWindow->m_realSize->goal(); - } - - if (EFFECTIVE_MODE == FSMODE_NONE) { - // if it got its fullscreen disabled, set back its node if it had one - const auto PNODE = getNodeFromWindow(pWindow); - if (PNODE) - applyNodeDataToWindow(PNODE); - else { - // get back its' dimensions from position and size - *pWindow->m_realPosition = pWindow->m_lastFloatingPosition; - *pWindow->m_realSize = pWindow->m_lastFloatingSize; - - pWindow->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT); - pWindow->updateWindowData(); - } - } else { - // apply new pos and size being monitors' box - if (EFFECTIVE_MODE == FSMODE_FULLSCREEN) { - *pWindow->m_realPosition = PMONITOR->m_position; - *pWindow->m_realSize = PMONITOR->m_size; - } else { - // This is a massive hack. - // We make a fake "only" node and apply - // To keep consistent with the settings without C+P code - - SP fakeNode = makeShared(); - fakeNode->self = fakeNode; - fakeNode->pWindow = pWindow; - fakeNode->box = PMONITOR->logicalBoxMinusReserved(); - fakeNode->workspaceID = pWindow->workspaceID(); - pWindow->m_position = fakeNode->box.pos(); - pWindow->m_size = fakeNode->box.size(); - fakeNode->ignoreFullscreenChecks = true; - fakeNode->layout = this; - - applyNodeDataToWindow(fakeNode); - } - } - - g_pCompositor->changeWindowZOrder(pWindow, true); -} - -void CHyprDwindleLayout::recalculateWindow(PHLWINDOW pWindow) { - const auto PNODE = getNodeFromWindow(pWindow); - - if (!PNODE) - return; - - PNODE->recalcSizePosRecursive(); -} - -SWindowRenderLayoutHints CHyprDwindleLayout::requestRenderHints(PHLWINDOW pWindow) { - // window should be valid, insallah - SWindowRenderLayoutHints hints; - - const auto PNODE = getNodeFromWindow(pWindow); - if (!PNODE) - return hints; // left for the future, maybe floating funkiness - - return hints; -} - -void CHyprDwindleLayout::moveWindowTo(PHLWINDOW pWindow, const std::string& dir, bool silent) { - if (!isDirection(dir)) - return; - - const auto PNODE = getNodeFromWindow(pWindow); - const auto originalWorkspaceID = pWindow->workspaceID(); - const Vector2D originalPos = pWindow->middle(); - - if (!PNODE || !pWindow->m_monitor) - return; - - Vector2D focalPoint; - - const auto WINDOWIDEALBB = pWindow->isFullscreen() ? CBox{pWindow->m_monitor->m_position, pWindow->m_monitor->m_size} : pWindow->getWindowIdealBoundingBoxIgnoreReserved(); - - switch (dir[0]) { - case 't': - case 'u': focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x / 2.0, -1.0}; break; - case 'd': - case 'b': focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x / 2.0, WINDOWIDEALBB.size().y + 1.0}; break; - case 'l': focalPoint = WINDOWIDEALBB.pos() + Vector2D{-1.0, WINDOWIDEALBB.size().y / 2.0}; break; - case 'r': focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x + 1.0, WINDOWIDEALBB.size().y / 2.0}; break; - default: UNREACHABLE(); - } - - pWindow->setAnimationsToMove(); - - onWindowRemovedTiling(pWindow); - - m_overrideFocalPoint = focalPoint; - - const auto PMONITORFOCAL = g_pCompositor->getMonitorFromVector(focalPoint); - - if (PMONITORFOCAL != pWindow->m_monitor) { - pWindow->moveToWorkspace(PMONITORFOCAL->m_activeWorkspace); - pWindow->m_monitor = PMONITORFOCAL; - } - - pWindow->updateGroupOutputs(); - if (!pWindow->m_groupData.pNextWindow.expired()) { - PHLWINDOW next = pWindow->m_groupData.pNextWindow.lock(); - while (next != pWindow) { - next->updateToplevel(); - next = next->m_groupData.pNextWindow.lock(); - } - } - - onWindowCreatedTiling(pWindow); - - m_overrideFocalPoint.reset(); - - // restore focus to the previous position - if (silent) { - const auto PNODETOFOCUS = getClosestNodeOnWorkspace(originalWorkspaceID, originalPos); - if (PNODETOFOCUS && PNODETOFOCUS->pWindow.lock()) - Desktop::focusState()->fullWindowFocus(PNODETOFOCUS->pWindow.lock()); - } -} - -void CHyprDwindleLayout::switchWindows(PHLWINDOW pWindow, PHLWINDOW pWindow2) { - // windows should be valid, insallah - - auto PNODE = getNodeFromWindow(pWindow); - auto PNODE2 = getNodeFromWindow(pWindow2); - - if (!PNODE2 || !PNODE) - return; - - const eFullscreenMode MODE1 = pWindow->m_fullscreenState.internal; - const eFullscreenMode MODE2 = pWindow2->m_fullscreenState.internal; - - g_pCompositor->setWindowFullscreenInternal(pWindow, FSMODE_NONE); - g_pCompositor->setWindowFullscreenInternal(pWindow2, FSMODE_NONE); - - SDwindleNodeData* ACTIVE1 = nullptr; - SDwindleNodeData* ACTIVE2 = nullptr; - - // swap the windows and recalc - PNODE2->pWindow = pWindow; - PNODE->pWindow = pWindow2; - - if (PNODE->workspaceID != PNODE2->workspaceID) { - std::swap(pWindow2->m_monitor, pWindow->m_monitor); - std::swap(pWindow2->m_workspace, pWindow->m_workspace); - } - - pWindow->setAnimationsToMove(); - pWindow2->setAnimationsToMove(); - - // recalc the workspace - getMasterNodeOnWorkspace(PNODE->workspaceID)->recalcSizePosRecursive(); - - if (PNODE2->workspaceID != PNODE->workspaceID) - getMasterNodeOnWorkspace(PNODE2->workspaceID)->recalcSizePosRecursive(); - - if (ACTIVE1) { - ACTIVE1->box = PNODE->box; - ACTIVE1->pWindow->m_position = ACTIVE1->box.pos(); - ACTIVE1->pWindow->m_size = ACTIVE1->box.size(); - } - - if (ACTIVE2) { - ACTIVE2->box = PNODE2->box; - ACTIVE2->pWindow->m_position = ACTIVE2->box.pos(); - ACTIVE2->pWindow->m_size = ACTIVE2->box.size(); - } - - g_pHyprRenderer->damageWindow(pWindow); - g_pHyprRenderer->damageWindow(pWindow2); - - g_pCompositor->setWindowFullscreenInternal(pWindow2, MODE1); - g_pCompositor->setWindowFullscreenInternal(pWindow, MODE2); -} - -void CHyprDwindleLayout::alterSplitRatio(PHLWINDOW pWindow, float ratio, bool exact) { - // window should be valid, insallah - - const auto PNODE = getNodeFromWindow(pWindow); - - if (!PNODE || !PNODE->pParent) - return; - - float newRatio = exact ? ratio : PNODE->pParent->splitRatio + ratio; - PNODE->pParent->splitRatio = std::clamp(newRatio, 0.1f, 1.9f); - - PNODE->pParent->recalcSizePosRecursive(); -} - -std::any CHyprDwindleLayout::layoutMessage(SLayoutMessageHeader header, std::string message) { - const auto ARGS = CVarList(message, 0, ' '); - if (ARGS[0] == "togglesplit") { - 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]; - - if (direction.empty()) { - Log::logger->log(Log::ERR, "Expected direction for preselect"); - return ""; - } - - switch (direction.front()) { - case 'u': - case 't': { - m_overrideDirection = DIRECTION_UP; - break; - } - case 'd': - case 'b': { - m_overrideDirection = DIRECTION_DOWN; - break; - } - case 'r': { - m_overrideDirection = DIRECTION_RIGHT; - break; - } - case 'l': { - m_overrideDirection = DIRECTION_LEFT; - break; - } - default: { - // any other character resets the focus direction - // needed for the persistent mode - m_overrideDirection = DIRECTION_DEFAULT; - break; - } - } - } - - return ""; -} - -void CHyprDwindleLayout::toggleSplit(PHLWINDOW pWindow) { - const auto PNODE = getNodeFromWindow(pWindow); - - if (!PNODE || !PNODE->pParent) - return; - - if (pWindow->isFullscreen()) - return; - - PNODE->pParent->splitTop = !PNODE->pParent->splitTop; - - PNODE->pParent->recalcSizePosRecursive(); -} - -void CHyprDwindleLayout::swapSplit(PHLWINDOW pWindow) { - const auto PNODE = getNodeFromWindow(pWindow); - - if (!PNODE || !PNODE->pParent) - return; - - if (pWindow->isFullscreen()) - return; - - std::swap(PNODE->pParent->children[0], PNODE->pParent->children[1]); - - 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.lock(); - while (pRoot->pParent) { - pAncestor = pRoot; - pRoot = pRoot->pParent.lock(); - } - - 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 (pWindow->m_workspace && pWindow->m_workspace->isVisible()) - pRoot->recalcSizePosRecursive(); -} - -void CHyprDwindleLayout::replaceWindowDataWith(PHLWINDOW from, PHLWINDOW to) { - const auto PNODE = getNodeFromWindow(from); - - if (!PNODE) - return; - - PNODE->pWindow = to; - - applyNodeDataToWindow(PNODE, true); -} - -std::string CHyprDwindleLayout::getLayoutName() { - return "dwindle"; -} - -void CHyprDwindleLayout::onEnable() { - for (auto const& w : g_pCompositor->m_windows) { - if (w->m_isFloating || !w->m_isMapped || w->isHidden()) - continue; - - onWindowCreatedTiling(w); - } -} - -void CHyprDwindleLayout::onDisable() { - m_dwindleNodesData.clear(); -} - -Vector2D CHyprDwindleLayout::predictSizeForNewWindowTiled() { - if (!Desktop::focusState()->monitor()) - return {}; - - // get window candidate - PHLWINDOW candidate = Desktop::focusState()->window(); - - if (!candidate) - candidate = Desktop::focusState()->monitor()->m_activeWorkspace->getFirstWindow(); - - // create a fake node - SDwindleNodeData node; - - if (!candidate) - return Desktop::focusState()->monitor()->m_size; - else { - const auto PNODE = getNodeFromWindow(candidate); - - if (!PNODE) - return {}; - - node = *PNODE; - node.pWindow.reset(); - - CBox box = PNODE->box; - - static auto PFLMULT = CConfigValue("dwindle:split_width_multiplier"); - - bool splitTop = box.h * *PFLMULT > box.w; - - const auto SPLITSIDE = !splitTop; - - if (SPLITSIDE) - node.box = {{}, {box.w / 2.0, box.h}}; - else - node.box = {{}, {box.w, box.h / 2.0}}; - - // TODO: make this better and more accurate - - return node.box.size(); - } - - return {}; -} diff --git a/src/layout/DwindleLayout.hpp b/src/layout/DwindleLayout.hpp deleted file mode 100644 index e704b8f4..00000000 --- a/src/layout/DwindleLayout.hpp +++ /dev/null @@ -1,110 +0,0 @@ -#pragma once - -#include "IHyprLayout.hpp" -#include "../desktop/DesktopTypes.hpp" - -#include -#include -#include -#include -#include - -class CHyprDwindleLayout; -enum eFullscreenMode : int8_t; - -struct SDwindleNodeData { - WP pParent; - bool isNode = false; - - PHLWINDOWREF pWindow; - - std::array, 2> children = {}; - WP self; - - bool splitTop = false; // for preserve_split - - CBox box = {0}; - - WORKSPACEID workspaceID = WORKSPACE_INVALID; - - float splitRatio = 1.f; - - bool valid = true; - - bool ignoreFullscreenChecks = false; - - // For list lookup - bool operator==(const SDwindleNodeData& rhs) const { - return pWindow.lock() == rhs.pWindow.lock() && workspaceID == rhs.workspaceID && box == rhs.box && pParent == rhs.pParent && children[0] == rhs.children[0] && - children[1] == rhs.children[1]; - } - - void recalcSizePosRecursive(bool force = false, bool horizontalOverride = false, bool verticalOverride = false); - void applyRootBox(); - CHyprDwindleLayout* layout = nullptr; -}; - -class CHyprDwindleLayout : public IHyprLayout { - public: - virtual void onWindowCreatedTiling(PHLWINDOW, eDirection direction = DIRECTION_DEFAULT); - virtual void onWindowRemovedTiling(PHLWINDOW); - virtual bool isWindowTiled(PHLWINDOW); - virtual void recalculateMonitor(const MONITORID&); - virtual void recalculateWindow(PHLWINDOW); - virtual void onBeginDragWindow(); - 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); - virtual std::any layoutMessage(SLayoutMessageHeader, std::string); - virtual SWindowRenderLayoutHints requestRenderHints(PHLWINDOW); - virtual void switchWindows(PHLWINDOW, PHLWINDOW); - virtual void moveWindowTo(PHLWINDOW, const std::string& dir, bool silent); - virtual void alterSplitRatio(PHLWINDOW, float, bool); - virtual std::string getLayoutName(); - virtual void replaceWindowDataWith(PHLWINDOW from, PHLWINDOW to); - virtual Vector2D predictSizeForNewWindowTiled(); - - virtual void onEnable(); - virtual void onDisable(); - - private: - std::vector> m_dwindleNodesData; - - struct { - bool started = false; - bool pseudo = false; - bool xExtent = false; - bool yExtent = false; - } m_pseudoDragFlags; - - std::optional m_overrideFocalPoint; // for onWindowCreatedTiling. - - int getNodesOnWorkspace(const WORKSPACEID&); - void applyNodeDataToWindow(SP, bool force = false); - void calculateWorkspace(const PHLWORKSPACE& pWorkspace); - SP getNodeFromWindow(PHLWINDOW); - SP getFirstNodeOnWorkspace(const WORKSPACEID&); - SP getClosestNodeOnWorkspace(const WORKSPACEID&, const Vector2D&); - SP getMasterNodeOnWorkspace(const WORKSPACEID&); - - void toggleSplit(PHLWINDOW); - void swapSplit(PHLWINDOW); - void moveToRoot(PHLWINDOW, bool stable = true); - - eDirection m_overrideDirection = DIRECTION_DEFAULT; - - friend struct SDwindleNodeData; -}; - -template -struct std::formatter, CharT> : std::formatter { - template - auto format(const SP& node, FormatContext& ctx) const { - auto out = ctx.out(); - if (!node) - return std::format_to(out, "[Node nullptr]"); - std::format_to(out, "[Node {:x}: workspace: {}, pos: {:j2}, size: {:j2}", rc(node.get()), node->workspaceID, node->box.pos(), node->box.size()); - if (!node->isNode && !node->pWindow.expired()) - std::format_to(out, ", window: {:x}", node->pWindow.lock()); - return std::format_to(out, "]"); - } -}; diff --git a/src/layout/IHyprLayout.cpp b/src/layout/IHyprLayout.cpp deleted file mode 100644 index f434b580..00000000 --- a/src/layout/IHyprLayout.cpp +++ /dev/null @@ -1,1062 +0,0 @@ -#include "IHyprLayout.hpp" -#include "../defines.hpp" -#include "../Compositor.hpp" -#include "../render/decorations/CHyprGroupBarDecoration.hpp" -#include "../config/ConfigValue.hpp" -#include "../config/ConfigManager.hpp" -#include "../desktop/view/Window.hpp" -#include "../desktop/state/FocusState.hpp" -#include "../protocols/XDGShell.hpp" -#include "../protocols/core/Compositor.hpp" -#include "../xwayland/XSurface.hpp" -#include "../render/Renderer.hpp" -#include "../managers/LayoutManager.hpp" -#include "../managers/EventManager.hpp" -#include "../managers/HookSystemManager.hpp" -#include "../managers/cursor/CursorShapeOverrideController.hpp" -#include "../desktop/rule/windowRule/WindowRule.hpp" - -void IHyprLayout::onWindowCreated(PHLWINDOW pWindow, eDirection direction) { - CBox desiredGeometry = g_pXWaylandManager->getGeometryForWindow(pWindow); - - const bool HASPERSISTENTSIZE = pWindow->m_ruleApplicator->persistentSize().valueOrDefault(); - - const auto STOREDSIZE = HASPERSISTENTSIZE ? g_pConfigManager->getStoredFloatingSize(pWindow) : std::nullopt; - - if (STOREDSIZE.has_value()) { - Log::logger->log(Log::DEBUG, "using stored size {}x{} for new window {}::{}", STOREDSIZE->x, STOREDSIZE->y, pWindow->m_class, pWindow->m_title); - pWindow->m_lastFloatingSize = STOREDSIZE.value(); - } else if (desiredGeometry.width <= 5 || desiredGeometry.height <= 5) { - const auto PMONITOR = pWindow->m_monitor.lock(); - pWindow->m_lastFloatingSize = PMONITOR->m_size / 2.f; - } else - pWindow->m_lastFloatingSize = Vector2D(desiredGeometry.width, desiredGeometry.height); - - pWindow->m_pseudoSize = pWindow->m_lastFloatingSize; - - bool autoGrouped = IHyprLayout::onWindowCreatedAutoGroup(pWindow); - if (autoGrouped) - return; - - if (pWindow->m_isFloating) - onWindowCreatedFloating(pWindow); - else - onWindowCreatedTiling(pWindow, direction); - - if (!g_pXWaylandManager->shouldBeFloated(pWindow)) // do not apply group rules to child windows - pWindow->applyGroupRules(); -} - -void IHyprLayout::onWindowRemoved(PHLWINDOW pWindow) { - if (pWindow->isFullscreen()) - g_pCompositor->setWindowFullscreenInternal(pWindow, FSMODE_NONE); - - if (!pWindow->m_groupData.pNextWindow.expired()) { - if (pWindow->m_groupData.pNextWindow.lock() == pWindow) { - pWindow->m_groupData.pNextWindow.reset(); - pWindow->updateWindowDecos(); - } else { - // find last window and update - PHLWINDOW PWINDOWPREV = pWindow->getGroupPrevious(); - const auto WINDOWISVISIBLE = pWindow->getGroupCurrent() == pWindow; - - if (WINDOWISVISIBLE) - PWINDOWPREV->setGroupCurrent(pWindow->m_groupData.head ? pWindow->m_groupData.pNextWindow.lock() : PWINDOWPREV); - - PWINDOWPREV->m_groupData.pNextWindow = pWindow->m_groupData.pNextWindow; - - pWindow->m_groupData.pNextWindow.reset(); - - if (pWindow->m_groupData.head) { - std::swap(PWINDOWPREV->m_groupData.pNextWindow->m_groupData.head, pWindow->m_groupData.head); - std::swap(PWINDOWPREV->m_groupData.pNextWindow->m_groupData.locked, pWindow->m_groupData.locked); - } - - if (pWindow == m_lastTiledWindow) - m_lastTiledWindow.reset(); - - pWindow->setHidden(false); - - pWindow->updateWindowDecos(); - PWINDOWPREV->getGroupCurrent()->updateWindowDecos(); - pWindow->updateDecorationValues(); - - return; - } - } - - if (pWindow->m_isFloating) { - onWindowRemovedFloating(pWindow); - } else { - onWindowRemovedTiling(pWindow); - } - - if (pWindow == m_lastTiledWindow) - m_lastTiledWindow.reset(); -} - -void IHyprLayout::onWindowRemovedFloating(PHLWINDOW pWindow) { - ; // no-op -} - -void IHyprLayout::onWindowCreatedFloating(PHLWINDOW pWindow) { - - CBox desiredGeometry = g_pXWaylandManager->getGeometryForWindow(pWindow); - const auto PMONITOR = pWindow->m_monitor.lock(); - - if (pWindow->m_isX11) { - Vector2D xy = {desiredGeometry.x, desiredGeometry.y}; - xy = g_pXWaylandManager->xwaylandToWaylandCoords(xy); - desiredGeometry.x = xy.x; - desiredGeometry.y = xy.y; - } else if (pWindow->m_ruleApplicator->persistentSize().valueOrDefault()) { - desiredGeometry.w = pWindow->m_lastFloatingSize.x; - desiredGeometry.h = pWindow->m_lastFloatingSize.y; - } - - static auto PXWLFORCESCALEZERO = CConfigValue("xwayland:force_zero_scaling"); - - if (!PMONITOR) { - Log::logger->log(Log::ERR, "{:m} has an invalid monitor in onWindowCreatedFloating!!!", pWindow); - return; - } - - if (desiredGeometry.width <= 5 || desiredGeometry.height <= 5) { - const auto PWINDOWSURFACE = pWindow->wlSurface()->resource(); - *pWindow->m_realSize = PWINDOWSURFACE->m_current.size; - - if ((desiredGeometry.width <= 1 || desiredGeometry.height <= 1) && pWindow->m_isX11 && - pWindow->isX11OverrideRedirect()) { // XDG windows should be fine. TODO: check for weird atoms? - pWindow->setHidden(true); - return; - } - - // reject any windows with size <= 5x5 - if (pWindow->m_realSize->goal().x <= 5 || pWindow->m_realSize->goal().y <= 5) - *pWindow->m_realSize = PMONITOR->m_size / 2.f; - - if (pWindow->m_isX11 && pWindow->isX11OverrideRedirect()) { - - if (pWindow->m_xwaylandSurface->m_geometry.x != 0 && pWindow->m_xwaylandSurface->m_geometry.y != 0) - *pWindow->m_realPosition = g_pXWaylandManager->xwaylandToWaylandCoords(pWindow->m_xwaylandSurface->m_geometry.pos()); - else - *pWindow->m_realPosition = Vector2D(PMONITOR->m_position.x + (PMONITOR->m_size.x - pWindow->m_realSize->goal().x) / 2.f, - PMONITOR->m_position.y + (PMONITOR->m_size.y - pWindow->m_realSize->goal().y) / 2.f); - } else { - *pWindow->m_realPosition = Vector2D(PMONITOR->m_position.x + (PMONITOR->m_size.x - pWindow->m_realSize->goal().x) / 2.f, - PMONITOR->m_position.y + (PMONITOR->m_size.y - pWindow->m_realSize->goal().y) / 2.f); - } - } else { - // we respect the size. - *pWindow->m_realSize = Vector2D(desiredGeometry.width, desiredGeometry.height); - - // check if it's on the correct monitor! - Vector2D middlePoint = Vector2D(desiredGeometry.x, desiredGeometry.y) + Vector2D(desiredGeometry.width, desiredGeometry.height) / 2.f; - - // check if it's visible on any monitor (only for XDG) - bool visible = pWindow->m_isX11; - - if (!visible) { - visible = g_pCompositor->isPointOnAnyMonitor(Vector2D(desiredGeometry.x, desiredGeometry.y)) && - g_pCompositor->isPointOnAnyMonitor(Vector2D(desiredGeometry.x + desiredGeometry.width, desiredGeometry.y)) && - g_pCompositor->isPointOnAnyMonitor(Vector2D(desiredGeometry.x, desiredGeometry.y + desiredGeometry.height)) && - g_pCompositor->isPointOnAnyMonitor(Vector2D(desiredGeometry.x + desiredGeometry.width, desiredGeometry.y + desiredGeometry.height)); - } - - // TODO: detect a popup in a more consistent way. - bool centeredOnParent = false; - if ((desiredGeometry.x == 0 && desiredGeometry.y == 0) || !visible || !pWindow->m_isX11) { - // if the pos isn't set, fall back to the center placement if it's not a child - auto pos = PMONITOR->m_position + PMONITOR->m_size / 2.F - desiredGeometry.size() / 2.F; - - // otherwise middle of parent if available - if (!pWindow->m_isX11) { - if (const auto PARENT = pWindow->parent(); PARENT) { - *pWindow->m_realPosition = PARENT->m_realPosition->goal() + PARENT->m_realSize->goal() / 2.F - desiredGeometry.size() / 2.F; - pWindow->m_workspace = PARENT->m_workspace; - pWindow->m_monitor = PARENT->m_monitor; - centeredOnParent = true; - } - } - if (!centeredOnParent) - *pWindow->m_realPosition = pos; - } else { - // if it is, we respect where it wants to put itself, but apply monitor offset if outside - // most of these are popups - - if (const auto POPENMON = g_pCompositor->getMonitorFromVector(middlePoint); POPENMON->m_id != PMONITOR->m_id) - *pWindow->m_realPosition = Vector2D(desiredGeometry.x, desiredGeometry.y) - POPENMON->m_position + PMONITOR->m_position; - else - *pWindow->m_realPosition = Vector2D(desiredGeometry.x, desiredGeometry.y); - } - } - - if (*PXWLFORCESCALEZERO && pWindow->m_isX11) - *pWindow->m_realSize = pWindow->m_realSize->goal() / PMONITOR->m_scale; - - if (pWindow->m_X11DoesntWantBorders || (pWindow->m_isX11 && pWindow->isX11OverrideRedirect())) { - pWindow->m_realPosition->warp(); - pWindow->m_realSize->warp(); - } - - if (!pWindow->isX11OverrideRedirect()) - g_pCompositor->changeWindowZOrder(pWindow, true); - else { - pWindow->m_pendingReportedSize = pWindow->m_realSize->goal(); - pWindow->m_reportedSize = pWindow->m_pendingReportedSize; - } -} - -bool IHyprLayout::onWindowCreatedAutoGroup(PHLWINDOW pWindow) { - static auto PAUTOGROUP = CConfigValue("group:auto_group"); - const PHLWINDOW OPENINGON = Desktop::focusState()->window() && Desktop::focusState()->window()->m_workspace == pWindow->m_workspace ? - Desktop::focusState()->window() : - (pWindow->m_workspace ? pWindow->m_workspace->getFirstWindow() : nullptr); - const bool FLOATEDINTOTILED = pWindow->m_isFloating && OPENINGON && !OPENINGON->m_isFloating; - const bool SWALLOWING = pWindow->m_swallowed || pWindow->m_groupSwallowed; - - if ((*PAUTOGROUP || SWALLOWING) // continue if auto_group is enabled or if dealing with window swallowing. - && OPENINGON // this shouldn't be 0, but honestly, better safe than sorry. - && OPENINGON != pWindow // prevent freeze when the "group set" window rule makes the new window to be already a group. - && OPENINGON->m_groupData.pNextWindow.lock() // check if OPENINGON is 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 !FLOATEDINTOTILED. - && !FLOATEDINTOTILED) { // don't group a new floated window into a tiled group (for convenience). - - pWindow->m_isFloating = OPENINGON->m_isFloating; // 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->applyGroupRules(); - pWindow->updateWindowDecos(); - recalculateWindow(pWindow); - - return true; - } - - return false; -} - -void IHyprLayout::onBeginDragWindow() { - const auto DRAGGINGWINDOW = g_pInputManager->m_currentlyDraggedWindow.lock(); - static auto PDRAGTHRESHOLD = CConfigValue("binds:drag_threshold"); - - m_mouseMoveEventCount = 1; - m_beginDragSizeXY = Vector2D(); - - // Window will be floating. Let's check if it's valid. It should be, but I don't like crashing. - if (!validMapped(DRAGGINGWINDOW)) { - Log::logger->log(Log::ERR, "Dragging attempted on an invalid window!"); - CKeybindManager::changeMouseBindMode(MBIND_INVALID); - return; - } - - // Try to pick up dragged window now if drag_threshold is disabled - // or at least update dragging related variables for the cursors - g_pInputManager->m_dragThresholdReached = *PDRAGTHRESHOLD <= 0; - if (updateDragWindow()) - return; - - // get the grab corner - static auto RESIZECORNER = CConfigValue("general:resize_corner"); - if (*RESIZECORNER != 0 && *RESIZECORNER <= 4 && DRAGGINGWINDOW->m_isFloating) { - switch (*RESIZECORNER) { - case 1: - m_grabbedCorner = CORNER_TOPLEFT; - Cursor::overrideController->setOverride("nw-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); - break; - case 2: - m_grabbedCorner = CORNER_TOPRIGHT; - Cursor::overrideController->setOverride("ne-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); - break; - case 3: - m_grabbedCorner = CORNER_BOTTOMRIGHT; - Cursor::overrideController->setOverride("se-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); - break; - case 4: - m_grabbedCorner = CORNER_BOTTOMLEFT; - Cursor::overrideController->setOverride("sw-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); - break; - } - } else if (m_beginDragXY.x < m_beginDragPositionXY.x + m_beginDragSizeXY.x / 2.0) { - if (m_beginDragXY.y < m_beginDragPositionXY.y + m_beginDragSizeXY.y / 2.0) { - m_grabbedCorner = CORNER_TOPLEFT; - Cursor::overrideController->setOverride("nw-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); - } else { - m_grabbedCorner = CORNER_BOTTOMLEFT; - Cursor::overrideController->setOverride("sw-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); - } - } else { - if (m_beginDragXY.y < m_beginDragPositionXY.y + m_beginDragSizeXY.y / 2.0) { - m_grabbedCorner = CORNER_TOPRIGHT; - Cursor::overrideController->setOverride("ne-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); - } else { - m_grabbedCorner = CORNER_BOTTOMRIGHT; - Cursor::overrideController->setOverride("se-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); - } - } - - if (g_pInputManager->m_dragMode != MBIND_RESIZE && g_pInputManager->m_dragMode != MBIND_RESIZE_FORCE_RATIO && g_pInputManager->m_dragMode != MBIND_RESIZE_BLOCK_RATIO) - Cursor::overrideController->setOverride("grabbing", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); - - g_pHyprRenderer->damageWindow(DRAGGINGWINDOW); - - g_pKeybindManager->shadowKeybinds(); - - Desktop::focusState()->rawWindowFocus(DRAGGINGWINDOW); - g_pCompositor->changeWindowZOrder(DRAGGINGWINDOW, true); -} - -void IHyprLayout::onEndDragWindow() { - const auto DRAGGINGWINDOW = g_pInputManager->m_currentlyDraggedWindow.lock(); - - m_mouseMoveEventCount = 1; - - if (!validMapped(DRAGGINGWINDOW)) { - if (DRAGGINGWINDOW) { - Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); - g_pInputManager->m_currentlyDraggedWindow.reset(); - } - return; - } - - Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); - g_pInputManager->m_currentlyDraggedWindow.reset(); - g_pInputManager->m_wasDraggingWindow = true; - - if (g_pInputManager->m_dragMode == MBIND_MOVE) { - g_pHyprRenderer->damageWindow(DRAGGINGWINDOW); - const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal(); - PHLWINDOW pWindow = - g_pCompositor->vectorToWindowUnified(MOUSECOORDS, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING, DRAGGINGWINDOW); - - if (pWindow) { - if (pWindow->checkInputOnDecos(INPUT_TYPE_DRAG_END, MOUSECOORDS, DRAGGINGWINDOW)) - return; - - const bool FLOATEDINTOTILED = !pWindow->m_isFloating && !DRAGGINGWINDOW->m_draggingTiled; - static auto PDRAGINTOGROUP = CConfigValue("group:drag_into_group"); - - if (pWindow->m_groupData.pNextWindow.lock() && DRAGGINGWINDOW->canBeGroupedInto(pWindow) && *PDRAGINTOGROUP == 1 && !FLOATEDINTOTILED) { - - if (DRAGGINGWINDOW->m_groupData.pNextWindow) { - PHLWINDOW next = DRAGGINGWINDOW->m_groupData.pNextWindow.lock(); - while (next != DRAGGINGWINDOW) { - next->m_isFloating = pWindow->m_isFloating; // match the floating state of group members - *next->m_realSize = pWindow->m_realSize->goal(); // match the size of group members - *next->m_realPosition = pWindow->m_realPosition->goal(); // match the position of group members - next = next->m_groupData.pNextWindow.lock(); - } - } - - DRAGGINGWINDOW->m_isFloating = pWindow->m_isFloating; // match the floating state of the window - DRAGGINGWINDOW->m_lastFloatingSize = m_draggingWindowOriginalFloatSize; - DRAGGINGWINDOW->m_draggingTiled = false; - - static auto USECURRPOS = CConfigValue("group:insert_after_current"); - (*USECURRPOS ? pWindow : pWindow->getGroupTail())->insertWindowToGroup(DRAGGINGWINDOW); - pWindow->setGroupCurrent(DRAGGINGWINDOW); - DRAGGINGWINDOW->applyGroupRules(); - DRAGGINGWINDOW->updateWindowDecos(); - } - } - } - - if (DRAGGINGWINDOW->m_draggingTiled) { - static auto PPRECISEMOUSE = CConfigValue("dwindle:precise_mouse_move"); - DRAGGINGWINDOW->m_isFloating = false; - g_pInputManager->refocus(); - - if (*PPRECISEMOUSE) { - eDirection direction = DIRECTION_DEFAULT; - - const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal(); - const PHLWINDOW pReferenceWindow = - g_pCompositor->vectorToWindowUnified(MOUSECOORDS, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING, DRAGGINGWINDOW); - - if (pReferenceWindow && pReferenceWindow != DRAGGINGWINDOW) { - const Vector2D draggedCenter = DRAGGINGWINDOW->m_realPosition->goal() + DRAGGINGWINDOW->m_realSize->goal() / 2.f; - const Vector2D referenceCenter = pReferenceWindow->m_realPosition->goal() + pReferenceWindow->m_realSize->goal() / 2.f; - const float xDiff = draggedCenter.x - referenceCenter.x; - const float yDiff = draggedCenter.y - referenceCenter.y; - - if (fabs(xDiff) > fabs(yDiff)) - direction = xDiff < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT; - else - direction = yDiff < 0 ? DIRECTION_UP : DIRECTION_DOWN; - } - - onWindowRemovedTiling(DRAGGINGWINDOW); - onWindowCreatedTiling(DRAGGINGWINDOW, direction); - } else - changeWindowFloatingMode(DRAGGINGWINDOW); - - DRAGGINGWINDOW->m_lastFloatingSize = m_draggingWindowOriginalFloatSize; - } - - g_pHyprRenderer->damageWindow(DRAGGINGWINDOW); - Desktop::focusState()->fullWindowFocus(DRAGGINGWINDOW); - - g_pInputManager->m_wasDraggingWindow = false; -} - -static inline bool canSnap(const double SIDEA, const double SIDEB, const double GAP) { - return std::abs(SIDEA - SIDEB) < GAP; -} - -static void snapMove(double& start, double& end, const double P) { - end = P + (end - start); - start = P; -} - -static void snapResize(double& start, double& end, const double P) { - start = P; -} - -using SnapFn = std::function; - -void IHyprLayout::performSnap(Vector2D& sourcePos, Vector2D& sourceSize, PHLWINDOW DRAGGINGWINDOW, const eMouseBindMode MODE, const int CORNER, const Vector2D& BEGINSIZE) { - static auto SNAPWINDOWGAP = CConfigValue("general:snap:window_gap"); - static auto SNAPMONITORGAP = CConfigValue("general:snap:monitor_gap"); - static auto SNAPBORDEROVERLAP = CConfigValue("general:snap:border_overlap"); - static auto SNAPRESPECTGAPS = CConfigValue("general:snap:respect_gaps"); - - static auto PGAPSIN = CConfigValue("general:gaps_in"); - static auto PGAPSOUT = CConfigValue("general:gaps_out"); - const auto GAPSNONE = CCssGapData{0, 0, 0, 0}; - - const SnapFn SNAP = (MODE == MBIND_MOVE) ? snapMove : snapResize; - int snaps = 0; - - struct SRange { - double start = 0; - double end = 0; - }; - const auto EXTENTS = DRAGGINGWINDOW->getWindowExtentsUnified(Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS); - SRange sourceX = {sourcePos.x - EXTENTS.topLeft.x, sourcePos.x + sourceSize.x + EXTENTS.bottomRight.x}; - SRange sourceY = {sourcePos.y - EXTENTS.topLeft.y, sourcePos.y + sourceSize.y + EXTENTS.bottomRight.y}; - - if (*SNAPWINDOWGAP) { - const double GAPSIZE = *SNAPWINDOWGAP; - const auto WSID = DRAGGINGWINDOW->workspaceID(); - const bool HASFULLSCREEN = DRAGGINGWINDOW->m_workspace && DRAGGINGWINDOW->m_workspace->m_hasFullscreenWindow; - - const auto* GAPSIN = *SNAPRESPECTGAPS ? sc(PGAPSIN.ptr()->getData()) : &GAPSNONE; - const double GAPSX = GAPSIN->m_left + GAPSIN->m_right; - const double GAPSY = GAPSIN->m_top + GAPSIN->m_bottom; - - for (auto& other : g_pCompositor->m_windows) { - if ((HASFULLSCREEN && !other->m_createdOverFullscreen) || other == DRAGGINGWINDOW || other->workspaceID() != WSID || !other->m_isMapped || other->m_fadingOut || - other->isX11OverrideRedirect()) - continue; - - const CBox SURF = other->getWindowBoxUnified(Desktop::View::RESERVED_EXTENTS); - const SRange SURFBX = {SURF.x - GAPSX, SURF.x + SURF.w + GAPSX}; - const SRange SURFBY = {SURF.y - GAPSY, SURF.y + SURF.h + GAPSY}; - - // only snap windows if their ranges overlap in the opposite axis - if (sourceY.start <= SURFBY.end && SURFBY.start <= sourceY.end) { - if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && canSnap(sourceX.start, SURFBX.end, GAPSIZE)) { - SNAP(sourceX.start, sourceX.end, SURFBX.end); - snaps |= SNAP_LEFT; - } else if (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && canSnap(sourceX.end, SURFBX.start, GAPSIZE)) { - SNAP(sourceX.end, sourceX.start, SURFBX.start); - snaps |= SNAP_RIGHT; - } - } - if (sourceX.start <= SURFBX.end && SURFBX.start <= sourceX.end) { - if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && canSnap(sourceY.start, SURFBY.end, GAPSIZE)) { - SNAP(sourceY.start, sourceY.end, SURFBY.end); - snaps |= SNAP_UP; - } else if (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && canSnap(sourceY.end, SURFBY.start, GAPSIZE)) { - SNAP(sourceY.end, sourceY.start, SURFBY.start); - snaps |= SNAP_DOWN; - } - } - - // corner snapping - if (sourceX.start == SURFBX.end || SURFBX.start == sourceX.end) { - const SRange SURFY = {SURFBY.start + GAPSY, SURFBY.end - GAPSY}; - if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && !(snaps & SNAP_UP) && canSnap(sourceY.start, SURFY.start, GAPSIZE)) { - SNAP(sourceY.start, sourceY.end, SURFY.start); - snaps |= SNAP_UP; - } else if (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && !(snaps & SNAP_DOWN) && canSnap(sourceY.end, SURFY.end, GAPSIZE)) { - SNAP(sourceY.end, sourceY.start, SURFY.end); - snaps |= SNAP_DOWN; - } - } - if (sourceY.start == SURFBY.end || SURFBY.start == sourceY.end) { - const SRange SURFX = {SURFBX.start + GAPSX, SURFBX.end - GAPSX}; - if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && !(snaps & SNAP_LEFT) && canSnap(sourceX.start, SURFX.start, GAPSIZE)) { - SNAP(sourceX.start, sourceX.end, SURFX.start); - snaps |= SNAP_LEFT; - } else if (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && !(snaps & SNAP_RIGHT) && canSnap(sourceX.end, SURFX.end, GAPSIZE)) { - SNAP(sourceX.end, sourceX.start, SURFX.end); - snaps |= SNAP_RIGHT; - } - } - } - } - - if (*SNAPMONITORGAP) { - const double GAPSIZE = *SNAPMONITORGAP; - const auto EXTENTNONE = SBoxExtents{{0, 0}, {0, 0}}; - const auto* EXTENTDIFF = *SNAPBORDEROVERLAP ? &EXTENTS : &EXTENTNONE; - const auto MON = DRAGGINGWINDOW->m_monitor.lock(); - - const auto* GAPSOUT = *SNAPRESPECTGAPS ? sc(PGAPSOUT.ptr()->getData()) : &GAPSNONE; - const auto WORK_AREA = Desktop::CReservedArea{GAPSOUT->m_top, GAPSOUT->m_right, GAPSOUT->m_bottom, GAPSOUT->m_left}.apply(MON->logicalBoxMinusReserved()); - - SRange monX = {WORK_AREA.x, WORK_AREA.x + WORK_AREA.w}; - SRange monY = {WORK_AREA.y, WORK_AREA.y + WORK_AREA.h}; - - const bool HAS_LEFT = MON->m_reservedArea.left() > 0; - const bool HAS_TOP = MON->m_reservedArea.top() > 0; - const bool HAS_BOTTOM = MON->m_reservedArea.bottom() > 0; - const bool HAS_RIGHT = MON->m_reservedArea.right() > 0; - - if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && - ((HAS_LEFT && canSnap(sourceX.start, monX.start, GAPSIZE)) || canSnap(sourceX.start, (monX.start -= MON->m_reservedArea.left() + EXTENTDIFF->topLeft.x), GAPSIZE))) { - SNAP(sourceX.start, sourceX.end, monX.start); - snaps |= SNAP_LEFT; - } - if (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && - ((HAS_RIGHT && canSnap(sourceX.end, monX.end, GAPSIZE)) || canSnap(sourceX.end, (monX.end += MON->m_reservedArea.right() + EXTENTDIFF->bottomRight.x), GAPSIZE))) { - SNAP(sourceX.end, sourceX.start, monX.end); - snaps |= SNAP_RIGHT; - } - if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && - ((HAS_TOP && canSnap(sourceY.start, monY.start, GAPSIZE)) || canSnap(sourceY.start, (monY.start -= MON->m_reservedArea.top() + EXTENTDIFF->topLeft.y), GAPSIZE))) { - SNAP(sourceY.start, sourceY.end, monY.start); - snaps |= SNAP_UP; - } - if (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && - ((HAS_BOTTOM && canSnap(sourceY.end, monY.end, GAPSIZE)) || canSnap(sourceY.end, (monY.end += MON->m_reservedArea.bottom() + EXTENTDIFF->bottomRight.y), GAPSIZE))) { - SNAP(sourceY.end, sourceY.start, monY.end); - snaps |= SNAP_DOWN; - } - } - - // remove extents from main surface - sourceX = {sourceX.start + EXTENTS.topLeft.x, sourceX.end - EXTENTS.bottomRight.x}; - sourceY = {sourceY.start + EXTENTS.topLeft.y, sourceY.end - EXTENTS.bottomRight.y}; - - if (MODE == MBIND_RESIZE_FORCE_RATIO) { - if ((CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && snaps & SNAP_LEFT) || (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && snaps & SNAP_RIGHT)) { - const double SIZEY = (sourceX.end - sourceX.start) * (BEGINSIZE.y / BEGINSIZE.x); - if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT)) - sourceY.start = sourceY.end - SIZEY; - else - sourceY.end = sourceY.start + SIZEY; - } else if ((CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && snaps & SNAP_UP) || (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && snaps & SNAP_DOWN)) { - const double SIZEX = (sourceY.end - sourceY.start) * (BEGINSIZE.x / BEGINSIZE.y); - if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT)) - sourceX.start = sourceX.end - SIZEX; - else - sourceX.end = sourceX.start + SIZEX; - } - } - - sourcePos = {sourceX.start, sourceY.start}; - sourceSize = {sourceX.end - sourceX.start, sourceY.end - sourceY.start}; -} - -void IHyprLayout::onMouseMove(const Vector2D& mousePos) { - if (g_pInputManager->m_currentlyDraggedWindow.expired()) - return; - - const auto DRAGGINGWINDOW = g_pInputManager->m_currentlyDraggedWindow.lock(); - static auto PDRAGTHRESHOLD = CConfigValue("binds:drag_threshold"); - - // Window invalid or drag begin size 0,0 meaning we rejected it. - if ((!validMapped(DRAGGINGWINDOW) || m_beginDragSizeXY == Vector2D())) { - g_pKeybindManager->changeMouseBindMode(MBIND_INVALID); - return; - } - - // Yoink dragged window here instead if using drag_threshold and it has been reached - if (*PDRAGTHRESHOLD > 0 && !g_pInputManager->m_dragThresholdReached) { - if ((m_beginDragXY.distanceSq(mousePos) <= std::pow(*PDRAGTHRESHOLD, 2) && m_beginDragXY == m_lastDragXY)) - return; - g_pInputManager->m_dragThresholdReached = true; - if (updateDragWindow()) - return; - } - - static auto TIMER = std::chrono::high_resolution_clock::now(), MSTIMER = TIMER; - - const auto SPECIAL = DRAGGINGWINDOW->onSpecialWorkspace(); - - const auto DELTA = Vector2D(mousePos.x - m_beginDragXY.x, mousePos.y - m_beginDragXY.y); - const auto TICKDELTA = Vector2D(mousePos.x - m_lastDragXY.x, mousePos.y - m_lastDragXY.y); - - static auto PANIMATEMOUSE = CConfigValue("misc:animate_mouse_windowdragging"); - static auto PANIMATE = CConfigValue("misc:animate_manual_resizes"); - - static auto SNAPENABLED = CConfigValue("general:snap:enabled"); - - const auto TIMERDELTA = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - TIMER).count(); - const auto MSDELTA = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - MSTIMER).count(); - const auto MSMONITOR = 1000.0 / g_pHyprRenderer->m_mostHzMonitor->m_refreshRate; - static int totalMs = 0; - bool canSkipUpdate = true; - - MSTIMER = std::chrono::high_resolution_clock::now(); - - if (m_mouseMoveEventCount == 1) - totalMs = 0; - - if (MSMONITOR > 16.0) { - totalMs += MSDELTA < MSMONITOR ? MSDELTA : std::round(totalMs * 1.0 / m_mouseMoveEventCount); - m_mouseMoveEventCount += 1; - - // check if time-window is enough to skip update on 60hz monitor - canSkipUpdate = std::clamp(MSMONITOR - TIMERDELTA, 0.0, MSMONITOR) > totalMs * 1.0 / m_mouseMoveEventCount; - } - - if ((abs(TICKDELTA.x) < 1.f && abs(TICKDELTA.y) < 1.f) || (TIMERDELTA < MSMONITOR && canSkipUpdate && (g_pInputManager->m_dragMode != MBIND_MOVE || *PANIMATEMOUSE))) - return; - - TIMER = std::chrono::high_resolution_clock::now(); - - m_lastDragXY = mousePos; - - g_pHyprRenderer->damageWindow(DRAGGINGWINDOW); - - if (g_pInputManager->m_dragMode == MBIND_MOVE) { - - Vector2D newPos = m_beginDragPositionXY + DELTA; - Vector2D newSize = DRAGGINGWINDOW->m_realSize->goal(); - - if (*SNAPENABLED && !DRAGGINGWINDOW->m_draggingTiled) - performSnap(newPos, newSize, DRAGGINGWINDOW, MBIND_MOVE, -1, m_beginDragSizeXY); - - newPos = newPos.round(); - - if (*PANIMATEMOUSE) - *DRAGGINGWINDOW->m_realPosition = newPos; - else { - DRAGGINGWINDOW->m_realPosition->setValueAndWarp(newPos); - DRAGGINGWINDOW->sendWindowSize(); - } - - DRAGGINGWINDOW->m_position = newPos; - - } else if (g_pInputManager->m_dragMode == MBIND_RESIZE || g_pInputManager->m_dragMode == MBIND_RESIZE_FORCE_RATIO || g_pInputManager->m_dragMode == MBIND_RESIZE_BLOCK_RATIO) { - if (DRAGGINGWINDOW->m_isFloating) { - - Vector2D MINSIZE = DRAGGINGWINDOW->minSize().value_or(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}); - Vector2D MAXSIZE = DRAGGINGWINDOW->maxSize().value_or(Math::VECTOR2D_MAX); - - Vector2D newSize = m_beginDragSizeXY; - Vector2D newPos = m_beginDragPositionXY; - - if (m_grabbedCorner == CORNER_BOTTOMRIGHT) - newSize = newSize + DELTA; - else if (m_grabbedCorner == CORNER_TOPLEFT) - newSize = newSize - DELTA; - else if (m_grabbedCorner == CORNER_TOPRIGHT) - newSize = newSize + Vector2D(DELTA.x, -DELTA.y); - else if (m_grabbedCorner == CORNER_BOTTOMLEFT) - newSize = newSize + Vector2D(-DELTA.x, DELTA.y); - - eMouseBindMode mode = g_pInputManager->m_dragMode; - if (DRAGGINGWINDOW->m_ruleApplicator->keepAspectRatio().valueOrDefault() && mode != MBIND_RESIZE_BLOCK_RATIO) - mode = MBIND_RESIZE_FORCE_RATIO; - - if (m_beginDragSizeXY.x >= 1 && m_beginDragSizeXY.y >= 1 && mode == MBIND_RESIZE_FORCE_RATIO) { - - const float RATIO = m_beginDragSizeXY.y / m_beginDragSizeXY.x; - - if (MINSIZE.x * RATIO > MINSIZE.y) - MINSIZE = Vector2D(MINSIZE.x, MINSIZE.x * RATIO); - else - MINSIZE = Vector2D(MINSIZE.y / RATIO, MINSIZE.y); - - if (MAXSIZE.x * RATIO < MAXSIZE.y) - MAXSIZE = Vector2D(MAXSIZE.x, MAXSIZE.x * RATIO); - else - MAXSIZE = Vector2D(MAXSIZE.y / RATIO, MAXSIZE.y); - - if (newSize.x * RATIO > newSize.y) - newSize = Vector2D(newSize.x, newSize.x * RATIO); - else - newSize = Vector2D(newSize.y / RATIO, newSize.y); - } - - newSize = newSize.clamp(MINSIZE, MAXSIZE); - - if (m_grabbedCorner == CORNER_TOPLEFT) - newPos = newPos - newSize + m_beginDragSizeXY; - else if (m_grabbedCorner == CORNER_TOPRIGHT) - newPos = newPos + Vector2D(0.0, (m_beginDragSizeXY - newSize).y); - else if (m_grabbedCorner == CORNER_BOTTOMLEFT) - newPos = newPos + Vector2D((m_beginDragSizeXY - newSize).x, 0.0); - - if (*SNAPENABLED) { - performSnap(newPos, newSize, DRAGGINGWINDOW, mode, m_grabbedCorner, m_beginDragSizeXY); - newSize = newSize.clamp(MINSIZE, MAXSIZE); - } - - CBox wb = {newPos, newSize}; - wb.round(); - - if (*PANIMATE) { - *DRAGGINGWINDOW->m_realSize = wb.size(); - *DRAGGINGWINDOW->m_realPosition = wb.pos(); - } else { - DRAGGINGWINDOW->m_realSize->setValueAndWarp(wb.size()); - DRAGGINGWINDOW->m_realPosition->setValueAndWarp(wb.pos()); - DRAGGINGWINDOW->sendWindowSize(); - } - - DRAGGINGWINDOW->m_position = wb.pos(); - DRAGGINGWINDOW->m_size = wb.size(); - } else { - resizeActiveWindow(TICKDELTA, m_grabbedCorner, DRAGGINGWINDOW); - } - } - - // get middle point - Vector2D middle = DRAGGINGWINDOW->m_realPosition->value() + DRAGGINGWINDOW->m_realSize->value() / 2.f; - - // and check its monitor - const auto PMONITOR = g_pCompositor->getMonitorFromVector(middle); - - if (PMONITOR && !SPECIAL) { - DRAGGINGWINDOW->m_monitor = PMONITOR; - DRAGGINGWINDOW->moveToWorkspace(PMONITOR->m_activeWorkspace); - DRAGGINGWINDOW->updateGroupOutputs(); - - DRAGGINGWINDOW->updateToplevel(); - } - - DRAGGINGWINDOW->updateWindowDecos(); - - g_pHyprRenderer->damageWindow(DRAGGINGWINDOW); -} - -void IHyprLayout::changeWindowFloatingMode(PHLWINDOW pWindow) { - - if (pWindow->isFullscreen()) { - Log::logger->log(Log::DEBUG, "changeWindowFloatingMode: fullscreen"); - g_pCompositor->setWindowFullscreenInternal(pWindow, FSMODE_NONE); - } - - pWindow->m_pinned = false; - - g_pHyprRenderer->damageWindow(pWindow, true); - - const auto TILED = isWindowTiled(pWindow); - - // event - g_pEventManager->postEvent(SHyprIPCEvent{"changefloatingmode", std::format("{:x},{}", rc(pWindow.get()), sc(TILED))}); - EMIT_HOOK_EVENT("changeFloatingMode", pWindow); - - if (!TILED) { - const auto PNEWMON = g_pCompositor->getMonitorFromVector(pWindow->m_realPosition->value() + pWindow->m_realSize->value() / 2.f); - pWindow->m_monitor = PNEWMON; - pWindow->moveToWorkspace(PNEWMON->m_activeSpecialWorkspace ? PNEWMON->m_activeSpecialWorkspace : PNEWMON->m_activeWorkspace); - pWindow->updateGroupOutputs(); - - const auto PWORKSPACE = PNEWMON->m_activeSpecialWorkspace ? PNEWMON->m_activeSpecialWorkspace : PNEWMON->m_activeWorkspace; - - if (PWORKSPACE->m_hasFullscreenWindow) - g_pCompositor->setWindowFullscreenInternal(PWORKSPACE->getFullscreenWindow(), FSMODE_NONE); - - // save real pos cuz the func applies the default 5,5 mid - const auto PSAVEDPOS = pWindow->m_realPosition->goal(); - const auto PSAVEDSIZE = pWindow->m_realSize->goal(); - - // if the window is pseudo, update its size - if (!pWindow->m_draggingTiled) - pWindow->m_pseudoSize = pWindow->m_realSize->goal(); - - pWindow->m_lastFloatingSize = PSAVEDSIZE; - - // move to narnia because we don't wanna find our own node. onWindowCreatedTiling should apply the coords back. - pWindow->m_position = Vector2D(-999999, -999999); - - onWindowCreatedTiling(pWindow); - - pWindow->m_realPosition->setValue(PSAVEDPOS); - pWindow->m_realSize->setValue(PSAVEDSIZE); - - // fix pseudo leaving artifacts - g_pHyprRenderer->damageMonitor(pWindow->m_monitor.lock()); - - if (pWindow == Desktop::focusState()->window()) - m_lastTiledWindow = pWindow; - } else { - onWindowRemovedTiling(pWindow); - - g_pCompositor->changeWindowZOrder(pWindow, true); - - CBox wb = {pWindow->m_realPosition->goal() + (pWindow->m_realSize->goal() - pWindow->m_lastFloatingSize) / 2.f, pWindow->m_lastFloatingSize}; - wb.round(); - - if (!(pWindow->m_isFloating && pWindow->m_isPseudotiled) && DELTALESSTHAN(pWindow->m_realSize->goal().x, pWindow->m_lastFloatingSize.x, 10) && - DELTALESSTHAN(pWindow->m_realSize->goal().y, pWindow->m_lastFloatingSize.y, 10)) { - wb = {wb.pos() + Vector2D{10, 10}, wb.size() - Vector2D{20, 20}}; - } - - pWindow->m_size = wb.size(); - pWindow->m_position = wb.pos(); - - fitFloatingWindowOnMonitor(pWindow, wb); - - g_pHyprRenderer->damageMonitor(pWindow->m_monitor.lock()); - - pWindow->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT); - pWindow->updateWindowData(); - - if (pWindow == m_lastTiledWindow) - m_lastTiledWindow.reset(); - } - - pWindow->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE | Desktop::Rule::RULE_PROP_FLOATING); - pWindow->updateDecorationValues(); - pWindow->updateToplevel(); - g_pHyprRenderer->damageWindow(pWindow); -} - -void IHyprLayout::fitFloatingWindowOnMonitor(PHLWINDOW w, std::optional tb) { - if (!w->m_isFloating) - return; - - const auto PMONITOR = w->m_monitor.lock(); - - if (!PMONITOR) - return; - - const auto EXTENTS = w->getWindowExtentsUnified(Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS); - CBox targetBoxMonLocal = tb.value_or(w->getWindowMainSurfaceBox()).translate(-PMONITOR->m_position).addExtents(EXTENTS); - const auto MONITOR_LOCAL_BOX = PMONITOR->logicalBoxMinusReserved().translate(-PMONITOR->m_position); - - if (targetBoxMonLocal.w < MONITOR_LOCAL_BOX.w) { - if (targetBoxMonLocal.x < MONITOR_LOCAL_BOX.x) - targetBoxMonLocal.x = MONITOR_LOCAL_BOX.x; - else if (targetBoxMonLocal.x + targetBoxMonLocal.w > MONITOR_LOCAL_BOX.w) - targetBoxMonLocal.x = MONITOR_LOCAL_BOX.w - targetBoxMonLocal.w; - } - - if (targetBoxMonLocal.h < MONITOR_LOCAL_BOX.h) { - if (targetBoxMonLocal.y < MONITOR_LOCAL_BOX.y) - targetBoxMonLocal.y = MONITOR_LOCAL_BOX.y; - else if (targetBoxMonLocal.y + targetBoxMonLocal.h > MONITOR_LOCAL_BOX.h) - targetBoxMonLocal.y = MONITOR_LOCAL_BOX.h - targetBoxMonLocal.h; - } - - *w->m_realPosition = (targetBoxMonLocal.pos() + PMONITOR->m_position + EXTENTS.topLeft).round(); - *w->m_realSize = (targetBoxMonLocal.size() - EXTENTS.topLeft - EXTENTS.bottomRight).round(); -} - -void IHyprLayout::moveActiveWindow(const Vector2D& delta, PHLWINDOW pWindow) { - const auto PWINDOW = pWindow ? pWindow : Desktop::focusState()->window(); - - if (!validMapped(PWINDOW)) - return; - - if (!PWINDOW->m_isFloating) { - Log::logger->log(Log::DEBUG, "Dwindle cannot move a tiled window in moveActiveWindow!"); - return; - } - - PWINDOW->setAnimationsToMove(); - - PWINDOW->m_position += delta; - *PWINDOW->m_realPosition = PWINDOW->m_realPosition->goal() + delta; - - g_pHyprRenderer->damageWindow(PWINDOW); -} - -void IHyprLayout::onWindowFocusChange(PHLWINDOW pNewFocus) { - m_lastTiledWindow = pNewFocus && !pNewFocus->m_isFloating ? pNewFocus : m_lastTiledWindow; -} - -PHLWINDOW IHyprLayout::getNextWindowCandidate(PHLWINDOW pWindow) { - // although we don't expect nullptrs here, let's verify jic - if (!pWindow) - return nullptr; - - const auto PWORKSPACE = pWindow->m_workspace; - - // first of all, if this is a fullscreen workspace, - if (PWORKSPACE->m_hasFullscreenWindow) - return PWORKSPACE->getFullscreenWindow(); - - if (pWindow->m_isFloating) { - - // find whether there is a floating window below this one - for (auto const& w : g_pCompositor->m_windows) { - if (w->m_isMapped && !w->isHidden() && w->m_isFloating && !w->isX11OverrideRedirect() && w->m_workspace == pWindow->m_workspace && !w->m_X11ShouldntFocus && - !w->m_ruleApplicator->noFocus().valueOrDefault() && w != pWindow) { - if (VECINRECT((pWindow->m_size / 2.f + pWindow->m_position), w->m_position.x, w->m_position.y, w->m_position.x + w->m_size.x, w->m_position.y + w->m_size.y)) { - return w; - } - } - } - - // let's try the last tiled window. - if (m_lastTiledWindow.lock() && m_lastTiledWindow->m_workspace == pWindow->m_workspace) - return m_lastTiledWindow.lock(); - - // if we don't, let's try to find any window that is in the middle - if (const auto PWINDOWCANDIDATE = - g_pCompositor->vectorToWindowUnified(pWindow->middle(), Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING); - PWINDOWCANDIDATE && PWINDOWCANDIDATE != pWindow) - return PWINDOWCANDIDATE; - - // if not, floating window - for (auto const& w : g_pCompositor->m_windows) { - if (w->m_isMapped && !w->isHidden() && w->m_isFloating && !w->isX11OverrideRedirect() && w->m_workspace == pWindow->m_workspace && !w->m_X11ShouldntFocus && - !w->m_ruleApplicator->noFocus().valueOrDefault() && w != pWindow) - return w; - } - - // if there is no candidate, too bad - return nullptr; - } - - // if it was a tiled window, we first try to find the window that will replace it. - auto pWindowCandidate = g_pCompositor->vectorToWindowUnified(pWindow->middle(), Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING); - - if (!pWindowCandidate) - pWindowCandidate = PWORKSPACE->getTopLeftWindow(); - - if (!pWindowCandidate) - pWindowCandidate = PWORKSPACE->getFirstWindow(); - - if (!pWindowCandidate || pWindow == pWindowCandidate || !pWindowCandidate->m_isMapped || pWindowCandidate->isHidden() || pWindowCandidate->m_X11ShouldntFocus || - pWindowCandidate->isX11OverrideRedirect() || pWindowCandidate->m_monitor != Desktop::focusState()->monitor()) - return nullptr; - - return pWindowCandidate; -} - -bool IHyprLayout::isWindowReachable(PHLWINDOW pWindow) { - return pWindow && (!pWindow->isHidden() || pWindow->m_groupData.pNextWindow); -} - -void IHyprLayout::bringWindowToTop(PHLWINDOW pWindow) { - if (pWindow == nullptr) - return; - - if (pWindow->isHidden() && pWindow->m_groupData.pNextWindow) { - // grouped, change the current to this window - pWindow->setGroupCurrent(pWindow); - } -} - -void IHyprLayout::requestFocusForWindow(PHLWINDOW pWindow) { - bringWindowToTop(pWindow); - Desktop::focusState()->fullWindowFocus(pWindow); - g_pCompositor->warpCursorTo(pWindow->middle()); -} - -Vector2D IHyprLayout::predictSizeForNewWindowFloating(PHLWINDOW pWindow) { // get all rules, see if we have any size overrides. - Vector2D sizeOverride = {}; - if (Desktop::focusState()->monitor()) { - - // If `persistentsize` is set, use the stored size if available. - const bool HASPERSISTENTSIZE = pWindow->m_ruleApplicator->persistentSize().valueOrDefault(); - - const auto STOREDSIZE = HASPERSISTENTSIZE ? g_pConfigManager->getStoredFloatingSize(pWindow) : std::nullopt; - - if (STOREDSIZE.has_value()) { - Log::logger->log(Log::DEBUG, "using stored size {}x{} for new floating window {}::{}", STOREDSIZE->x, STOREDSIZE->y, pWindow->m_class, pWindow->m_title); - return STOREDSIZE.value(); - } - - if (!pWindow->m_ruleApplicator->static_.size.empty()) { - const auto SIZE = pWindow->calculateExpression(pWindow->m_ruleApplicator->static_.size); - if (SIZE) - return SIZE.value(); - } - } - - return sizeOverride; -} - -Vector2D IHyprLayout::predictSizeForNewWindow(PHLWINDOW pWindow) { - bool shouldBeFloated = g_pXWaylandManager->shouldBeFloated(pWindow, true) || pWindow->m_ruleApplicator->static_.floating.value_or(false); - - Vector2D sizePredicted = {}; - - if (!shouldBeFloated) - sizePredicted = predictSizeForNewWindowTiled(); - else - sizePredicted = predictSizeForNewWindowFloating(pWindow); - - Vector2D maxSize = pWindow->m_xdgSurface->m_toplevel->m_pending.maxSize; - - if ((maxSize.x > 0 && maxSize.x < sizePredicted.x) || (maxSize.y > 0 && maxSize.y < sizePredicted.y)) - sizePredicted = {}; - - return sizePredicted; -} - -bool IHyprLayout::updateDragWindow() { - const auto DRAGGINGWINDOW = g_pInputManager->m_currentlyDraggedWindow.lock(); - const bool WAS_FULLSCREEN = DRAGGINGWINDOW->isFullscreen(); - - if (g_pInputManager->m_dragThresholdReached) { - if (WAS_FULLSCREEN) { - Log::logger->log(Log::DEBUG, "Dragging a fullscreen window"); - g_pCompositor->setWindowFullscreenInternal(DRAGGINGWINDOW, FSMODE_NONE); - } - - const auto PWORKSPACE = DRAGGINGWINDOW->m_workspace; - - if (PWORKSPACE->m_hasFullscreenWindow && (!DRAGGINGWINDOW->m_isFloating || (!DRAGGINGWINDOW->m_createdOverFullscreen && !DRAGGINGWINDOW->m_pinned))) { - Log::logger->log(Log::DEBUG, "Rejecting drag on a fullscreen workspace. (window under fullscreen)"); - g_pKeybindManager->changeMouseBindMode(MBIND_INVALID); - return true; - } - } - - DRAGGINGWINDOW->m_draggingTiled = false; - m_draggingWindowOriginalFloatSize = DRAGGINGWINDOW->m_lastFloatingSize; - - if (WAS_FULLSCREEN && DRAGGINGWINDOW->m_isFloating) { - const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal(); - *DRAGGINGWINDOW->m_realPosition = MOUSECOORDS - DRAGGINGWINDOW->m_realSize->goal() / 2.f; - } else if (!DRAGGINGWINDOW->m_isFloating && g_pInputManager->m_dragMode == MBIND_MOVE) { - Vector2D MINSIZE = DRAGGINGWINDOW->minSize().value_or(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}); - DRAGGINGWINDOW->m_lastFloatingSize = (DRAGGINGWINDOW->m_realSize->goal() * 0.8489).clamp(MINSIZE, Vector2D{}).floor(); - *DRAGGINGWINDOW->m_realPosition = g_pInputManager->getMouseCoordsInternal() - DRAGGINGWINDOW->m_realSize->goal() / 2.f; - if (g_pInputManager->m_dragThresholdReached) { - changeWindowFloatingMode(DRAGGINGWINDOW); - DRAGGINGWINDOW->m_isFloating = true; - DRAGGINGWINDOW->m_draggingTiled = true; - } - } - - m_beginDragXY = g_pInputManager->getMouseCoordsInternal(); - m_beginDragPositionXY = DRAGGINGWINDOW->m_realPosition->goal(); - m_beginDragSizeXY = DRAGGINGWINDOW->m_realSize->goal(); - m_lastDragXY = m_beginDragXY; - - return false; -} - -CBox IHyprLayout::workAreaOnWorkspace(const PHLWORKSPACE& pWorkspace) { - if (!pWorkspace || !pWorkspace->m_monitor) - return {}; - - const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(pWorkspace); - - auto workArea = pWorkspace->m_monitor->logicalBoxMinusReserved(); - - static auto PGAPSOUTDATA = CConfigValue("general:gaps_out"); - auto* const PGAPSOUT = sc((PGAPSOUTDATA.ptr())->getData()); - auto gapsOut = WORKSPACERULE.gapsOut.value_or(*PGAPSOUT); - - Desktop::CReservedArea reservedGaps{gapsOut.m_top, gapsOut.m_right, gapsOut.m_bottom, gapsOut.m_left}; - - reservedGaps.applyip(workArea); - - return workArea; -} diff --git a/src/layout/IHyprLayout.hpp b/src/layout/IHyprLayout.hpp deleted file mode 100644 index 0b23bc3b..00000000 --- a/src/layout/IHyprLayout.hpp +++ /dev/null @@ -1,248 +0,0 @@ -#pragma once - -#include "../defines.hpp" -#include "../managers/input/InputManager.hpp" -#include - -class CGradientValueData; - -struct SWindowRenderLayoutHints { - bool isBorderGradient = false; - CGradientValueData* borderGradient = nullptr; -}; - -struct SLayoutMessageHeader { - PHLWINDOW pWindow; -}; - -enum eFullscreenMode : int8_t; - -enum eRectCorner : uint8_t { - CORNER_NONE = 0, - CORNER_TOPLEFT = (1 << 0), - CORNER_TOPRIGHT = (1 << 1), - CORNER_BOTTOMRIGHT = (1 << 2), - CORNER_BOTTOMLEFT = (1 << 3), -}; - -inline eRectCorner cornerFromBox(const CBox& box, const Vector2D& pos) { - const auto CENTER = box.middle(); - - if (pos.x < CENTER.x) - return pos.y < CENTER.y ? CORNER_TOPLEFT : CORNER_BOTTOMLEFT; - return pos.y < CENTER.y ? CORNER_TOPRIGHT : CORNER_BOTTOMRIGHT; -} - -enum eSnapEdge : uint8_t { - SNAP_INVALID = 0, - SNAP_UP = (1 << 0), - SNAP_DOWN = (1 << 1), - SNAP_LEFT = (1 << 2), - SNAP_RIGHT = (1 << 3), -}; - -enum eDirection : int8_t { - DIRECTION_DEFAULT = -1, - DIRECTION_UP = 0, - DIRECTION_RIGHT, - DIRECTION_DOWN, - DIRECTION_LEFT -}; - -class IHyprLayout { - public: - virtual ~IHyprLayout() = default; - virtual void onEnable() = 0; - virtual void onDisable() = 0; - - /* - Called when a window is created (mapped) - The layout HAS TO set the goal pos and size (anim mgr will use it) - If !animationinprogress, then the anim mgr will not apply an anim. - */ - 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 - */ - virtual bool isWindowTiled(PHLWINDOW) = 0; - - /* - Called when a window is removed (unmapped) - */ - virtual void onWindowRemoved(PHLWINDOW); - virtual void onWindowRemovedTiling(PHLWINDOW) = 0; - virtual void onWindowRemovedFloating(PHLWINDOW); - /* - Called when the monitor requires a layout recalculation - this usually means reserved area changes - */ - virtual void recalculateMonitor(const MONITORID&) = 0; - - /* - Called when the compositor requests a window - to be recalculated, e.g. when pseudo is toggled. - */ - virtual void recalculateWindow(PHLWINDOW) = 0; - - /* - Called when a window is requested to be floated - */ - virtual void changeWindowFloatingMode(PHLWINDOW); - /* - Called when a window is clicked on, beginning a drag - this might be a resize, move, whatever the layout defines it - as. - */ - virtual void onBeginDragWindow(); - /* - Called when a user requests a resize of the current window by a vec - Vector2D holds pixel values - Optional pWindow for a specific window - */ - virtual void resizeActiveWindow(const Vector2D&, eRectCorner corner = CORNER_NONE, PHLWINDOW pWindow = nullptr) = 0; - /* - Called when a user requests a move of the current window by a vec - Vector2D holds pixel values - Optional pWindow for a specific window - */ - virtual void moveActiveWindow(const Vector2D&, PHLWINDOW pWindow = nullptr); - /* - Called when a window is ended being dragged - (mouse up) - */ - virtual void onEndDragWindow(); - /* - Called whenever the mouse moves, should the layout want to - do anything with it. - Useful for dragging. - */ - virtual void onMouseMove(const Vector2D&); - - /* - Called when a window / the user requests to toggle the fullscreen state of a window - The layout sets all the fullscreen flags. - It can either accept or ignore. - */ - virtual void fullscreenRequestForWindow(PHLWINDOW pWindow, const eFullscreenMode CURRENT_EFFECTIVE_MODE, const eFullscreenMode EFFECTIVE_MODE) = 0; - - /* - Called when a dispatcher requests a custom message - The layout is free to ignore. - std::any is the reply. Can be empty. - */ - virtual std::any layoutMessage(SLayoutMessageHeader, std::string) = 0; - - /* - Required to be handled, but may return just SWindowRenderLayoutHints() - Called when the renderer requests any special draw flags for - a specific window, e.g. border color for groups. - */ - virtual SWindowRenderLayoutHints requestRenderHints(PHLWINDOW) = 0; - - /* - Called when the user requests two windows to be swapped places. - The layout is free to ignore. - */ - virtual void switchWindows(PHLWINDOW, PHLWINDOW) = 0; - - /* - Called when the user requests a window move in a direction. - The layout is free to ignore. - */ - virtual void moveWindowTo(PHLWINDOW, const std::string& direction, bool silent = false) = 0; - - /* - Called when the user requests to change the splitratio by or to X - on a window - */ - virtual void alterSplitRatio(PHLWINDOW, float, bool exact = false) = 0; - - /* - Called when something wants the current layout's name - */ - virtual std::string getLayoutName() = 0; - - /* - Called for getting the next candidate for a focus - */ - virtual PHLWINDOW getNextWindowCandidate(PHLWINDOW); - - /* - Internal: called when window focus changes - */ - virtual void onWindowFocusChange(PHLWINDOW); - - /* - Called for replacing any data a layout has for a new window - */ - virtual void replaceWindowDataWith(PHLWINDOW from, PHLWINDOW to) = 0; - - /* - Determines if a window can be focused. If hidden this usually means the window is part of a group. - */ - virtual bool isWindowReachable(PHLWINDOW); - - /* - Called before an attempt is made to focus a window. - Brings the window to the top of any groups and ensures it is not hidden. - If the window is unmapped following this call, the focus attempt will fail. - */ - virtual void bringWindowToTop(PHLWINDOW); - - /* - Called via the foreign toplevel activation protocol. - Focuses a window, bringing it to the top of its group if applicable. - May be ignored. - */ - virtual void requestFocusForWindow(PHLWINDOW); - - /* - Called to predict the size of a newly opened window to send it a configure. - Return 0,0 if unpredictable - */ - virtual Vector2D predictSizeForNewWindowTiled() = 0; - - /* - Prefer not overriding, use predictSizeForNewWindowTiled. - */ - virtual Vector2D predictSizeForNewWindow(PHLWINDOW pWindow); - virtual Vector2D predictSizeForNewWindowFloating(PHLWINDOW pWindow); - - /* - Called to try to pick up window for dragging. - Updates drag related variables and floats window if threshold reached. - Return true to reject - */ - virtual bool updateDragWindow(); - - /* - Triggers a window snap event - */ - virtual void performSnap(Vector2D& sourcePos, Vector2D& sourceSize, PHLWINDOW DRAGGINGWINDOW, const eMouseBindMode MODE, const int CORNER, const Vector2D& BEGINSIZE); - - /* - Fits a floating window on its monitor - */ - virtual void fitFloatingWindowOnMonitor(PHLWINDOW w, std::optional targetBox = std::nullopt); - - /* - Returns a logical box describing the work area on a workspace - (monitor size - reserved - gapsOut) - */ - virtual CBox workAreaOnWorkspace(const PHLWORKSPACE& pWorkspace); - - private: - int m_mouseMoveEventCount; - Vector2D m_beginDragXY; - Vector2D m_lastDragXY; - Vector2D m_beginDragPositionXY; - Vector2D m_beginDragSizeXY; - Vector2D m_draggingWindowOriginalFloatSize; - eRectCorner m_grabbedCorner = CORNER_TOPLEFT; - - PHLWINDOWREF m_lastTiledWindow; -}; diff --git a/src/layout/LayoutManager.cpp b/src/layout/LayoutManager.cpp new file mode 100644 index 00000000..4a93809c --- /dev/null +++ b/src/layout/LayoutManager.cpp @@ -0,0 +1,345 @@ +#include "LayoutManager.hpp" + +#include "space/Space.hpp" +#include "target/Target.hpp" + +#include "../config/ConfigManager.hpp" +#include "../Compositor.hpp" +#include "../desktop/state/FocusState.hpp" +#include "../desktop/view/Group.hpp" +#include "../event/EventBus.hpp" + +using namespace Layout; + +CLayoutManager::CLayoutManager() = default; + +void CLayoutManager::newTarget(SP target, SP space) { + // on a new target: remember desired pos for float, if available + if (const auto DESIRED_GEOM = target->desiredGeometry(); DESIRED_GEOM) + target->rememberFloatingSize(DESIRED_GEOM->size); + + target->assignToSpace(space); +} + +void CLayoutManager::removeTarget(SP target) { + target->assignToSpace(nullptr); +} + +void CLayoutManager::changeFloatingMode(SP target) { + if (!target->space()) + return; + + target->space()->toggleTargetFloating(target); +} + +void CLayoutManager::beginDragTarget(SP target, eMouseBindMode mode) { + m_dragStateController->dragBegin(target, mode); +} + +void CLayoutManager::moveMouse(const Vector2D& mousePos) { + m_dragStateController->mouseMove(mousePos); +} + +void CLayoutManager::resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner) { + if (target->isPseudo()) { + auto fixedΔ = Δ; + if (corner == CORNER_TOPLEFT || corner == CORNER_BOTTOMLEFT) + fixedΔ.x = -fixedΔ.x; + if (corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT) + fixedΔ.y = -fixedΔ.y; + + auto newPseudoSize = target->pseudoSize() + fixedΔ; + const auto TARGET_TILE_SIZE = target->position().size(); + newPseudoSize.x = std::clamp(newPseudoSize.x, MIN_WINDOW_SIZE, TARGET_TILE_SIZE.x); + newPseudoSize.y = std::clamp(newPseudoSize.y, MIN_WINDOW_SIZE, TARGET_TILE_SIZE.y); + + target->setPseudoSize(newPseudoSize); + + return; + } + + target->space()->resizeTarget(Δ, target, corner); +} + +void CLayoutManager::setTargetGeom(const CBox& box, SP target) { + if (!target->floating()) + return; + + target->space()->setTargetGeom(box, target); +} + +std::expected CLayoutManager::layoutMsg(const std::string_view& sv) { + + const auto MONITOR = Desktop::focusState()->monitor(); + // forward to the active workspace + if (!MONITOR) + return std::unexpected("No monitor, can't find ws to target"); + + auto ws = MONITOR->m_activeSpecialWorkspace ? MONITOR->m_activeSpecialWorkspace : MONITOR->m_activeWorkspace; + + if (!ws) + return std::unexpected("No workspace, can't target"); + + return ws->m_space->layoutMsg(sv); +} + +void CLayoutManager::moveTarget(const Vector2D& Δ, SP target) { + if (!target->floating()) + return; + + target->space()->moveTarget(Δ, target); +} + +void CLayoutManager::endDragTarget() { + m_dragStateController->dragEnd(); +} + +void CLayoutManager::fullscreenRequestForTarget(SP target, eFullscreenMode currentEffectiveMode, eFullscreenMode effectiveMode) { + target->space()->setFullscreen(target, effectiveMode); +} + +void CLayoutManager::switchTargets(SP a, SP b, bool preserveFocus) { + + if (preserveFocus) { + a->swap(b); + return; + } + + const auto IS_A_ACTIVE = Desktop::focusState()->window() == a->window(); + const auto IS_B_ACTIVE = Desktop::focusState()->window() == b->window(); + + a->swap(b); + + if (IS_A_ACTIVE && b->window()) + Desktop::focusState()->fullWindowFocus(b->window(), Desktop::FOCUS_REASON_KEYBIND); + + if (IS_B_ACTIVE && a->window()) + Desktop::focusState()->fullWindowFocus(a->window(), Desktop::FOCUS_REASON_KEYBIND); +} + +void CLayoutManager::moveInDirection(SP target, const std::string& direction, bool silent) { + Math::eDirection dir = Math::fromChar(direction.at(0)); + if (dir == Math::DIRECTION_DEFAULT) { + Log::logger->log(Log::ERR, "invalid direction for moveInDirection: {}", direction); + return; + } + + target->space()->moveTargetInDirection(target, dir, silent); +} + +SP CLayoutManager::getNextCandidate(SP space, SP from) { + return space->getNextCandidate(from); +} + +bool CLayoutManager::isReachable(SP target) { + return true; +} + +void CLayoutManager::bringTargetToTop(SP target) { + if (!target) + return; + + if (target->window()->m_group) { + // grouped, change the current to this window + target->window()->m_group->setCurrent(target->window()); + } +} + +std::optional CLayoutManager::predictSizeForNewTiledTarget() { + const auto FOCUSED_MON = Desktop::focusState()->monitor(); + + if (!FOCUSED_MON || !FOCUSED_MON->m_activeWorkspace) + return std::nullopt; + + if (FOCUSED_MON->m_activeSpecialWorkspace) + return FOCUSED_MON->m_activeSpecialWorkspace->m_space->predictSizeForNewTiledTarget(); + + return FOCUSED_MON->m_activeWorkspace->m_space->predictSizeForNewTiledTarget(); +} + +const UP& CLayoutManager::dragController() { + return m_dragStateController; +} + +static inline bool canSnap(const double SIDEA, const double SIDEB, const double GAP) { + return std::abs(SIDEA - SIDEB) < GAP; +} + +static void snapMove(double& start, double& end, const double P) { + end = P + (end - start); + start = P; +} + +static void snapResize(double& start, double& end, const double P) { + start = P; +} + +using SnapFn = std::function; + +void CLayoutManager::performSnap(Vector2D& sourcePos, Vector2D& sourceSize, SP DRAGGINGTARGET, const eMouseBindMode MODE, const int CORNER, const Vector2D& BEGINSIZE) { + + const auto DRAGGINGWINDOW = DRAGGINGTARGET->window(); + + if (!Desktop::View::validMapped(DRAGGINGWINDOW)) + return; + + static auto SNAPWINDOWGAP = CConfigValue("general:snap:window_gap"); + static auto SNAPMONITORGAP = CConfigValue("general:snap:monitor_gap"); + static auto SNAPBORDEROVERLAP = CConfigValue("general:snap:border_overlap"); + static auto SNAPRESPECTGAPS = CConfigValue("general:snap:respect_gaps"); + + static auto PGAPSIN = CConfigValue("general:gaps_in"); + static auto PGAPSOUT = CConfigValue("general:gaps_out"); + const auto GAPSNONE = CCssGapData{0, 0, 0, 0}; + + const SnapFn SNAP = (MODE == MBIND_MOVE) ? snapMove : snapResize; + int snaps = 0; + + struct SRange { + double start = 0; + double end = 0; + }; + const auto EXTENTS = DRAGGINGWINDOW->getWindowExtentsUnified(Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS); + SRange sourceX = {sourcePos.x - EXTENTS.topLeft.x, sourcePos.x + sourceSize.x + EXTENTS.bottomRight.x}; + SRange sourceY = {sourcePos.y - EXTENTS.topLeft.y, sourcePos.y + sourceSize.y + EXTENTS.bottomRight.y}; + + if (*SNAPWINDOWGAP) { + const double GAPSIZE = *SNAPWINDOWGAP; + const auto WSID = DRAGGINGWINDOW->workspaceID(); + const bool HASFULLSCREEN = DRAGGINGWINDOW->m_workspace && DRAGGINGWINDOW->m_workspace->m_hasFullscreenWindow; + + const auto* GAPSIN = *SNAPRESPECTGAPS ? sc(PGAPSIN.ptr()->getData()) : &GAPSNONE; + const double GAPSX = GAPSIN->m_left + GAPSIN->m_right; + const double GAPSY = GAPSIN->m_top + GAPSIN->m_bottom; + + for (auto& other : g_pCompositor->m_windows) { + if ((HASFULLSCREEN && !other->m_createdOverFullscreen) || other == DRAGGINGWINDOW || other->workspaceID() != WSID || !other->m_isMapped || other->m_fadingOut || + other->isX11OverrideRedirect()) + continue; + + const CBox SURF = other->getWindowBoxUnified(Desktop::View::RESERVED_EXTENTS); + const SRange SURFBX = {SURF.x - GAPSX, SURF.x + SURF.w + GAPSX}; + const SRange SURFBY = {SURF.y - GAPSY, SURF.y + SURF.h + GAPSY}; + + // only snap windows if their ranges overlap in the opposite axis + if (sourceY.start <= SURFBY.end && SURFBY.start <= sourceY.end) { + if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && canSnap(sourceX.start, SURFBX.end, GAPSIZE)) { + SNAP(sourceX.start, sourceX.end, SURFBX.end); + snaps |= SNAP_LEFT; + } else if (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && canSnap(sourceX.end, SURFBX.start, GAPSIZE)) { + SNAP(sourceX.end, sourceX.start, SURFBX.start); + snaps |= SNAP_RIGHT; + } + } + if (sourceX.start <= SURFBX.end && SURFBX.start <= sourceX.end) { + if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && canSnap(sourceY.start, SURFBY.end, GAPSIZE)) { + SNAP(sourceY.start, sourceY.end, SURFBY.end); + snaps |= SNAP_UP; + } else if (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && canSnap(sourceY.end, SURFBY.start, GAPSIZE)) { + SNAP(sourceY.end, sourceY.start, SURFBY.start); + snaps |= SNAP_DOWN; + } + } + + // corner snapping + if (sourceX.start == SURFBX.end || SURFBX.start == sourceX.end) { + const SRange SURFY = {SURFBY.start + GAPSY, SURFBY.end - GAPSY}; + if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && !(snaps & SNAP_UP) && canSnap(sourceY.start, SURFY.start, GAPSIZE)) { + SNAP(sourceY.start, sourceY.end, SURFY.start); + snaps |= SNAP_UP; + } else if (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && !(snaps & SNAP_DOWN) && canSnap(sourceY.end, SURFY.end, GAPSIZE)) { + SNAP(sourceY.end, sourceY.start, SURFY.end); + snaps |= SNAP_DOWN; + } + } + if (sourceY.start == SURFBY.end || SURFBY.start == sourceY.end) { + const SRange SURFX = {SURFBX.start + GAPSX, SURFBX.end - GAPSX}; + if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && !(snaps & SNAP_LEFT) && canSnap(sourceX.start, SURFX.start, GAPSIZE)) { + SNAP(sourceX.start, sourceX.end, SURFX.start); + snaps |= SNAP_LEFT; + } else if (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && !(snaps & SNAP_RIGHT) && canSnap(sourceX.end, SURFX.end, GAPSIZE)) { + SNAP(sourceX.end, sourceX.start, SURFX.end); + snaps |= SNAP_RIGHT; + } + } + } + } + + if (*SNAPMONITORGAP) { + const double GAPSIZE = *SNAPMONITORGAP; + const auto EXTENTNONE = SBoxExtents{{0, 0}, {0, 0}}; + const auto* EXTENTDIFF = *SNAPBORDEROVERLAP ? &EXTENTS : &EXTENTNONE; + const auto MON = DRAGGINGWINDOW->m_monitor.lock(); + + const auto* GAPSOUT = *SNAPRESPECTGAPS ? sc(PGAPSOUT.ptr()->getData()) : &GAPSNONE; + const auto WORK_AREA = Desktop::CReservedArea{GAPSOUT->m_top, GAPSOUT->m_right, GAPSOUT->m_bottom, GAPSOUT->m_left}.apply(MON->logicalBoxMinusReserved()); + + SRange monX = {WORK_AREA.x, WORK_AREA.x + WORK_AREA.w}; + SRange monY = {WORK_AREA.y, WORK_AREA.y + WORK_AREA.h}; + + const bool HAS_LEFT = MON->m_reservedArea.left() > 0; + const bool HAS_TOP = MON->m_reservedArea.top() > 0; + const bool HAS_BOTTOM = MON->m_reservedArea.bottom() > 0; + const bool HAS_RIGHT = MON->m_reservedArea.right() > 0; + + if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && + ((HAS_LEFT && canSnap(sourceX.start, monX.start, GAPSIZE)) || canSnap(sourceX.start, (monX.start -= MON->m_reservedArea.left() + EXTENTDIFF->topLeft.x), GAPSIZE))) { + SNAP(sourceX.start, sourceX.end, monX.start); + snaps |= SNAP_LEFT; + } + if (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && + ((HAS_RIGHT && canSnap(sourceX.end, monX.end, GAPSIZE)) || canSnap(sourceX.end, (monX.end += MON->m_reservedArea.right() + EXTENTDIFF->bottomRight.x), GAPSIZE))) { + SNAP(sourceX.end, sourceX.start, monX.end); + snaps |= SNAP_RIGHT; + } + if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && + ((HAS_TOP && canSnap(sourceY.start, monY.start, GAPSIZE)) || canSnap(sourceY.start, (monY.start -= MON->m_reservedArea.top() + EXTENTDIFF->topLeft.y), GAPSIZE))) { + SNAP(sourceY.start, sourceY.end, monY.start); + snaps |= SNAP_UP; + } + if (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && + ((HAS_BOTTOM && canSnap(sourceY.end, monY.end, GAPSIZE)) || canSnap(sourceY.end, (monY.end += MON->m_reservedArea.bottom() + EXTENTDIFF->bottomRight.y), GAPSIZE))) { + SNAP(sourceY.end, sourceY.start, monY.end); + snaps |= SNAP_DOWN; + } + } + + // remove extents from main surface + sourceX = {sourceX.start + EXTENTS.topLeft.x, sourceX.end - EXTENTS.bottomRight.x}; + sourceY = {sourceY.start + EXTENTS.topLeft.y, sourceY.end - EXTENTS.bottomRight.y}; + + if (MODE == MBIND_RESIZE_FORCE_RATIO) { + if ((CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT) && snaps & SNAP_LEFT) || (CORNER & (CORNER_TOPRIGHT | CORNER_BOTTOMRIGHT) && snaps & SNAP_RIGHT)) { + const double SIZEY = (sourceX.end - sourceX.start) * (BEGINSIZE.y / BEGINSIZE.x); + if (CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT)) + sourceY.start = sourceY.end - SIZEY; + else + sourceY.end = sourceY.start + SIZEY; + } else if ((CORNER & (CORNER_TOPLEFT | CORNER_TOPRIGHT) && snaps & SNAP_UP) || (CORNER & (CORNER_BOTTOMLEFT | CORNER_BOTTOMRIGHT) && snaps & SNAP_DOWN)) { + const double SIZEX = (sourceY.end - sourceY.start) * (BEGINSIZE.x / BEGINSIZE.y); + if (CORNER & (CORNER_TOPLEFT | CORNER_BOTTOMLEFT)) + sourceX.start = sourceX.end - SIZEX; + else + sourceX.end = sourceX.start + SIZEX; + } + } + + sourcePos = {sourceX.start, sourceY.start}; + sourceSize = {sourceX.end - sourceX.start, sourceY.end - sourceY.start}; +} + +void CLayoutManager::recalculateMonitor(PHLMONITOR m) { + if (m->m_activeSpecialWorkspace) + m->m_activeSpecialWorkspace->m_space->recalculate(); + if (m->m_activeWorkspace) + m->m_activeWorkspace->m_space->recalculate(); +} + +void CLayoutManager::invalidateMonitorGeometries(PHLMONITOR m) { + for (const auto& ws : g_pCompositor->getWorkspaces()) { + if (ws && ws->m_monitor == m) { + ws->m_space->recheckWorkArea(); + ws->m_space->recalculate(); + } + } +} diff --git a/src/layout/LayoutManager.hpp b/src/layout/LayoutManager.hpp new file mode 100644 index 00000000..e99911d5 --- /dev/null +++ b/src/layout/LayoutManager.hpp @@ -0,0 +1,87 @@ +#pragma once + +#include "../helpers/memory/Memory.hpp" +#include "../helpers/math/Math.hpp" +#include "../managers/input/InputManager.hpp" + +#include "supplementary/DragController.hpp" + +#include +#include + +enum eFullscreenMode : int8_t; + +namespace Layout { + class ITarget; + class CSpace; + + enum eRectCorner : uint8_t { + CORNER_NONE = 0, + CORNER_TOPLEFT = (1 << 0), + CORNER_TOPRIGHT = (1 << 1), + CORNER_BOTTOMRIGHT = (1 << 2), + CORNER_BOTTOMLEFT = (1 << 3), + }; + + inline eRectCorner cornerFromBox(const CBox& box, const Vector2D& pos) { + const auto CENTER = box.middle(); + + if (pos.x < CENTER.x) + return pos.y < CENTER.y ? CORNER_TOPLEFT : CORNER_BOTTOMLEFT; + return pos.y < CENTER.y ? CORNER_TOPRIGHT : CORNER_BOTTOMRIGHT; + } + + enum eSnapEdge : uint8_t { + SNAP_INVALID = 0, + SNAP_UP = (1 << 0), + SNAP_DOWN = (1 << 1), + SNAP_LEFT = (1 << 2), + SNAP_RIGHT = (1 << 3), + }; + + class CLayoutManager { + public: + CLayoutManager(); + ~CLayoutManager() = default; + + void newTarget(SP target, SP space); + void removeTarget(SP target); + + void changeFloatingMode(SP target); + + void beginDragTarget(SP target, eMouseBindMode mode); + void moveMouse(const Vector2D& mousePos); + void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); + void moveTarget(const Vector2D& Δ, SP target); + void setTargetGeom(const CBox& box, SP target); // floats only + void endDragTarget(); + + std::expected layoutMsg(const std::string_view& sv); + + void fullscreenRequestForTarget(SP target, eFullscreenMode currentEffectiveMode, eFullscreenMode effectiveMode); + + void switchTargets(SP a, SP b, bool preserveFocus = true); + + void moveInDirection(SP target, const std::string& direction, bool silent = false); + + SP getNextCandidate(SP space, SP from); + + bool isReachable(SP target); + + void bringTargetToTop(SP target); + + std::optional predictSizeForNewTiledTarget(); + + void performSnap(Vector2D& sourcePos, Vector2D& sourceSize, SP target, eMouseBindMode mode, int corner, const Vector2D& beginSize); + + void invalidateMonitorGeometries(PHLMONITOR); + void recalculateMonitor(PHLMONITOR); + + const UP& dragController(); + + private: + UP m_dragStateController = makeUnique(); + }; +} + +inline UP g_layoutManager; \ No newline at end of file diff --git a/src/layout/MasterLayout.cpp b/src/layout/MasterLayout.cpp deleted file mode 100644 index 8c6376ab..00000000 --- a/src/layout/MasterLayout.cpp +++ /dev/null @@ -1,1526 +0,0 @@ -#include "MasterLayout.hpp" -#include "../Compositor.hpp" -#include "../render/decorations/CHyprGroupBarDecoration.hpp" -#include "config/ConfigDataValues.hpp" -#include -#include "../config/ConfigValue.hpp" -#include "../config/ConfigManager.hpp" -#include "../render/Renderer.hpp" -#include "../managers/input/InputManager.hpp" -#include "../managers/LayoutManager.hpp" -#include "../managers/EventManager.hpp" -#include "../desktop/state/FocusState.hpp" -#include "xwayland/XWayland.hpp" - -SMasterNodeData* CHyprMasterLayout::getNodeFromWindow(PHLWINDOW pWindow) { - for (auto& nd : m_masterNodesData) { - if (nd.pWindow.lock() == pWindow) - return &nd; - } - - return nullptr; -} - -int CHyprMasterLayout::getNodesOnWorkspace(const WORKSPACEID& ws) { - int no = 0; - for (auto const& n : m_masterNodesData) { - if (n.workspaceID == ws) - no++; - } - - return no; -} - -int CHyprMasterLayout::getMastersOnWorkspace(const WORKSPACEID& ws) { - int no = 0; - for (auto const& n : m_masterNodesData) { - if (n.workspaceID == ws && n.isMaster) - no++; - } - - return no; -} - -SMasterWorkspaceData* CHyprMasterLayout::getMasterWorkspaceData(const WORKSPACEID& ws) { - for (auto& n : m_masterWorkspacesData) { - if (n.workspaceID == ws) - return &n; - } - - //create on the fly if it doesn't exist yet - const auto PWORKSPACEDATA = &m_masterWorkspacesData.emplace_back(); - PWORKSPACEDATA->workspaceID = ws; - static auto PORIENTATION = CConfigValue("master:orientation"); - - if (*PORIENTATION == "top") - PWORKSPACEDATA->orientation = ORIENTATION_TOP; - else if (*PORIENTATION == "right") - PWORKSPACEDATA->orientation = ORIENTATION_RIGHT; - else if (*PORIENTATION == "bottom") - PWORKSPACEDATA->orientation = ORIENTATION_BOTTOM; - else if (*PORIENTATION == "center") - PWORKSPACEDATA->orientation = ORIENTATION_CENTER; - else - PWORKSPACEDATA->orientation = ORIENTATION_LEFT; - - return PWORKSPACEDATA; -} - -std::string CHyprMasterLayout::getLayoutName() { - return "Master"; -} - -SMasterNodeData* CHyprMasterLayout::getMasterNodeOnWorkspace(const WORKSPACEID& ws) { - for (auto& n : m_masterNodesData) { - if (n.isMaster && n.workspaceID == ws) - return &n; - } - - return nullptr; -} - -void CHyprMasterLayout::onWindowCreatedTiling(PHLWINDOW pWindow, eDirection direction) { - if (pWindow->m_isFloating) - return; - - static auto PNEWONACTIVE = CConfigValue("master:new_on_active"); - static auto PNEWONTOP = CConfigValue("master:new_on_top"); - static auto PNEWSTATUS = CConfigValue("master:new_status"); - - const auto PMONITOR = pWindow->m_monitor.lock(); - - const bool BNEWBEFOREACTIVE = *PNEWONACTIVE == "before"; - const bool BNEWISMASTER = *PNEWSTATUS == "master"; - - const auto PNODE = [&]() { - if (*PNEWONACTIVE != "none" && !BNEWISMASTER) { - const auto pLastNode = getNodeFromWindow(Desktop::focusState()->window()); - if (pLastNode && !(pLastNode->isMaster && (getMastersOnWorkspace(pWindow->workspaceID()) == 1 || *PNEWSTATUS == "slave"))) { - auto it = std::ranges::find(m_masterNodesData, *pLastNode); - if (!BNEWBEFOREACTIVE) - ++it; - return &(*m_masterNodesData.emplace(it)); - } - } - return *PNEWONTOP ? &m_masterNodesData.emplace_front() : &m_masterNodesData.emplace_back(); - }(); - - PNODE->workspaceID = pWindow->workspaceID(); - PNODE->pWindow = pWindow; - - const auto WINDOWSONWORKSPACE = getNodesOnWorkspace(PNODE->workspaceID); - static auto PMFACT = CConfigValue("master:mfact"); - float lastSplitPercent = *PMFACT; - - auto OPENINGON = isWindowTiled(Desktop::focusState()->window()) && Desktop::focusState()->window()->m_workspace == pWindow->m_workspace ? - getNodeFromWindow(Desktop::focusState()->window()) : - getMasterNodeOnWorkspace(pWindow->workspaceID()); - - const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal(); - static auto PDROPATCURSOR = CConfigValue("master:drop_at_cursor"); - eOrientation orientation = getDynamicOrientation(pWindow->m_workspace); - const auto NODEIT = std::ranges::find(m_masterNodesData, *PNODE); - - bool forceDropAsMaster = false; - // if dragging window to move, drop it at the cursor position instead of bottom/top of stack - if (*PDROPATCURSOR && g_pInputManager->m_dragMode == MBIND_MOVE) { - if (WINDOWSONWORKSPACE > 2) { - for (auto it = m_masterNodesData.begin(); it != m_masterNodesData.end(); ++it) { - if (it->workspaceID != pWindow->workspaceID()) - continue; - const CBox box = it->pWindow->getWindowIdealBoundingBoxIgnoreReserved(); - if (box.containsPoint(MOUSECOORDS)) { - switch (orientation) { - case ORIENTATION_LEFT: - case ORIENTATION_RIGHT: - if (MOUSECOORDS.y > it->pWindow->middle().y) - ++it; - break; - case ORIENTATION_TOP: - case ORIENTATION_BOTTOM: - if (MOUSECOORDS.x > it->pWindow->middle().x) - ++it; - break; - case ORIENTATION_CENTER: break; - default: UNREACHABLE(); - } - m_masterNodesData.splice(it, m_masterNodesData, NODEIT); - break; - } - } - } 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 const& nd : m_masterNodesData) { - if (nd.isMaster && nd.workspaceID == PNODE->workspaceID) { - switch (orientation) { - case ORIENTATION_LEFT: - case ORIENTATION_CENTER: - if (MOUSECOORDS.x < nd.pWindow->middle().x) - forceDropAsMaster = true; - break; - case ORIENTATION_RIGHT: - if (MOUSECOORDS.x > nd.pWindow->middle().x) - forceDropAsMaster = true; - break; - case ORIENTATION_TOP: - if (MOUSECOORDS.y < nd.pWindow->middle().y) - forceDropAsMaster = true; - break; - case ORIENTATION_BOTTOM: - if (MOUSECOORDS.y > nd.pWindow->middle().y) - forceDropAsMaster = true; - break; - default: UNREACHABLE(); - } - break; - } - } - } - } - - if ((BNEWISMASTER && g_pInputManager->m_dragMode != MBIND_MOVE) // - || WINDOWSONWORKSPACE == 1 // - || (WINDOWSONWORKSPACE > 2 && !pWindow->m_firstMap && OPENINGON && OPENINGON->isMaster) // - || forceDropAsMaster // - || (*PNEWSTATUS == "inherit" && OPENINGON && OPENINGON->isMaster && g_pInputManager->m_dragMode != MBIND_MOVE)) { - - if (BNEWBEFOREACTIVE) { - for (auto& nd : m_masterNodesData | std::views::reverse) { - if (nd.isMaster && nd.workspaceID == PNODE->workspaceID) { - nd.isMaster = false; - lastSplitPercent = nd.percMaster; - break; - } - } - } else { - for (auto& nd : m_masterNodesData) { - if (nd.isMaster && nd.workspaceID == PNODE->workspaceID) { - nd.isMaster = false; - lastSplitPercent = nd.percMaster; - break; - } - } - } - - PNODE->isMaster = true; - PNODE->percMaster = lastSplitPercent; - - // first, check if it isn't too big. - if (const auto MAXSIZE = pWindow->maxSize().value_or(Math::VECTOR2D_MAX); MAXSIZE.x < PMONITOR->m_size.x * lastSplitPercent || MAXSIZE.y < PMONITOR->m_size.y) { - // we can't continue. make it floating. - pWindow->m_isFloating = true; - m_masterNodesData.remove(*PNODE); - g_pLayoutManager->getCurrentLayout()->onWindowCreatedFloating(pWindow); - return; - } - } else { - PNODE->isMaster = false; - PNODE->percMaster = lastSplitPercent; - - // first, check if it isn't too big. - if (const auto MAXSIZE = pWindow->maxSize().value_or(Math::VECTOR2D_MAX); - MAXSIZE.x < PMONITOR->m_size.x * (1 - lastSplitPercent) || MAXSIZE.y < PMONITOR->m_size.y * (1.f / (WINDOWSONWORKSPACE - 1))) { - // we can't continue. make it floating. - pWindow->m_isFloating = true; - m_masterNodesData.remove(*PNODE); - g_pLayoutManager->getCurrentLayout()->onWindowCreatedFloating(pWindow); - return; - } - } - - // recalc - recalculateMonitor(pWindow->monitorID()); - pWindow->m_workspace->updateWindows(); -} - -void CHyprMasterLayout::onWindowRemovedTiling(PHLWINDOW pWindow) { - const auto PNODE = getNodeFromWindow(pWindow); - - if (!PNODE) - return; - - const auto WORKSPACEID = PNODE->workspaceID; - const auto MASTERSLEFT = getMastersOnWorkspace(WORKSPACEID); - static auto SMALLSPLIT = CConfigValue("master:allow_small_split"); - - pWindow->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT); - pWindow->updateWindowData(); - - if (pWindow->isFullscreen()) - g_pCompositor->setWindowFullscreenInternal(pWindow, FSMODE_NONE); - - if (PNODE->isMaster && (MASTERSLEFT <= 1 || *SMALLSPLIT == 1)) { - // find a new master from top of the list - for (auto& nd : m_masterNodesData) { - if (!nd.isMaster && nd.workspaceID == WORKSPACEID) { - nd.isMaster = true; - nd.percMaster = PNODE->percMaster; - break; - } - } - } - - m_masterNodesData.remove(*PNODE); - - if (getMastersOnWorkspace(WORKSPACEID) == getNodesOnWorkspace(WORKSPACEID) && MASTERSLEFT > 1) { - for (auto& nd : m_masterNodesData | std::views::reverse) { - if (nd.workspaceID == WORKSPACEID) { - nd.isMaster = false; - break; - } - } - } - // BUGFIX: correct bug where closing one master in a stack of 2 would leave - // the screen half bare, and make it difficult to select remaining window - if (getNodesOnWorkspace(WORKSPACEID) == 1) { - for (auto& nd : m_masterNodesData) { - if (nd.workspaceID == WORKSPACEID && !nd.isMaster) { - nd.isMaster = true; - break; - } - } - } - recalculateMonitor(pWindow->monitorID()); - pWindow->m_workspace->updateWindows(); -} - -void CHyprMasterLayout::recalculateMonitor(const MONITORID& monid) { - const auto PMONITOR = g_pCompositor->getMonitorFromID(monid); - - if (!PMONITOR || !PMONITOR->m_activeWorkspace) - return; - - g_pHyprRenderer->damageMonitor(PMONITOR); - - if (PMONITOR->m_activeSpecialWorkspace) - calculateWorkspace(PMONITOR->m_activeSpecialWorkspace); - - calculateWorkspace(PMONITOR->m_activeWorkspace); - -#ifndef NO_XWAYLAND - CBox box = g_pCompositor->calculateX11WorkArea(); - if (!g_pXWayland || !g_pXWayland->m_wm) - return; - g_pXWayland->m_wm->updateWorkArea(box.x, box.y, box.w, box.h); -#endif -} - -void CHyprMasterLayout::calculateWorkspace(PHLWORKSPACE pWorkspace) { - const auto PMONITOR = pWorkspace->m_monitor.lock(); - - if (!PMONITOR) - return; - - if (pWorkspace->m_hasFullscreenWindow) { - // massive hack from the fullscreen func - const auto PFULLWINDOW = pWorkspace->getFullscreenWindow(); - - if (pWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN) { - *PFULLWINDOW->m_realPosition = PMONITOR->m_position; - *PFULLWINDOW->m_realSize = PMONITOR->m_size; - } else if (pWorkspace->m_fullscreenMode == FSMODE_MAXIMIZED) { - SMasterNodeData fakeNode; - fakeNode.pWindow = PFULLWINDOW; - const auto WORKAREA = workAreaOnWorkspace(pWorkspace); - fakeNode.position = WORKAREA.pos(); - fakeNode.size = WORKAREA.size(); - fakeNode.workspaceID = pWorkspace->m_id; - PFULLWINDOW->m_position = fakeNode.position; - PFULLWINDOW->m_size = fakeNode.size; - fakeNode.ignoreFullscreenChecks = true; - - applyNodeDataToWindow(&fakeNode); - } - - // if has fullscreen, don't calculate the rest - return; - } - - const auto PMASTERNODE = getMasterNodeOnWorkspace(pWorkspace->m_id); - - if (!PMASTERNODE) - return; - - eOrientation orientation = getDynamicOrientation(pWorkspace); - bool centerMasterWindow = false; - static auto SLAVECOUNTFORCENTER = CConfigValue("master:slave_count_for_center_master"); - static auto CMFALLBACK = CConfigValue("master:center_master_fallback"); - static auto PIGNORERESERVED = CConfigValue("master:center_ignores_reserved"); - static auto PSMARTRESIZING = CConfigValue("master:smart_resizing"); - - const auto MASTERS = getMastersOnWorkspace(pWorkspace->m_id); - const auto WINDOWS = getNodesOnWorkspace(pWorkspace->m_id); - const auto STACKWINDOWS = WINDOWS - MASTERS; - const auto WORKAREA = workAreaOnWorkspace(pWorkspace); - const auto UNRESERVED_WIDTH = WORKAREA.width + PMONITOR->m_reservedArea.left() + PMONITOR->m_reservedArea.right(); - - if (orientation == ORIENTATION_CENTER) { - if (STACKWINDOWS >= *SLAVECOUNTFORCENTER) - centerMasterWindow = true; - else { - if (*CMFALLBACK == "left") - orientation = ORIENTATION_LEFT; - else if (*CMFALLBACK == "right") - orientation = ORIENTATION_RIGHT; - else if (*CMFALLBACK == "top") - orientation = ORIENTATION_TOP; - else if (*CMFALLBACK == "bottom") - orientation = ORIENTATION_BOTTOM; - else - orientation = ORIENTATION_LEFT; - } - } - - const float totalSize = (orientation == ORIENTATION_TOP || orientation == ORIENTATION_BOTTOM) ? WORKAREA.w : WORKAREA.h; - const float masterAverageSize = totalSize / MASTERS; - const float slaveAverageSize = totalSize / STACKWINDOWS; - float masterAccumulatedSize = 0; - float slaveAccumulatedSize = 0; - - if (*PSMARTRESIZING) { - // check the total width and height so that later - // if larger/smaller than screen size them down/up - for (auto const& nd : m_masterNodesData) { - if (nd.workspaceID == pWorkspace->m_id) { - if (nd.isMaster) - masterAccumulatedSize += totalSize / MASTERS * nd.percSize; - else - slaveAccumulatedSize += totalSize / STACKWINDOWS * nd.percSize; - } - } - } - - // compute placement of master window(s) - if (WINDOWS == 1 && !centerMasterWindow) { - static auto PALWAYSKEEPPOSITION = CConfigValue("master:always_keep_position"); - if (*PALWAYSKEEPPOSITION) { - const float WIDTH = WORKAREA.w * PMASTERNODE->percMaster; - float nextX = 0; - - if (orientation == ORIENTATION_RIGHT) - nextX = WORKAREA.w - WIDTH; - else if (orientation == ORIENTATION_CENTER) - nextX = (WORKAREA.w - WIDTH) / 2; - - PMASTERNODE->size = Vector2D(WIDTH, WORKAREA.h); - PMASTERNODE->position = WORKAREA.pos() + Vector2D(nextX, 0.0); - } else { - PMASTERNODE->size = WORKAREA.size(); - PMASTERNODE->position = WORKAREA.pos(); - } - - applyNodeDataToWindow(PMASTERNODE); - return; - } else if (orientation == ORIENTATION_TOP || orientation == ORIENTATION_BOTTOM) { - const float HEIGHT = STACKWINDOWS != 0 ? WORKAREA.h * PMASTERNODE->percMaster : WORKAREA.h; - float widthLeft = WORKAREA.w; - int mastersLeft = MASTERS; - float nextX = 0; - float nextY = 0; - - if (orientation == ORIENTATION_BOTTOM) - nextY = WORKAREA.h - HEIGHT; - - for (auto& nd : m_masterNodesData) { - if (nd.workspaceID != pWorkspace->m_id || !nd.isMaster) - continue; - - float WIDTH = mastersLeft > 1 ? widthLeft / mastersLeft * nd.percSize : widthLeft; - if (WIDTH > widthLeft * 0.9f && mastersLeft > 1) - WIDTH = widthLeft * 0.9f; - - if (*PSMARTRESIZING) { - nd.percSize *= WORKAREA.w / masterAccumulatedSize; - WIDTH = masterAverageSize * nd.percSize; - } - - nd.size = Vector2D(WIDTH, HEIGHT); - nd.position = WORKAREA.pos() + Vector2D(nextX, nextY); - applyNodeDataToWindow(&nd); - - mastersLeft--; - widthLeft -= WIDTH; - nextX += WIDTH; - } - } else { // orientation left, right or center - const float TOTAL_WIDTH = *PIGNORERESERVED && centerMasterWindow ? UNRESERVED_WIDTH : WORKAREA.w; - float WIDTH = TOTAL_WIDTH; - float heightLeft = WORKAREA.h; - int mastersLeft = MASTERS; - float nextX = 0; - float nextY = 0; - - if (STACKWINDOWS > 0 || centerMasterWindow) - WIDTH *= PMASTERNODE->percMaster; - - if (orientation == ORIENTATION_RIGHT) - nextX = WORKAREA.w - WIDTH; - else if (centerMasterWindow) - nextX += (TOTAL_WIDTH - WIDTH) / 2; - - for (auto& nd : m_masterNodesData) { - if (nd.workspaceID != pWorkspace->m_id || !nd.isMaster) - continue; - - float HEIGHT = mastersLeft > 1 ? heightLeft / mastersLeft * nd.percSize : heightLeft; - if (HEIGHT > heightLeft * 0.9f && mastersLeft > 1) - HEIGHT = heightLeft * 0.9f; - - if (*PSMARTRESIZING) { - nd.percSize *= WORKAREA.h / masterAccumulatedSize; - HEIGHT = masterAverageSize * nd.percSize; - } - - nd.size = Vector2D(WIDTH, HEIGHT); - nd.position = (*PIGNORERESERVED && centerMasterWindow ? WORKAREA.pos() - Vector2D(PMONITOR->m_reservedArea.left(), 0.0) : WORKAREA.pos()) + Vector2D(nextX, nextY); - applyNodeDataToWindow(&nd); - - mastersLeft--; - heightLeft -= HEIGHT; - nextY += HEIGHT; - } - } - - if (STACKWINDOWS == 0) - return; - - // compute placement of slave window(s) - int slavesLeft = STACKWINDOWS; - if (orientation == ORIENTATION_TOP || orientation == ORIENTATION_BOTTOM) { - const float HEIGHT = WORKAREA.h - PMASTERNODE->size.y; - float widthLeft = WORKAREA.w; - float nextX = 0; - float nextY = 0; - - if (orientation == ORIENTATION_TOP) - nextY = PMASTERNODE->size.y; - - for (auto& nd : m_masterNodesData) { - if (nd.workspaceID != pWorkspace->m_id || nd.isMaster) - continue; - - float WIDTH = slavesLeft > 1 ? widthLeft / slavesLeft * nd.percSize : widthLeft; - if (WIDTH > widthLeft * 0.9f && slavesLeft > 1) - WIDTH = widthLeft * 0.9f; - - if (*PSMARTRESIZING) { - nd.percSize *= WORKAREA.w / slaveAccumulatedSize; - WIDTH = slaveAverageSize * nd.percSize; - } - - nd.size = Vector2D(WIDTH, HEIGHT); - nd.position = WORKAREA.pos() + Vector2D(nextX, nextY); - applyNodeDataToWindow(&nd); - - slavesLeft--; - widthLeft -= WIDTH; - nextX += WIDTH; - } - } else if (orientation == ORIENTATION_LEFT || orientation == ORIENTATION_RIGHT) { - const float WIDTH = WORKAREA.w - PMASTERNODE->size.x; - float heightLeft = WORKAREA.h; - float nextY = 0; - float nextX = 0; - - if (orientation == ORIENTATION_LEFT) - nextX = PMASTERNODE->size.x; - - for (auto& nd : m_masterNodesData) { - if (nd.workspaceID != pWorkspace->m_id || nd.isMaster) - continue; - - float HEIGHT = slavesLeft > 1 ? heightLeft / slavesLeft * nd.percSize : heightLeft; - if (HEIGHT > heightLeft * 0.9f && slavesLeft > 1) - HEIGHT = heightLeft * 0.9f; - - if (*PSMARTRESIZING) { - nd.percSize *= WORKAREA.h / slaveAccumulatedSize; - HEIGHT = slaveAverageSize * nd.percSize; - } - - nd.size = Vector2D(WIDTH, HEIGHT); - nd.position = WORKAREA.pos() + Vector2D(nextX, nextY); - applyNodeDataToWindow(&nd); - - slavesLeft--; - heightLeft -= HEIGHT; - nextY += HEIGHT; - } - } else { // slaves for centered master window(s) - const float WIDTH = ((*PIGNORERESERVED ? UNRESERVED_WIDTH : WORKAREA.w) - PMASTERNODE->size.x) / 2.0; - float heightLeft = 0; - float heightLeftL = WORKAREA.h; - float heightLeftR = WORKAREA.h; - float nextX = 0; - float nextY = 0; - float nextYL = 0; - float nextYR = 0; - bool onRight = *CMFALLBACK == "right"; - int slavesLeftL = 1 + (slavesLeft - 1) / 2; - int slavesLeftR = slavesLeft - slavesLeftL; - - if (onRight) { - slavesLeftR = 1 + (slavesLeft - 1) / 2; - slavesLeftL = slavesLeft - slavesLeftR; - } - - const float slaveAverageHeightL = WORKAREA.h / slavesLeftL; - const float slaveAverageHeightR = WORKAREA.h / slavesLeftR; - float slaveAccumulatedHeightL = 0; - float slaveAccumulatedHeightR = 0; - - if (*PSMARTRESIZING) { - for (auto const& nd : m_masterNodesData) { - if (nd.workspaceID != pWorkspace->m_id || nd.isMaster) - continue; - - if (onRight) { - slaveAccumulatedHeightR += slaveAverageHeightR * nd.percSize; - } else { - slaveAccumulatedHeightL += slaveAverageHeightL * nd.percSize; - } - onRight = !onRight; - } - - onRight = *CMFALLBACK == "right"; - } - - for (auto& nd : m_masterNodesData) { - if (nd.workspaceID != pWorkspace->m_id || nd.isMaster) - continue; - - if (onRight) { - nextX = WIDTH + PMASTERNODE->size.x - (*PIGNORERESERVED ? PMONITOR->m_reservedArea.left() : 0); - nextY = nextYR; - heightLeft = heightLeftR; - slavesLeft = slavesLeftR; - } else { - nextX = 0; - nextY = nextYL; - heightLeft = heightLeftL; - slavesLeft = slavesLeftL; - } - - float HEIGHT = slavesLeft > 1 ? heightLeft / slavesLeft * nd.percSize : heightLeft; - if (HEIGHT > heightLeft * 0.9f && slavesLeft > 1) - HEIGHT = heightLeft * 0.9f; - - if (*PSMARTRESIZING) { - if (onRight) { - nd.percSize *= WORKAREA.h / slaveAccumulatedHeightR; - HEIGHT = slaveAverageHeightR * nd.percSize; - } else { - nd.percSize *= WORKAREA.h / slaveAccumulatedHeightL; - HEIGHT = slaveAverageHeightL * nd.percSize; - } - } - - nd.size = Vector2D(*PIGNORERESERVED ? (WIDTH - (onRight ? PMONITOR->m_reservedArea.right() : PMONITOR->m_reservedArea.left())) : WIDTH, HEIGHT); - nd.position = WORKAREA.pos() + Vector2D(nextX, nextY); - applyNodeDataToWindow(&nd); - - if (onRight) { - heightLeftR -= HEIGHT; - nextYR += HEIGHT; - slavesLeftR--; - } else { - heightLeftL -= HEIGHT; - nextYL += HEIGHT; - slavesLeftL--; - } - - onRight = !onRight; - } - } -} - -void CHyprMasterLayout::applyNodeDataToWindow(SMasterNodeData* pNode) { - PHLMONITOR PMONITOR = nullptr; - - const auto WS = g_pCompositor->getWorkspaceByID(pNode->workspaceID); - - if (g_pCompositor->isWorkspaceSpecial(pNode->workspaceID)) { - for (auto const& m : g_pCompositor->m_monitors) { - if (m->activeSpecialWorkspaceID() == pNode->workspaceID) { - PMONITOR = m; - break; - } - } - } else if (WS) - PMONITOR = WS->m_monitor.lock(); - - if (!PMONITOR || !WS) { - Log::logger->log(Log::ERR, "Orphaned Node {}!!", pNode); - return; - } - - // for gaps outer - const auto WORKAREA = workAreaOnWorkspace(WS); - const bool DISPLAYLEFT = STICKS(pNode->position.x, WORKAREA.x); - const bool DISPLAYRIGHT = STICKS(pNode->position.x + pNode->size.x, WORKAREA.x + WORKAREA.w); - const bool DISPLAYTOP = STICKS(pNode->position.y, WORKAREA.y); - const bool DISPLAYBOTTOM = STICKS(pNode->position.y + pNode->size.y, WORKAREA.y + WORKAREA.h); - - const auto PWINDOW = pNode->pWindow.lock(); - // get specific gaps and rules for this workspace, - // if user specified them in config - const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(PWINDOW->m_workspace); - - if (PWINDOW->isFullscreen() && !pNode->ignoreFullscreenChecks) - return; - - PWINDOW->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT); - PWINDOW->updateWindowData(); - - static auto PANIMATE = CConfigValue("misc:animate_manual_resizes"); - static auto PGAPSINDATA = CConfigValue("general:gaps_in"); - auto* PGAPSIN = sc((PGAPSINDATA.ptr())->getData()); - - auto gapsIn = WORKSPACERULE.gapsIn.value_or(*PGAPSIN); - - if (!validMapped(PWINDOW)) { - Log::logger->log(Log::ERR, "Node {} holding invalid {}!!", pNode, PWINDOW); - return; - } - - PWINDOW->m_size = pNode->size; - PWINDOW->m_position = pNode->position; - - PWINDOW->updateWindowDecos(); - - auto calcPos = PWINDOW->m_position; - auto calcSize = PWINDOW->m_size; - - const auto OFFSETTOPLEFT = Vector2D(sc(DISPLAYLEFT ? 0 : gapsIn.m_left), sc(DISPLAYTOP ? 0 : gapsIn.m_top)); - - const auto OFFSETBOTTOMRIGHT = Vector2D(sc(DISPLAYRIGHT ? 0 : gapsIn.m_right), sc(DISPLAYBOTTOM ? 0 : gapsIn.m_bottom)); - - calcPos = calcPos + OFFSETTOPLEFT; - calcSize = calcSize - OFFSETTOPLEFT - OFFSETBOTTOMRIGHT; - - const auto RESERVED = PWINDOW->getFullWindowReservedArea(); - calcPos = calcPos + RESERVED.topLeft; - calcSize = calcSize - (RESERVED.topLeft + RESERVED.bottomRight); - - Vector2D availableSpace = calcSize; - - static auto PCLAMP_TILED = CConfigValue("misc:size_limits_tiled"); - - if (*PCLAMP_TILED) { - const auto borderSize = PWINDOW->getRealBorderSize(); - Vector2D monitorAvailable = WORKAREA.size() - Vector2D{2.0 * borderSize, 2.0 * borderSize}; - - Vector2D minSize = PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}).clamp(Vector2D{0, 0}, monitorAvailable); - Vector2D maxSize = PWINDOW->isFullscreen() ? Vector2D{INFINITY, INFINITY} : - PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY}).clamp(Vector2D{0, 0}, monitorAvailable); - calcSize = calcSize.clamp(minSize, maxSize); - - calcPos += (availableSpace - calcSize) / 2.0; - - calcPos.x = std::clamp(calcPos.x, WORKAREA.x + borderSize, WORKAREA.x + WORKAREA.w - calcSize.x - borderSize); - calcPos.y = std::clamp(calcPos.y, WORKAREA.y + borderSize, WORKAREA.y + WORKAREA.h - calcSize.y - borderSize); - } - - if (PWINDOW->onSpecialWorkspace() && !PWINDOW->isFullscreen()) { - static auto PSCALEFACTOR = CConfigValue("master:special_scale_factor"); - - CBox wb = {calcPos + (calcSize - calcSize * *PSCALEFACTOR) / 2.f, calcSize * *PSCALEFACTOR}; - wb.round(); // avoid rounding mess - - *PWINDOW->m_realPosition = wb.pos(); - *PWINDOW->m_realSize = wb.size(); - } else { - CBox wb = {calcPos, calcSize}; - wb.round(); // avoid rounding mess - - *PWINDOW->m_realPosition = wb.pos(); - *PWINDOW->m_realSize = wb.size(); - } - - if (m_forceWarps && !*PANIMATE) { - g_pHyprRenderer->damageWindow(PWINDOW); - - PWINDOW->m_realPosition->warp(); - PWINDOW->m_realSize->warp(); - - g_pHyprRenderer->damageWindow(PWINDOW); - } - - PWINDOW->updateWindowDecos(); -} - -bool CHyprMasterLayout::isWindowTiled(PHLWINDOW pWindow) { - return getNodeFromWindow(pWindow) != nullptr; -} - -void CHyprMasterLayout::resizeActiveWindow(const Vector2D& pixResize, eRectCorner corner, PHLWINDOW pWindow) { - const auto PWINDOW = pWindow ? pWindow : Desktop::focusState()->window(); - - if (!validMapped(PWINDOW)) - return; - - const auto PNODE = getNodeFromWindow(PWINDOW); - - if (!PNODE) { - *PWINDOW->m_realSize = (PWINDOW->m_realSize->goal() + pixResize) - .clamp(PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}), - PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY})); - PWINDOW->updateWindowDecos(); - return; - } - - const auto PMONITOR = PWINDOW->m_monitor.lock(); - static auto SLAVECOUNTFORCENTER = CConfigValue("master:slave_count_for_center_master"); - static auto PSMARTRESIZING = CConfigValue("master:smart_resizing"); - - const auto WORKAREA = PMONITOR->logicalBoxMinusReserved(); - const bool DISPLAYBOTTOM = STICKS(PWINDOW->m_position.y + PWINDOW->m_size.y, WORKAREA.y + WORKAREA.h); - const bool DISPLAYRIGHT = STICKS(PWINDOW->m_position.x + PWINDOW->m_size.x, WORKAREA.x + WORKAREA.w); - const bool DISPLAYTOP = STICKS(PWINDOW->m_position.y, WORKAREA.y); - const bool DISPLAYLEFT = STICKS(PWINDOW->m_position.x, WORKAREA.x); - - const bool LEFT = corner == CORNER_TOPLEFT || corner == CORNER_BOTTOMLEFT; - const bool TOP = corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT; - const bool NONE = corner == CORNER_NONE; - - const auto MASTERS = getMastersOnWorkspace(PNODE->workspaceID); - const auto WINDOWS = getNodesOnWorkspace(PNODE->workspaceID); - const auto STACKWINDOWS = WINDOWS - MASTERS; - - eOrientation orientation = getDynamicOrientation(PWINDOW->m_workspace); - bool centered = orientation == ORIENTATION_CENTER && (STACKWINDOWS >= *SLAVECOUNTFORCENTER); - double delta = 0; - - if (getNodesOnWorkspace(PWINDOW->workspaceID()) == 1 && !centered) - return; - - m_forceWarps = true; - - switch (orientation) { - case ORIENTATION_LEFT: delta = pixResize.x / PMONITOR->m_size.x; break; - case ORIENTATION_RIGHT: delta = -pixResize.x / PMONITOR->m_size.x; break; - case ORIENTATION_BOTTOM: delta = -pixResize.y / PMONITOR->m_size.y; break; - case ORIENTATION_TOP: delta = pixResize.y / PMONITOR->m_size.y; break; - case ORIENTATION_CENTER: - delta = pixResize.x / PMONITOR->m_size.x; - if (STACKWINDOWS >= *SLAVECOUNTFORCENTER) { - if (!NONE || !PNODE->isMaster) - delta *= 2; - if ((!PNODE->isMaster && DISPLAYLEFT) || (PNODE->isMaster && LEFT && *PSMARTRESIZING)) - delta = -delta; - } - break; - default: UNREACHABLE(); - } - - const auto workspaceIdForResizing = PMONITOR->m_activeSpecialWorkspace ? PMONITOR->activeSpecialWorkspaceID() : PMONITOR->activeWorkspaceID(); - for (auto& n : m_masterNodesData) { - if (n.isMaster && n.workspaceID == workspaceIdForResizing) - n.percMaster = std::clamp(n.percMaster + delta, 0.05, 0.95); - } - - // check the up/down resize - const bool isStackVertical = orientation == ORIENTATION_LEFT || orientation == ORIENTATION_RIGHT || orientation == ORIENTATION_CENTER; - - const auto RESIZEDELTA = isStackVertical ? pixResize.y : pixResize.x; - - auto nodesInSameColumn = PNODE->isMaster ? MASTERS : STACKWINDOWS; - if (orientation == ORIENTATION_CENTER && !PNODE->isMaster) - nodesInSameColumn = DISPLAYRIGHT ? (nodesInSameColumn + 1) / 2 : nodesInSameColumn / 2; - - const auto SIZE = isStackVertical ? WORKAREA.h / nodesInSameColumn : WORKAREA.w / nodesInSameColumn; - - if (RESIZEDELTA != 0 && nodesInSameColumn > 1) { - if (!*PSMARTRESIZING) { - PNODE->percSize = std::clamp(PNODE->percSize + RESIZEDELTA / SIZE, 0.05, 1.95); - } else { - const auto NODEIT = std::ranges::find(m_masterNodesData, *PNODE); - const auto REVNODEIT = std::ranges::find(m_masterNodesData | std::views::reverse, *PNODE); - - const float totalSize = isStackVertical ? WORKAREA.h : WORKAREA.w; - const float minSize = totalSize / nodesInSameColumn * 0.2; - const bool resizePrevNodes = isStackVertical ? (TOP || DISPLAYBOTTOM) && !DISPLAYTOP : (LEFT || DISPLAYRIGHT) && !DISPLAYLEFT; - - int nodesLeft = 0; - float sizeLeft = 0; - int nodeCount = 0; - // check the sizes of all the nodes to be resized for later calculation - auto checkNodesLeft = [&sizeLeft, &nodesLeft, orientation, isStackVertical, &nodeCount, PNODE](auto it) { - if (it.isMaster != PNODE->isMaster || it.workspaceID != PNODE->workspaceID) - return; - nodeCount++; - if (!it.isMaster && orientation == ORIENTATION_CENTER && nodeCount % 2 == 1) - return; - sizeLeft += isStackVertical ? it.size.y : it.size.x; - nodesLeft++; - }; - float resizeDiff; - if (resizePrevNodes) { - std::for_each(std::next(REVNODEIT), m_masterNodesData.rend(), checkNodesLeft); - resizeDiff = -RESIZEDELTA; - } else { - std::for_each(std::next(NODEIT), m_masterNodesData.end(), checkNodesLeft); - resizeDiff = RESIZEDELTA; - } - - const float nodeSize = isStackVertical ? PNODE->size.y : PNODE->size.x; - const float maxSizeIncrease = sizeLeft - nodesLeft * minSize; - const float maxSizeDecrease = minSize - nodeSize; - - // leaves enough room for the other nodes - resizeDiff = std::clamp(resizeDiff, maxSizeDecrease, maxSizeIncrease); - PNODE->percSize += resizeDiff / SIZE; - - // resize the other nodes - nodeCount = 0; - auto resizeNodesLeft = [maxSizeIncrease, resizeDiff, minSize, orientation, isStackVertical, SIZE, &nodeCount, nodesLeft, PNODE](auto& it) { - if (it.isMaster != PNODE->isMaster || it.workspaceID != PNODE->workspaceID) - return; - nodeCount++; - // if center orientation, only resize when on the same side - if (!it.isMaster && orientation == ORIENTATION_CENTER && nodeCount % 2 == 1) - return; - const float size = isStackVertical ? it.size.y : it.size.x; - const float resizeDeltaForEach = maxSizeIncrease != 0 ? resizeDiff * (size - minSize) / maxSizeIncrease : resizeDiff / nodesLeft; - it.percSize -= resizeDeltaForEach / SIZE; - }; - if (resizePrevNodes) { - std::for_each(std::next(REVNODEIT), m_masterNodesData.rend(), resizeNodesLeft); - } else { - std::for_each(std::next(NODEIT), m_masterNodesData.end(), resizeNodesLeft); - } - } - } - - recalculateMonitor(PMONITOR->m_id); - - m_forceWarps = false; -} - -void CHyprMasterLayout::fullscreenRequestForWindow(PHLWINDOW pWindow, const eFullscreenMode CURRENT_EFFECTIVE_MODE, const eFullscreenMode EFFECTIVE_MODE) { - const auto PMONITOR = pWindow->m_monitor.lock(); - const auto PWORKSPACE = pWindow->m_workspace; - - // save position and size if floating - if (pWindow->m_isFloating && CURRENT_EFFECTIVE_MODE == FSMODE_NONE) { - pWindow->m_lastFloatingSize = pWindow->m_realSize->goal(); - pWindow->m_lastFloatingPosition = pWindow->m_realPosition->goal(); - pWindow->m_position = pWindow->m_realPosition->goal(); - pWindow->m_size = pWindow->m_realSize->goal(); - } - - if (EFFECTIVE_MODE == FSMODE_NONE) { - // if it got its fullscreen disabled, set back its node if it had one - const auto PNODE = getNodeFromWindow(pWindow); - if (PNODE) - applyNodeDataToWindow(PNODE); - else { - // get back its' dimensions from position and size - *pWindow->m_realPosition = pWindow->m_lastFloatingPosition; - *pWindow->m_realSize = pWindow->m_lastFloatingSize; - - pWindow->m_ruleApplicator->resetProps(Desktop::Rule::RULE_PROP_ALL, Desktop::Types::PRIORITY_LAYOUT); - pWindow->updateWindowData(); - } - } else { - // apply new pos and size being monitors' box - if (EFFECTIVE_MODE == FSMODE_FULLSCREEN) { - *pWindow->m_realPosition = PMONITOR->m_position; - *pWindow->m_realSize = PMONITOR->m_size; - } else { - // This is a massive hack. - // We make a fake "only" node and apply - // To keep consistent with the settings without C+P code - - SMasterNodeData fakeNode; - const auto WORKAREA = PMONITOR->logicalBoxMinusReserved(); - fakeNode.pWindow = pWindow; - fakeNode.position = WORKAREA.pos(); - fakeNode.size = WORKAREA.size(); - fakeNode.workspaceID = pWindow->workspaceID(); - pWindow->m_position = fakeNode.position; - pWindow->m_size = fakeNode.size; - fakeNode.ignoreFullscreenChecks = true; - - applyNodeDataToWindow(&fakeNode); - } - } - - g_pCompositor->changeWindowZOrder(pWindow, true); -} - -void CHyprMasterLayout::recalculateWindow(PHLWINDOW pWindow) { - const auto PNODE = getNodeFromWindow(pWindow); - - if (!PNODE) - return; - - recalculateMonitor(pWindow->monitorID()); -} - -SWindowRenderLayoutHints CHyprMasterLayout::requestRenderHints(PHLWINDOW pWindow) { - // window should be valid, insallah - - SWindowRenderLayoutHints hints; - - return hints; // master doesn't have any hints -} - -void CHyprMasterLayout::moveWindowTo(PHLWINDOW pWindow, const std::string& dir, bool silent) { - if (!isDirection(dir)) - return; - - const auto PWINDOW2 = g_pCompositor->getWindowInDirection(pWindow, dir[0]); - - if (!PWINDOW2) - return; - - pWindow->setAnimationsToMove(); - - if (pWindow->m_workspace != PWINDOW2->m_workspace) { - // if different monitors, send to monitor - onWindowRemovedTiling(pWindow); - pWindow->moveToWorkspace(PWINDOW2->m_workspace); - pWindow->m_monitor = PWINDOW2->m_monitor; - if (!silent) { - const auto pMonitor = pWindow->m_monitor.lock(); - Desktop::focusState()->rawMonitorFocus(pMonitor); - } - onWindowCreatedTiling(pWindow); - } else { - // if same monitor, switch windows - switchWindows(pWindow, PWINDOW2); - if (silent) - Desktop::focusState()->fullWindowFocus(PWINDOW2); - } - - pWindow->updateGroupOutputs(); - if (!pWindow->m_groupData.pNextWindow.expired()) { - PHLWINDOW next = pWindow->m_groupData.pNextWindow.lock(); - while (next != pWindow) { - next->updateToplevel(); - next = next->m_groupData.pNextWindow.lock(); - } - } -} - -void CHyprMasterLayout::switchWindows(PHLWINDOW pWindow, PHLWINDOW pWindow2) { - // windows should be valid, insallah - - const auto PNODE = getNodeFromWindow(pWindow); - const auto PNODE2 = getNodeFromWindow(pWindow2); - - if (!PNODE2 || !PNODE) - return; - - if (PNODE->workspaceID != PNODE2->workspaceID) { - std::swap(pWindow2->m_monitor, pWindow->m_monitor); - std::swap(pWindow2->m_workspace, pWindow->m_workspace); - } - - // massive hack: just swap window pointers, lol - PNODE->pWindow = pWindow2; - PNODE2->pWindow = pWindow; - - pWindow->setAnimationsToMove(); - pWindow2->setAnimationsToMove(); - - recalculateMonitor(pWindow->monitorID()); - if (PNODE2->workspaceID != PNODE->workspaceID) - recalculateMonitor(pWindow2->monitorID()); - - g_pHyprRenderer->damageWindow(pWindow); - g_pHyprRenderer->damageWindow(pWindow2); -} - -void CHyprMasterLayout::alterSplitRatio(PHLWINDOW pWindow, float ratio, bool exact) { - // window should be valid, insallah - - const auto PNODE = getNodeFromWindow(pWindow); - - if (!PNODE) - return; - - const auto PMASTER = getMasterNodeOnWorkspace(pWindow->workspaceID()); - - float newRatio = exact ? ratio : PMASTER->percMaster + ratio; - PMASTER->percMaster = std::clamp(newRatio, 0.05f, 0.95f); - - recalculateMonitor(pWindow->monitorID()); -} - -PHLWINDOW CHyprMasterLayout::getNextWindow(PHLWINDOW pWindow, bool next, bool loop) { - if (!isWindowTiled(pWindow)) - return nullptr; - - const auto PNODE = getNodeFromWindow(pWindow); - - auto nodes = m_masterNodesData; - if (!next) - std::ranges::reverse(nodes); - - const auto NODEIT = std::ranges::find(nodes, *PNODE); - - const bool ISMASTER = PNODE->isMaster; - - auto CANDIDATE = std::find_if(NODEIT, nodes.end(), [&](const auto& other) { return other != *PNODE && ISMASTER == other.isMaster && other.workspaceID == PNODE->workspaceID; }); - if (CANDIDATE == nodes.end()) - CANDIDATE = std::ranges::find_if(nodes, [&](const auto& other) { return other != *PNODE && ISMASTER != other.isMaster && other.workspaceID == PNODE->workspaceID; }); - - if (CANDIDATE != nodes.end() && !loop) { - if (CANDIDATE->isMaster && next) - return nullptr; - if (!CANDIDATE->isMaster && ISMASTER && !next) - return nullptr; - } - - return CANDIDATE == nodes.end() ? nullptr : CANDIDATE->pWindow.lock(); -} - -std::any CHyprMasterLayout::layoutMessage(SLayoutMessageHeader header, std::string message) { - auto switchToWindow = [&](PHLWINDOW PWINDOWTOCHANGETO) { - if (!validMapped(PWINDOWTOCHANGETO)) - return; - - Desktop::focusState()->fullWindowFocus(PWINDOWTOCHANGETO); - g_pCompositor->warpCursorTo(PWINDOWTOCHANGETO->middle()); - - g_pInputManager->m_forcedFocus = PWINDOWTOCHANGETO; - g_pInputManager->simulateMouseMovement(); - g_pInputManager->m_forcedFocus.reset(); - }; - - CVarList vars(message, 0, ' '); - - if (vars.size() < 1 || vars[0].empty()) { - Log::logger->log(Log::ERR, "layoutmsg called without params"); - return 0; - } - - auto command = vars[0]; - - // swapwithmaster - // first message argument can have the following values: - // * master - keep the focus at the new master - // * child - keep the focus at the new child - // * auto (default) - swap the focus (keep the focus of the previously selected window) - // * ignoremaster - ignore if master is focused - if (command == "swapwithmaster") { - const auto PWINDOW = header.pWindow; - - if (!PWINDOW) - return 0; - - if (!isWindowTiled(PWINDOW)) - return 0; - - const auto PMASTER = getMasterNodeOnWorkspace(PWINDOW->workspaceID()); - - if (!PMASTER) - return 0; - - const auto NEWCHILD = PMASTER->pWindow.lock(); - - const bool IGNORE_IF_MASTER = vars.size() >= 2 && std::ranges::any_of(vars, [](const auto& e) { return e == "ignoremaster"; }); - - if (PMASTER->pWindow.lock() != PWINDOW) { - const auto& NEWMASTER = PWINDOW; - const bool newFocusToChild = vars.size() >= 2 && vars[1] == "child"; - switchWindows(NEWMASTER, NEWCHILD); - const auto NEWFOCUS = newFocusToChild ? NEWCHILD : NEWMASTER; - switchToWindow(NEWFOCUS); - } else if (!IGNORE_IF_MASTER) { - for (auto const& n : m_masterNodesData) { - if (n.workspaceID == PMASTER->workspaceID && !n.isMaster) { - const auto NEWMASTER = n.pWindow.lock(); - switchWindows(NEWMASTER, NEWCHILD); - const bool newFocusToMaster = vars.size() >= 2 && vars[1] == "master"; - const auto NEWFOCUS = newFocusToMaster ? NEWMASTER : NEWCHILD; - switchToWindow(NEWFOCUS); - break; - } - } - } - - return 0; - } - // focusmaster - // first message argument can have the following values: - // * master - keep the focus at the new master, even if it was focused before - // * previous - focus window which was previously switched from using `focusmaster previous` command, otherwise fallback to `auto` - // * auto (default) - swap the focus with the first child, if the current focus was master, otherwise focus master - else if (command == "focusmaster") { - const auto PWINDOW = header.pWindow; - - if (!PWINDOW) - return 0; - - const auto PMASTER = getMasterNodeOnWorkspace(PWINDOW->workspaceID()); - - if (!PMASTER) - return 0; - - const auto& ARG = vars[1]; // returns empty string if out of bounds - - if (PMASTER->pWindow.lock() != PWINDOW) { - switchToWindow(PMASTER->pWindow.lock()); - // save previously focused window (only for `previous` mode) - if (ARG == "previous") - getMasterWorkspaceData(PWINDOW->workspaceID())->focusMasterPrev = PWINDOW; - return 0; - } - - const auto focusAuto = [&]() { - // focus first non-master window - for (auto const& n : m_masterNodesData) { - if (n.workspaceID == PMASTER->workspaceID && !n.isMaster) { - switchToWindow(n.pWindow.lock()); - break; - } - } - }; - - if (ARG == "master") - return 0; - // switch to previously saved window - else if (ARG == "previous") { - const auto PREVWINDOW = getMasterWorkspaceData(PWINDOW->workspaceID())->focusMasterPrev.lock(); - const bool VALID = validMapped(PREVWINDOW) && PWINDOW->workspaceID() == PREVWINDOW->workspaceID() && PWINDOW != PREVWINDOW; - VALID ? switchToWindow(PREVWINDOW) : focusAuto(); - } else - focusAuto(); - } else if (command == "cyclenext") { - const auto PWINDOW = header.pWindow; - - if (!PWINDOW) - return 0; - - const bool NOLOOP = vars.size() >= 2 && vars[1] == "noloop"; - const auto PNEXTWINDOW = getNextWindow(PWINDOW, true, !NOLOOP); - switchToWindow(PNEXTWINDOW); - } else if (command == "cycleprev") { - const auto PWINDOW = header.pWindow; - - if (!PWINDOW) - return 0; - - const bool NOLOOP = vars.size() >= 2 && vars[1] == "noloop"; - const auto PPREVWINDOW = getNextWindow(PWINDOW, false, !NOLOOP); - switchToWindow(PPREVWINDOW); - } else if (command == "swapnext") { - if (!validMapped(header.pWindow)) - return 0; - - if (header.pWindow->m_isFloating) { - g_pKeybindManager->m_dispatchers["swapnext"](""); - return 0; - } - - const bool NOLOOP = vars.size() >= 2 && vars[1] == "noloop"; - const auto PWINDOWTOSWAPWITH = getNextWindow(header.pWindow, true, !NOLOOP); - - if (PWINDOWTOSWAPWITH) { - g_pCompositor->setWindowFullscreenInternal(header.pWindow, FSMODE_NONE); - switchWindows(header.pWindow, PWINDOWTOSWAPWITH); - switchToWindow(header.pWindow); - } - } else if (command == "swapprev") { - if (!validMapped(header.pWindow)) - return 0; - - if (header.pWindow->m_isFloating) { - g_pKeybindManager->m_dispatchers["swapnext"]("prev"); - return 0; - } - - const bool NOLOOP = vars.size() >= 2 && vars[1] == "noloop"; - const auto PWINDOWTOSWAPWITH = getNextWindow(header.pWindow, false, !NOLOOP); - - if (PWINDOWTOSWAPWITH) { - g_pCompositor->setWindowFullscreenClient(header.pWindow, FSMODE_NONE); - switchWindows(header.pWindow, PWINDOWTOSWAPWITH); - switchToWindow(header.pWindow); - } - } else if (command == "addmaster") { - if (!validMapped(header.pWindow)) - return 0; - - if (header.pWindow->m_isFloating) - return 0; - - const auto PNODE = getNodeFromWindow(header.pWindow); - - const auto WINDOWS = getNodesOnWorkspace(header.pWindow->workspaceID()); - const auto MASTERS = getMastersOnWorkspace(header.pWindow->workspaceID()); - static auto SMALLSPLIT = CConfigValue("master:allow_small_split"); - - if (MASTERS + 2 > WINDOWS && *SMALLSPLIT == 0) - return 0; - - g_pCompositor->setWindowFullscreenInternal(header.pWindow, FSMODE_NONE); - - if (!PNODE || PNODE->isMaster) { - // first non-master node - for (auto& n : m_masterNodesData) { - if (n.workspaceID == header.pWindow->workspaceID() && !n.isMaster) { - n.isMaster = true; - break; - } - } - } else { - PNODE->isMaster = true; - } - - recalculateMonitor(header.pWindow->monitorID()); - - } else if (command == "removemaster") { - - if (!validMapped(header.pWindow)) - return 0; - - if (header.pWindow->m_isFloating) - return 0; - - const auto PNODE = getNodeFromWindow(header.pWindow); - - const auto WINDOWS = getNodesOnWorkspace(header.pWindow->workspaceID()); - const auto MASTERS = getMastersOnWorkspace(header.pWindow->workspaceID()); - - if (WINDOWS < 2 || MASTERS < 2) - return 0; - - g_pCompositor->setWindowFullscreenInternal(header.pWindow, FSMODE_NONE); - - if (!PNODE || !PNODE->isMaster) { - // first non-master node - for (auto& nd : m_masterNodesData | std::views::reverse) { - if (nd.workspaceID == header.pWindow->workspaceID() && nd.isMaster) { - nd.isMaster = false; - break; - } - } - } else { - PNODE->isMaster = false; - } - - recalculateMonitor(header.pWindow->monitorID()); - } else if (command == "orientationleft" || command == "orientationright" || command == "orientationtop" || command == "orientationbottom" || command == "orientationcenter") { - const auto PWINDOW = header.pWindow; - - if (!PWINDOW) - return 0; - - g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE); - - const auto PWORKSPACEDATA = getMasterWorkspaceData(PWINDOW->workspaceID()); - - if (command == "orientationleft") - PWORKSPACEDATA->orientation = ORIENTATION_LEFT; - else if (command == "orientationright") - PWORKSPACEDATA->orientation = ORIENTATION_RIGHT; - else if (command == "orientationtop") - PWORKSPACEDATA->orientation = ORIENTATION_TOP; - else if (command == "orientationbottom") - PWORKSPACEDATA->orientation = ORIENTATION_BOTTOM; - else if (command == "orientationcenter") - PWORKSPACEDATA->orientation = ORIENTATION_CENTER; - - recalculateMonitor(header.pWindow->monitorID()); - - } else if (command == "orientationnext") { - runOrientationCycle(header, nullptr, 1); - } else if (command == "orientationprev") { - runOrientationCycle(header, nullptr, -1); - } else if (command == "orientationcycle") { - runOrientationCycle(header, &vars, 1); - } else if (command == "mfact") { - g_pKeybindManager->m_dispatchers["splitratio"](vars[1] + " " + vars[2]); - } else if (command == "rollnext") { - const auto PWINDOW = header.pWindow; - const auto PNODE = getNodeFromWindow(PWINDOW); - - if (!PNODE) - return 0; - - const auto OLDMASTER = PNODE->isMaster ? PNODE : getMasterNodeOnWorkspace(PNODE->workspaceID); - if (!OLDMASTER) - return 0; - - const auto OLDMASTERIT = std::ranges::find(m_masterNodesData, *OLDMASTER); - - for (auto& nd : m_masterNodesData) { - if (nd.workspaceID == PNODE->workspaceID && !nd.isMaster) { - nd.isMaster = true; - const auto NEWMASTERIT = std::ranges::find(m_masterNodesData, nd); - m_masterNodesData.splice(OLDMASTERIT, m_masterNodesData, NEWMASTERIT); - switchToWindow(nd.pWindow.lock()); - OLDMASTER->isMaster = false; - m_masterNodesData.splice(m_masterNodesData.end(), m_masterNodesData, OLDMASTERIT); - break; - } - } - - recalculateMonitor(PWINDOW->monitorID()); - } else if (command == "rollprev") { - const auto PWINDOW = header.pWindow; - const auto PNODE = getNodeFromWindow(PWINDOW); - - if (!PNODE) - return 0; - - const auto OLDMASTER = PNODE->isMaster ? PNODE : getMasterNodeOnWorkspace(PNODE->workspaceID); - if (!OLDMASTER) - return 0; - - const auto OLDMASTERIT = std::ranges::find(m_masterNodesData, *OLDMASTER); - - for (auto& nd : m_masterNodesData | std::views::reverse) { - if (nd.workspaceID == PNODE->workspaceID && !nd.isMaster) { - nd.isMaster = true; - const auto NEWMASTERIT = std::ranges::find(m_masterNodesData, nd); - m_masterNodesData.splice(OLDMASTERIT, m_masterNodesData, NEWMASTERIT); - switchToWindow(nd.pWindow.lock()); - OLDMASTER->isMaster = false; - m_masterNodesData.splice(m_masterNodesData.begin(), m_masterNodesData, OLDMASTERIT); - break; - } - } - - recalculateMonitor(PWINDOW->monitorID()); - } - - return 0; -} - -// If vars is null, we use the default list -void CHyprMasterLayout::runOrientationCycle(SLayoutMessageHeader& header, CVarList* vars, int direction) { - std::vector cycle; - if (vars != nullptr) - buildOrientationCycleVectorFromVars(cycle, *vars); - - if (cycle.empty()) - buildOrientationCycleVectorFromEOperation(cycle); - - const auto PWINDOW = header.pWindow; - - if (!PWINDOW) - return; - - g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE); - - const auto PWORKSPACEDATA = getMasterWorkspaceData(PWINDOW->workspaceID()); - - int nextOrPrev = 0; - for (size_t i = 0; i < cycle.size(); ++i) { - if (PWORKSPACEDATA->orientation == cycle[i]) { - nextOrPrev = i + direction; - break; - } - } - - if (nextOrPrev >= sc(cycle.size())) - nextOrPrev = nextOrPrev % sc(cycle.size()); - else if (nextOrPrev < 0) - nextOrPrev = cycle.size() + (nextOrPrev % sc(cycle.size())); - - PWORKSPACEDATA->orientation = cycle.at(nextOrPrev); - recalculateMonitor(header.pWindow->monitorID()); -} - -void CHyprMasterLayout::buildOrientationCycleVectorFromEOperation(std::vector& cycle) { - for (int i = 0; i <= ORIENTATION_CENTER; ++i) { - cycle.push_back(sc(i)); - } -} - -void CHyprMasterLayout::buildOrientationCycleVectorFromVars(std::vector& cycle, CVarList& vars) { - for (size_t i = 1; i < vars.size(); ++i) { - if (vars[i] == "top") { - cycle.push_back(ORIENTATION_TOP); - } else if (vars[i] == "right") { - cycle.push_back(ORIENTATION_RIGHT); - } else if (vars[i] == "bottom") { - cycle.push_back(ORIENTATION_BOTTOM); - } else if (vars[i] == "left") { - cycle.push_back(ORIENTATION_LEFT); - } else if (vars[i] == "center") { - cycle.push_back(ORIENTATION_CENTER); - } - } -} - -eOrientation CHyprMasterLayout::getDynamicOrientation(PHLWORKSPACE pWorkspace) { - const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(pWorkspace); - std::string orientationString; - if (WORKSPACERULE.layoutopts.contains("orientation")) - orientationString = WORKSPACERULE.layoutopts.at("orientation"); - - eOrientation orientation = getMasterWorkspaceData(pWorkspace->m_id)->orientation; - // override if workspace rule is set - if (!orientationString.empty()) { - if (orientationString == "top") - orientation = ORIENTATION_TOP; - else if (orientationString == "right") - orientation = ORIENTATION_RIGHT; - else if (orientationString == "bottom") - orientation = ORIENTATION_BOTTOM; - else if (orientationString == "center") - orientation = ORIENTATION_CENTER; - else - orientation = ORIENTATION_LEFT; - } - - return orientation; -} - -void CHyprMasterLayout::replaceWindowDataWith(PHLWINDOW from, PHLWINDOW to) { - const auto PNODE = getNodeFromWindow(from); - - if (!PNODE) - return; - - PNODE->pWindow = to; - - applyNodeDataToWindow(PNODE); -} - -Vector2D CHyprMasterLayout::predictSizeForNewWindowTiled() { - static auto PNEWSTATUS = CConfigValue("master:new_status"); - - if (!Desktop::focusState()->monitor()) - return {}; - - const int NODES = getNodesOnWorkspace(Desktop::focusState()->monitor()->m_activeWorkspace->m_id); - - if (NODES <= 0) - return Desktop::focusState()->monitor()->m_size; - - const auto MASTER = getMasterNodeOnWorkspace(Desktop::focusState()->monitor()->m_activeWorkspace->m_id); - if (!MASTER) // wtf - return {}; - - if (*PNEWSTATUS == "master") { - return MASTER->size; - } else { - const auto SLAVES = NODES - getMastersOnWorkspace(Desktop::focusState()->monitor()->m_activeWorkspace->m_id); - - // TODO: make this better - return {Desktop::focusState()->monitor()->m_size.x - MASTER->size.x, Desktop::focusState()->monitor()->m_size.y / (SLAVES + 1)}; - } - - return {}; -} - -void CHyprMasterLayout::onEnable() { - for (auto const& w : g_pCompositor->m_windows) { - if (w->m_isFloating || !w->m_isMapped || w->isHidden()) - continue; - - onWindowCreatedTiling(w); - } -} - -void CHyprMasterLayout::onDisable() { - m_masterNodesData.clear(); -} diff --git a/src/layout/MasterLayout.hpp b/src/layout/MasterLayout.hpp deleted file mode 100644 index a5968916..00000000 --- a/src/layout/MasterLayout.hpp +++ /dev/null @@ -1,112 +0,0 @@ -#pragma once - -#include "IHyprLayout.hpp" -#include "../desktop/DesktopTypes.hpp" -#include "../helpers/varlist/VarList.hpp" -#include -#include -#include - -enum eFullscreenMode : int8_t; - -//orientation determines which side of the screen the master area resides -enum eOrientation : uint8_t { - ORIENTATION_LEFT = 0, - ORIENTATION_TOP, - ORIENTATION_RIGHT, - ORIENTATION_BOTTOM, - ORIENTATION_CENTER -}; - -struct SMasterNodeData { - bool isMaster = false; - float percMaster = 0.5f; - - PHLWINDOWREF pWindow; - - Vector2D position; - Vector2D size; - - float percSize = 1.f; // size multiplier for resizing children - - WORKSPACEID workspaceID = WORKSPACE_INVALID; - - bool ignoreFullscreenChecks = false; - - // - bool operator==(const SMasterNodeData& rhs) const { - return pWindow.lock() == rhs.pWindow.lock(); - } -}; - -struct SMasterWorkspaceData { - WORKSPACEID workspaceID = WORKSPACE_INVALID; - eOrientation orientation = ORIENTATION_LEFT; - // Previously focused non-master window when `focusmaster previous` command was issued - PHLWINDOWREF focusMasterPrev; - - // - bool operator==(const SMasterWorkspaceData& rhs) const { - return workspaceID == rhs.workspaceID; - } -}; - -class CHyprMasterLayout : public IHyprLayout { - public: - virtual void onWindowCreatedTiling(PHLWINDOW, eDirection direction = DIRECTION_DEFAULT); - virtual void onWindowRemovedTiling(PHLWINDOW); - virtual bool isWindowTiled(PHLWINDOW); - 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); - virtual std::any layoutMessage(SLayoutMessageHeader, std::string); - virtual SWindowRenderLayoutHints requestRenderHints(PHLWINDOW); - virtual void switchWindows(PHLWINDOW, PHLWINDOW); - virtual void moveWindowTo(PHLWINDOW, const std::string& dir, bool silent); - virtual void alterSplitRatio(PHLWINDOW, float, bool); - virtual std::string getLayoutName(); - virtual void replaceWindowDataWith(PHLWINDOW from, PHLWINDOW to); - virtual Vector2D predictSizeForNewWindowTiled(); - - virtual void onEnable(); - virtual void onDisable(); - - private: - std::list m_masterNodesData; - std::vector m_masterWorkspacesData; - - bool m_forceWarps = false; - - void buildOrientationCycleVectorFromVars(std::vector& cycle, CVarList& vars); - void buildOrientationCycleVectorFromEOperation(std::vector& cycle); - void runOrientationCycle(SLayoutMessageHeader& header, CVarList* vars, int next); - eOrientation getDynamicOrientation(PHLWORKSPACE); - int getNodesOnWorkspace(const WORKSPACEID&); - void applyNodeDataToWindow(SMasterNodeData*); - SMasterNodeData* getNodeFromWindow(PHLWINDOW); - SMasterNodeData* getMasterNodeOnWorkspace(const WORKSPACEID&); - SMasterWorkspaceData* getMasterWorkspaceData(const WORKSPACEID&); - void calculateWorkspace(PHLWORKSPACE); - PHLWINDOW getNextWindow(PHLWINDOW, bool, bool); - int getMastersOnWorkspace(const WORKSPACEID&); - - friend struct SMasterNodeData; - friend struct SMasterWorkspaceData; -}; - -template -struct std::formatter : std::formatter { - template - auto format(const SMasterNodeData* const& node, FormatContext& ctx) const { - auto out = ctx.out(); - if (!node) - return std::format_to(out, "[Node nullptr]"); - std::format_to(out, "[Node {:x}: workspace: {}, pos: {:j2}, size: {:j2}", rc(node), node->workspaceID, node->position, node->size); - if (node->isMaster) - std::format_to(out, ", master"); - if (!node->pWindow.expired()) - std::format_to(out, ", window: {:x}", node->pWindow.lock()); - return std::format_to(out, "]"); - } -}; diff --git a/src/layout/algorithm/Algorithm.cpp b/src/layout/algorithm/Algorithm.cpp new file mode 100644 index 00000000..cfb5b7e3 --- /dev/null +++ b/src/layout/algorithm/Algorithm.cpp @@ -0,0 +1,271 @@ +#include "Algorithm.hpp" + +#include "FloatingAlgorithm.hpp" +#include "TiledAlgorithm.hpp" +#include "../target/WindowTarget.hpp" +#include "../space/Space.hpp" +#include "../../desktop/view/Window.hpp" +#include "../../desktop/history/WindowHistoryTracker.hpp" +#include "../../helpers/Monitor.hpp" +#include "../../render/Renderer.hpp" + +#include "../../debug/log/Logger.hpp" + +using namespace Layout; + +SP CAlgorithm::create(UP&& tiled, UP&& floating, SP space) { + auto algo = SP(new CAlgorithm(std::move(tiled), std::move(floating), space)); + algo->m_self = algo; + algo->m_tiled->m_parent = algo; + algo->m_floating->m_parent = algo; + return algo; +} + +CAlgorithm::CAlgorithm(UP&& tiled, UP&& floating, SP space) : + m_tiled(std::move(tiled)), m_floating(std::move(floating)), m_space(space) { + ; +} + +void CAlgorithm::addTarget(SP target) { + const bool SHOULD_FLOAT = target->floating(); + + if (SHOULD_FLOAT) { + m_floatingTargets.emplace_back(target); + m_floating->newTarget(target); + } else { + m_tiledTargets.emplace_back(target); + m_tiled->newTarget(target); + } +} + +void CAlgorithm::removeTarget(SP target) { + const bool IS_FLOATING = std::ranges::contains(m_floatingTargets, target); + + if (IS_FLOATING) { + std::erase(m_floatingTargets, target); + m_floating->removeTarget(target); + return; + } + + const bool IS_TILED = std::ranges::contains(m_tiledTargets, target); + + if (IS_TILED) { + std::erase(m_tiledTargets, target); + m_tiled->removeTarget(target); + return; + } + + Log::logger->log(Log::ERR, "BUG THIS: CAlgorithm::removeTarget, but not found"); +} + +void CAlgorithm::moveTarget(SP target, std::optional focalPoint, bool reposition) { + const bool SHOULD_FLOAT = target->floating(); + + if (SHOULD_FLOAT) { + m_floatingTargets.emplace_back(target); + if (reposition) + m_floating->newTarget(target); + else + m_floating->movedTarget(target, focalPoint); + } else { + m_tiledTargets.emplace_back(target); + if (reposition) + m_tiled->newTarget(target); + else + m_tiled->movedTarget(target, focalPoint); + } +} + +SP CAlgorithm::space() const { + return m_space.lock(); +} + +void CAlgorithm::setFloating(SP target, bool floating, bool reposition) { + removeTarget(target); + + g_pHyprRenderer->damageWindow(target->window()); + + target->setFloating(floating); + + moveTarget(target, std::nullopt, reposition); + + g_pHyprRenderer->damageWindow(target->window()); +} + +size_t CAlgorithm::tiledTargets() const { + return m_tiledTargets.size(); +} + +size_t CAlgorithm::floatingTargets() const { + return m_floatingTargets.size(); +} + +void CAlgorithm::recalculate() { + m_tiled->recalculate(); + m_floating->recalculate(); + + const auto PWORKSPACE = m_space->workspace(); + const auto PMONITOR = PWORKSPACE->m_monitor; + + if (PWORKSPACE->m_hasFullscreenWindow && PMONITOR) { + // massive hack from the fullscreen func + const auto PFULLWINDOW = PWORKSPACE->getFullscreenWindow(); + + if (PFULLWINDOW) { + if (PWORKSPACE->m_fullscreenMode == FSMODE_FULLSCREEN) { + *PFULLWINDOW->m_realPosition = PMONITOR->m_position; + *PFULLWINDOW->m_realSize = PMONITOR->m_size; + } else if (PWORKSPACE->m_fullscreenMode == FSMODE_MAXIMIZED) + PFULLWINDOW->layoutTarget()->setPositionGlobal(m_space->workArea()); + } + + return; + } +} + +void CAlgorithm::recenter(SP t) { + if (t->floating()) + m_floating->recenter(t); +} + +std::expected CAlgorithm::layoutMsg(const std::string_view& sv) { + if (const auto ret = m_floating->layoutMsg(sv); !ret) + return ret; + return m_tiled->layoutMsg(sv); +} + +std::optional CAlgorithm::predictSizeForNewTiledTarget() { + return m_tiled->predictSizeForNewTarget(); +} + +void CAlgorithm::resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner) { + if (target->floating()) + m_floating->resizeTarget(Δ, target, corner); + else + m_tiled->resizeTarget(Δ, target, corner); +} + +void CAlgorithm::moveTarget(const Vector2D& Δ, SP target) { + if (target->floating()) + m_floating->moveTarget(Δ, target); +} + +void CAlgorithm::swapTargets(SP a, SP b) { + auto swapFirst = [&a, &b](std::vector>& targets) -> bool { + auto ia = std::ranges::find(targets, a); + auto ib = std::ranges::find(targets, b); + + if (ia != std::ranges::end(targets) && ib != std::ranges::end(targets)) { + std::iter_swap(ia, ib); + return true; + } else if (ia != std::ranges::end(targets)) + *ia = b; + else if (ib != std::ranges::end(targets)) + *ib = a; + + return false; + }; + + if (!swapFirst(m_tiledTargets)) + swapFirst(m_floatingTargets); + + const WP algA = a->floating() ? WP(m_floating) : WP(m_tiled); + const WP algB = b->floating() ? WP(m_floating) : WP(m_tiled); + + algA->swapTargets(a, b); + if (algA != algB) + algB->swapTargets(b, a); +} + +void CAlgorithm::moveTargetInDirection(SP t, Math::eDirection dir, bool silent) { + if (t->floating()) + m_floating->moveTargetInDirection(t, dir, silent); + else + m_tiled->moveTargetInDirection(t, dir, silent); +} + +void CAlgorithm::updateFloatingAlgo(UP&& algo) { + algo->m_parent = m_self; + + for (const auto& t : m_floatingTargets) { + const auto TARGET = t.lock(); + if (!TARGET) + continue; + + // Unhide windows when switching layouts to prevent them from being permanently lost + const auto WINDOW = TARGET->window(); + if (WINDOW) + WINDOW->setHidden(false); + + m_floating->removeTarget(TARGET); + algo->newTarget(TARGET); + } + + m_floating = std::move(algo); +} + +void CAlgorithm::updateTiledAlgo(UP&& algo) { + algo->m_parent = m_self; + + for (const auto& t : m_tiledTargets) { + const auto TARGET = t.lock(); + if (!TARGET) + continue; + + // Unhide windows when switching layouts to prevent them from being permanently lost + // This is a safeguard for layouts (including third-party plugins) that use setHidden + const auto WINDOW = TARGET->window(); + if (WINDOW) + WINDOW->setHidden(false); + + m_tiled->removeTarget(TARGET); + algo->newTarget(TARGET); + } + + m_tiled = std::move(algo); +} + +const UP& CAlgorithm::tiledAlgo() const { + return m_tiled; +} + +const UP& CAlgorithm::floatingAlgo() const { + return m_floating; +} + +SP CAlgorithm::getNextCandidate(SP old) { + if (old->floating()) { + // use window history to determine best target + for (const auto& w : Desktop::History::windowTracker()->fullHistory() | std::views::reverse) { + if (!w->m_workspace || w->m_workspace->m_space != m_space || !w->layoutTarget() || !w->layoutTarget()->space()) + continue; + + return w->layoutTarget(); + } + + // no history, fall back + } else { + // ask the layout + const auto CANDIDATE = m_tiled->getNextCandidate(old); + if (CANDIDATE) + return CANDIDATE; + + // no candidate, fall back + } + + // fallback: try to focus anything + if (!m_tiledTargets.empty()) + return m_tiledTargets.back().lock(); + if (!m_floatingTargets.empty()) + return m_floatingTargets.back().lock(); + + // god damn it, maybe empty? + return nullptr; +} + +void CAlgorithm::setTargetGeom(const CBox& box, SP target) { + if (!target->floating() || !std::ranges::contains(m_floatingTargets, target)) + return; + + m_floating->setTargetGeom(box, target); +} diff --git a/src/layout/algorithm/Algorithm.hpp b/src/layout/algorithm/Algorithm.hpp new file mode 100644 index 00000000..7df6c5c1 --- /dev/null +++ b/src/layout/algorithm/Algorithm.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include "../../helpers/math/Math.hpp" +#include "../../helpers/math/Direction.hpp" +#include "../../helpers/memory/Memory.hpp" + +#include "../LayoutManager.hpp" + +#include +#include + +namespace Layout { + class ITarget; + class IFloatingAlgorithm; + class ITiledAlgorithm; + class CSpace; + + class CAlgorithm { + public: + static SP create(UP&& tiled, UP&& floating, SP space); + ~CAlgorithm() = default; + + void addTarget(SP target); + void moveTarget(SP target, std::optional focalPoint = std::nullopt, bool reposition = false); + void removeTarget(SP target); + + void swapTargets(SP a, SP b); + void moveTargetInDirection(SP t, Math::eDirection dir, bool silent); + + SP getNextCandidate(SP old); + + void setFloating(SP target, bool floating, bool reposition = false); + + std::expected layoutMsg(const std::string_view& sv); + std::optional predictSizeForNewTiledTarget(); + + void recalculate(); + void recenter(SP t); + + void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); + void moveTarget(const Vector2D& Δ, SP target); + + void setTargetGeom(const CBox& box, SP target); // only for float + + void updateFloatingAlgo(UP&& algo); + void updateTiledAlgo(UP&& algo); + + const UP& tiledAlgo() const; + const UP& floatingAlgo() const; + + SP space() const; + + size_t tiledTargets() const; + size_t floatingTargets() const; + + private: + CAlgorithm(UP&& tiled, UP&& floating, SP space); + + UP m_tiled; + UP m_floating; + WP m_space; + WP m_self; + + std::vector> m_tiledTargets, m_floatingTargets; + }; +} \ No newline at end of file diff --git a/src/layout/algorithm/FloatingAlgorithm.cpp b/src/layout/algorithm/FloatingAlgorithm.cpp new file mode 100644 index 00000000..058887bf --- /dev/null +++ b/src/layout/algorithm/FloatingAlgorithm.cpp @@ -0,0 +1,18 @@ +#include "FloatingAlgorithm.hpp" +#include "Algorithm.hpp" +#include "../space/Space.hpp" + +using namespace Layout; + +void IFloatingAlgorithm::recalculate() { + ; +} + +void IFloatingAlgorithm::recenter(SP t) { + const auto LAST = t->lastFloatingSize(); + + if (LAST.x <= 5 || LAST.y <= 5) + return; + + t->setPositionGlobal({m_parent->space()->workArea().middle() - LAST / 2.F, LAST}); +} diff --git a/src/layout/algorithm/FloatingAlgorithm.hpp b/src/layout/algorithm/FloatingAlgorithm.hpp new file mode 100644 index 00000000..40e53034 --- /dev/null +++ b/src/layout/algorithm/FloatingAlgorithm.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include "../../helpers/math/Math.hpp" +#include "../../helpers/memory/Memory.hpp" + +#include "ModeAlgorithm.hpp" + +namespace Layout { + + class ITarget; + class CAlgorithm; + + class IFloatingAlgorithm : public IModeAlgorithm { + public: + virtual ~IFloatingAlgorithm() = default; + + // a target is being moved by a delta + virtual void moveTarget(const Vector2D& Δ, SP target) = 0; + + // a target is moved to a pos x size + virtual void setTargetGeom(const CBox& geom, SP target) = 0; + + virtual void recenter(SP t); + + virtual void recalculate(); + + protected: + IFloatingAlgorithm() = default; + + WP m_parent; + + friend class Layout::CAlgorithm; + }; +} \ No newline at end of file diff --git a/src/layout/algorithm/ModeAlgorithm.cpp b/src/layout/algorithm/ModeAlgorithm.cpp new file mode 100644 index 00000000..dea5bb17 --- /dev/null +++ b/src/layout/algorithm/ModeAlgorithm.cpp @@ -0,0 +1,33 @@ +#include "ModeAlgorithm.hpp" + +#include "../space/Space.hpp" +#include "Algorithm.hpp" +#include "../../helpers/Monitor.hpp" +#include "../../desktop/view/Window.hpp" + +using namespace Layout; + +std::expected IModeAlgorithm::layoutMsg(const std::string_view& sv) { + return {}; +} + +std::optional IModeAlgorithm::predictSizeForNewTarget() { + return std::nullopt; +} + +std::optional IModeAlgorithm::focalPointForDir(SP t, Math::eDirection dir) { + Vector2D focalPoint; + + const auto WINDOWIDEALBB = + t->fullscreenMode() != FSMODE_NONE ? m_parent->space()->workspace()->m_monitor->logicalBox() : t->window()->getWindowIdealBoundingBoxIgnoreReserved(); + + switch (dir) { + case Math::DIRECTION_UP: focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x / 2.0, -1.0}; break; + case Math::DIRECTION_DOWN: focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x / 2.0, WINDOWIDEALBB.size().y + 1.0}; break; + case Math::DIRECTION_LEFT: focalPoint = WINDOWIDEALBB.pos() + Vector2D{-1.0, WINDOWIDEALBB.size().y / 2.0}; break; + case Math::DIRECTION_RIGHT: focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x + 1.0, WINDOWIDEALBB.size().y / 2.0}; break; + default: return std::nullopt; + } + + return focalPoint; +} diff --git a/src/layout/algorithm/ModeAlgorithm.hpp b/src/layout/algorithm/ModeAlgorithm.hpp new file mode 100644 index 00000000..0fedc3da --- /dev/null +++ b/src/layout/algorithm/ModeAlgorithm.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include "../../helpers/math/Math.hpp" +#include "../../helpers/math/Direction.hpp" +#include "../../helpers/memory/Memory.hpp" + +#include "../LayoutManager.hpp" + +#include + +namespace Layout { + + class ITarget; + class CAlgorithm; + + class IModeAlgorithm { + public: + virtual ~IModeAlgorithm() = default; + + // a completely new target + virtual void newTarget(SP target) = 0; + + // a target moved into the algorithm (from another) + virtual void movedTarget(SP target, std::optional focalPoint = std::nullopt) = 0; + + // a target removed + virtual void removeTarget(SP target) = 0; + + // a target is being resized by a delta. Corner none likely means not interactive + virtual void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE) = 0; + + // recalculate layout + virtual void recalculate() = 0; + + // swap targets + virtual void swapTargets(SP a, SP b) = 0; + + // move a target in a given direction + virtual void moveTargetInDirection(SP t, Math::eDirection dir, bool silent) = 0; + + // optional: handle layout messages + virtual std::expected layoutMsg(const std::string_view& sv); + + // optional: predict new window's size + virtual std::optional predictSizeForNewTarget(); + + // Impl'd here: focal point for dir + virtual std::optional focalPointForDir(SP t, Math::eDirection dir); + + protected: + IModeAlgorithm() = default; + + WP m_parent; + + friend class Layout::CAlgorithm; + }; +} \ No newline at end of file diff --git a/src/layout/algorithm/TiledAlgorithm.hpp b/src/layout/algorithm/TiledAlgorithm.hpp new file mode 100644 index 00000000..99d1bd99 --- /dev/null +++ b/src/layout/algorithm/TiledAlgorithm.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "../../helpers/math/Math.hpp" +#include "../../helpers/memory/Memory.hpp" + +#include "ModeAlgorithm.hpp" + +namespace Layout { + + class ITarget; + class CAlgorithm; + + class ITiledAlgorithm : public IModeAlgorithm { + public: + virtual ~ITiledAlgorithm() = default; + + virtual SP getNextCandidate(SP old) = 0; + + protected: + ITiledAlgorithm() = default; + + WP m_parent; + + friend class Layout::CAlgorithm; + }; +} \ No newline at end of file diff --git a/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.cpp b/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.cpp new file mode 100644 index 00000000..0d069e4f --- /dev/null +++ b/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.cpp @@ -0,0 +1,252 @@ +#include "DefaultFloatingAlgorithm.hpp" + +#include "../../Algorithm.hpp" + +#include "../../../target/WindowTarget.hpp" +#include "../../../space/Space.hpp" + +#include "../../../../Compositor.hpp" +#include "../../../../helpers/Monitor.hpp" + +using namespace Layout; +using namespace Layout::Floating; + +constexpr const Vector2D DEFAULT_SIZE = {640, 400}; + +// +void CDefaultFloatingAlgorithm::newTarget(SP target) { + const auto WORK_AREA = m_parent->space()->workArea(true); + const auto DESIRED_GEOM = target->desiredGeometry(); + const auto MONITOR_POS = m_parent->space()->workspace()->m_monitor->logicalBox().pos(); + + CBox windowGeometry; + + if (!DESIRED_GEOM) { + switch (DESIRED_GEOM.error()) { + case GEOMETRY_INVALID_DESIRED: { + // if the desired is invalid, we hide the window. + if (target->type() == TARGET_TYPE_WINDOW) + dynamicPointerCast(target)->window()->setHidden(true); + return; + } + case GEOMETRY_NO_DESIRED: { + // add a default geom + windowGeometry = CBox{WORK_AREA.middle() - DEFAULT_SIZE / 2.F, DEFAULT_SIZE}; + break; + } + } + } else { + if (DESIRED_GEOM->pos) + windowGeometry = CBox{DESIRED_GEOM->pos.value(), DESIRED_GEOM->size}; + else + windowGeometry = CBox{WORK_AREA.middle() - DESIRED_GEOM->size / 2.F, DESIRED_GEOM->size}; + } + + bool posOverridden = false; + + if (target->window() && target->window()->m_firstMap) { + const auto WINDOW = target->window(); + + // set this here so that expressions can use it. This could be wrong of course. + WINDOW->m_realSize->setValueAndWarp(DESIRED_GEOM ? DESIRED_GEOM->size : DEFAULT_SIZE); + + if (!WINDOW->m_ruleApplicator->static_.size.empty()) { + const auto COMPUTED = WINDOW->calculateExpression(WINDOW->m_ruleApplicator->static_.size); + if (!COMPUTED) + Log::logger->log(Log::ERR, "failed to parse {} as an expression", WINDOW->m_ruleApplicator->static_.size); + else { + windowGeometry.w = COMPUTED->x; + windowGeometry.h = COMPUTED->y; + + // update for pos to work with size. + WINDOW->m_realPosition->setValueAndWarp(*COMPUTED); + } + } + + if (!WINDOW->m_ruleApplicator->static_.position.empty()) { + const auto COMPUTED = WINDOW->calculateExpression(WINDOW->m_ruleApplicator->static_.position); + if (!COMPUTED) + Log::logger->log(Log::ERR, "failed to parse {} as an expression", WINDOW->m_ruleApplicator->static_.position); + else { + windowGeometry.x = COMPUTED->x + MONITOR_POS.x; + windowGeometry.y = COMPUTED->y + MONITOR_POS.y; + posOverridden = true; + } + } + + if (WINDOW->m_ruleApplicator->static_.center.value_or(false)) { + const auto POS = WORK_AREA.middle() - windowGeometry.size() / 2.f; + windowGeometry.x = POS.x; + windowGeometry.y = POS.y; + posOverridden = true; + } + } else if (target->lastFloatingSize().x > 5 && target->lastFloatingSize().y > 5) { + windowGeometry.w = target->lastFloatingSize().x; + windowGeometry.h = target->lastFloatingSize().y; + } + + if (!posOverridden && (!DESIRED_GEOM || !DESIRED_GEOM->pos)) + windowGeometry = CBox{WORK_AREA.middle() - windowGeometry.size() / 2.F, windowGeometry.size()}; + + if (posOverridden // pos is overridden by a rule + || (DESIRED_GEOM && DESIRED_GEOM->pos && target->window() && target->window()->m_isX11) // X11 window with a geom + || WORK_AREA.containsPoint(windowGeometry.middle())) // geometry within work area + target->setPositionGlobal(windowGeometry); + else { + const auto POS = WORK_AREA.middle() - windowGeometry.size() / 2.f; + windowGeometry.x = POS.x; + windowGeometry.y = POS.y; + + target->setPositionGlobal(windowGeometry); + } + + // TODO: not very OOP, is it? + if (const auto WTARGET = dynamicPointerCast(target); WTARGET) { + const auto PWINDOW = WTARGET->window(); + + if (PWINDOW->m_X11DoesntWantBorders || (PWINDOW->m_isX11 && PWINDOW->isX11OverrideRedirect())) { + PWINDOW->m_realPosition->warp(); + PWINDOW->m_realSize->warp(); + } + + if (!PWINDOW->isX11OverrideRedirect()) + g_pCompositor->changeWindowZOrder(PWINDOW, true); + else { + PWINDOW->m_pendingReportedSize = PWINDOW->m_realSize->goal(); + PWINDOW->m_reportedSize = PWINDOW->m_pendingReportedSize; + } + } + + updateTarget(target); +} + +void CDefaultFloatingAlgorithm::movedTarget(SP target, std::optional focalPoint) { + auto LAST_SIZE = target->lastFloatingSize(); + const auto CURRENT_SIZE = target->position().size(); + + // ignore positioning a dragged target + if (g_layoutManager->dragController()->target() == target) + return; + + if (LAST_SIZE.x < 5 || LAST_SIZE.y < 5) { + const auto DESIRED = target->desiredGeometry(); + LAST_SIZE = DESIRED ? DESIRED->size : DEFAULT_SIZE; + } + + if (target->wasTiling()) { + // Avoid floating toggles that don't change size, they aren't easily visible to the user + if (std::abs(LAST_SIZE.x - CURRENT_SIZE.x) < 5 && std::abs(LAST_SIZE.y - CURRENT_SIZE.y) < 5) + LAST_SIZE += Vector2D{10, 10}; + + // calculate new position + const auto OLD_CENTER = target->position().middle(); + + // put around the current center, fit in workArea + target->setPositionGlobal(fitBoxInWorkArea(CBox{OLD_CENTER - LAST_SIZE / 2.F, LAST_SIZE}, target)); + + } else { + // calculate new position + const auto THIS_MON_POS = m_parent->space()->workspace()->m_monitor->m_position; + const auto OLD_POS = target->position().pos(); + const auto MON_FROM_OLD = g_pCompositor->getMonitorFromVector(OLD_POS); + const auto NEW_POS = MON_FROM_OLD ? OLD_POS - MON_FROM_OLD->m_position + THIS_MON_POS : OLD_POS; + + // put around the current center, fit in workArea + target->setPositionGlobal(fitBoxInWorkArea(CBox{NEW_POS, LAST_SIZE}, target)); + } + + updateTarget(target); +} + +CBox CDefaultFloatingAlgorithm::fitBoxInWorkArea(const CBox& box, SP t) { + const auto WORK_AREA = m_parent->space()->workArea(true); + const auto EXTENTS = t->window() ? t->window()->getWindowExtentsUnified(Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS) : SBoxExtents{}; + CBox targetBox = box.copy().addExtents(EXTENTS); + + targetBox.x = std::max(targetBox.x, WORK_AREA.x); + targetBox.y = std::max(targetBox.y, WORK_AREA.y); + + if (targetBox.x + targetBox.w > WORK_AREA.x + WORK_AREA.w) + targetBox.x = WORK_AREA.x + WORK_AREA.w - targetBox.w; + + if (targetBox.y + targetBox.h > WORK_AREA.y + WORK_AREA.h) + targetBox.y = WORK_AREA.y + WORK_AREA.h - targetBox.h; + + return targetBox.addExtents(SBoxExtents{.topLeft = -EXTENTS.topLeft, .bottomRight = -EXTENTS.bottomRight}); +} + +void CDefaultFloatingAlgorithm::removeTarget(SP target) { + target->rememberFloatingSize(target->position().size()); + m_datas.erase(target); +} + +void CDefaultFloatingAlgorithm::resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner) { + auto pos = target->position(); + pos.w += Δ.x; + pos.h += Δ.y; + pos.translate(-Δ / 2.F); + target->setPositionGlobal(pos); + + if (g_layoutManager->dragController()->target() == target) + target->warpPositionSize(); + + updateTarget(target); +} + +void CDefaultFloatingAlgorithm::moveTarget(const Vector2D& Δ, SP target) { + auto pos = target->position(); + pos.translate(Δ); + target->setPositionGlobal(pos); + + if (g_layoutManager->dragController()->target() == target) + target->warpPositionSize(); + + updateTarget(target); +} + +void CDefaultFloatingAlgorithm::swapTargets(SP a, SP b) { + auto posABackup = a->position(); + a->setPositionGlobal(b->position()); + b->setPositionGlobal(posABackup); + + updateTarget(a); + updateTarget(b); +} + +void CDefaultFloatingAlgorithm::moveTargetInDirection(SP t, Math::eDirection dir, bool silent) { + auto pos = t->position(); + auto work = m_parent->space()->workArea(true); + + const auto EXTENTS = t->window() ? t->window()->getWindowExtentsUnified(Desktop::View::RESERVED_EXTENTS) : SBoxExtents{}; + + switch (dir) { + case Math::DIRECTION_LEFT: pos.x = work.x + EXTENTS.topLeft.x; break; + case Math::DIRECTION_RIGHT: pos.x = work.x + work.w - pos.w - EXTENTS.bottomRight.x; break; + case Math::DIRECTION_UP: pos.y = work.y + EXTENTS.topLeft.y; break; + case Math::DIRECTION_DOWN: pos.y = work.y + work.h - pos.h - EXTENTS.bottomRight.y; break; + default: Log::logger->log(Log::ERR, "Invalid direction in CDefaultFloatingAlgorithm::moveTargetInDirection"); break; + } + + t->setPositionGlobal(pos); + + updateTarget(t); +} + +void CDefaultFloatingAlgorithm::recenter(SP t) { + if (!m_datas.contains(t)) { + IFloatingAlgorithm::recenter(t); + return; + } + + t->setPositionGlobal(m_datas.at(t).lastBox); +} + +void CDefaultFloatingAlgorithm::setTargetGeom(const CBox& geom, SP target) { + target->setPositionGlobal(geom); + + updateTarget(target); +} + +void CDefaultFloatingAlgorithm::updateTarget(SP t) { + m_datas[t] = {.lastBox = t->position()}; +} diff --git a/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.hpp b/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.hpp new file mode 100644 index 00000000..1e87fac1 --- /dev/null +++ b/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.hpp @@ -0,0 +1,40 @@ +#include "../../FloatingAlgorithm.hpp" + +#include + +namespace Layout { + class CAlgorithm; +} + +namespace Layout::Floating { + class CDefaultFloatingAlgorithm : public IFloatingAlgorithm { + public: + CDefaultFloatingAlgorithm() = default; + virtual ~CDefaultFloatingAlgorithm() = default; + + virtual void newTarget(SP target); + virtual void movedTarget(SP target, std::optional focalPoint = std::nullopt); + virtual void removeTarget(SP target); + + virtual void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); + virtual void moveTarget(const Vector2D& Δ, SP target); + + virtual void setTargetGeom(const CBox& geom, SP target); + + virtual void swapTargets(SP a, SP b); + virtual void moveTargetInDirection(SP t, Math::eDirection dir, bool silent); + + virtual void recenter(SP t); + + private: + CBox fitBoxInWorkArea(const CBox& box, SP t); + + void updateTarget(SP); + + struct SWindowData { + CBox lastBox; + }; + + std::map, SWindowData> m_datas; + }; +}; \ No newline at end of file diff --git a/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp b/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp new file mode 100644 index 00000000..7ef36753 --- /dev/null +++ b/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp @@ -0,0 +1,845 @@ +#include "DwindleAlgorithm.hpp" + +#include "../../Algorithm.hpp" +#include "../../../space/Space.hpp" +#include "../../../target/WindowTarget.hpp" +#include "../../../LayoutManager.hpp" + +#include "../../../../config/ConfigValue.hpp" +#include "../../../../desktop/state/FocusState.hpp" +#include "../../../../helpers/Monitor.hpp" +#include "../../../../Compositor.hpp" + +#include + +using namespace Layout; +using namespace Layout::Tiled; + +struct Layout::Tiled::SDwindleNodeData { + WP pParent; + bool isNode = false; + WP pTarget; + std::array, 2> children = {}; + WP self; + bool splitTop = false; // for preserve_split + CBox box = {0}; + float splitRatio = 1.f; + bool valid = true; + bool ignoreFullscreenChecks = false; + + // For list lookup + bool operator==(const SDwindleNodeData& rhs) const { + return pTarget.lock() == rhs.pTarget.lock() && box == rhs.box && pParent == rhs.pParent && children[0] == rhs.children[0] && children[1] == rhs.children[1]; + } + + void recalcSizePosRecursive(bool force = false, bool horizontalOverride = false, bool verticalOverride = false) { + if (children[0]) { + static auto PSMARTSPLIT = CConfigValue("dwindle:smart_split"); + static auto PPRESERVESPLIT = CConfigValue("dwindle:preserve_split"); + static auto PFLMULT = CConfigValue("dwindle:split_width_multiplier"); + + if (*PPRESERVESPLIT == 0 && *PSMARTSPLIT == 0) + splitTop = box.h * *PFLMULT > box.w; + + if (verticalOverride) + splitTop = true; + else if (horizontalOverride) + splitTop = false; + + const auto SPLITSIDE = !splitTop; + + if (SPLITSIDE) { + // split left/right + const float FIRSTSIZE = box.w / 2.0 * splitRatio; + children[0]->box = CBox{box.x, box.y, FIRSTSIZE, box.h}.noNegativeSize(); + children[1]->box = CBox{box.x + FIRSTSIZE, box.y, box.w - FIRSTSIZE, box.h}.noNegativeSize(); + } else { + // split top/bottom + const float FIRSTSIZE = box.h / 2.0 * splitRatio; + children[0]->box = CBox{box.x, box.y, box.w, FIRSTSIZE}.noNegativeSize(); + children[1]->box = CBox{box.x, box.y + FIRSTSIZE, box.w, box.h - FIRSTSIZE}.noNegativeSize(); + } + + children[0]->recalcSizePosRecursive(force); + children[1]->recalcSizePosRecursive(force); + } else + pTarget->setPositionGlobal(box); + } +}; + +void CDwindleAlgorithm::newTarget(SP target) { + addTarget(target); +} + +void CDwindleAlgorithm::addTarget(SP target, bool newTarget) { + const auto WORK_AREA = m_parent->space()->workArea(); + + const auto PNODE = m_dwindleNodesData.emplace_back(makeShared()); + PNODE->self = PNODE; + + const auto PMONITOR = m_parent->space()->workspace()->m_monitor; + const auto PWORKSPACE = m_parent->space()->workspace(); + + static auto PUSEACTIVE = CConfigValue("dwindle:use_active_for_splits"); + static auto PDEFAULTSPLIT = CConfigValue("dwindle:default_split_ratio"); + + // Populate the node with our window's data + PNODE->pTarget = target; + PNODE->isNode = false; + + SP OPENINGON; + + const auto MOUSECOORDS = m_overrideFocalPoint.value_or(g_pInputManager->getMouseCoordsInternal()); + const auto ACTIVE_MON = Desktop::focusState()->monitor(); + + if ((PWORKSPACE == ACTIVE_MON->m_activeWorkspace || (PWORKSPACE->m_isSpecialWorkspace && PMONITOR->m_activeSpecialWorkspace)) && !*PUSEACTIVE) { + OPENINGON = getNodeFromWindow( + g_pCompositor->vectorToWindowUnified(MOUSECOORDS, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::SKIP_FULLSCREEN_PRIORITY)); + + if (!OPENINGON && g_pCompositor->isPointOnReservedArea(MOUSECOORDS, ACTIVE_MON)) + OPENINGON = getClosestNode(MOUSECOORDS); + + } else if (*PUSEACTIVE || m_overrideFocalPoint) { + const auto ACTIVE_WINDOW = Desktop::focusState()->window(); + + if (m_overrideFocalPoint) + OPENINGON = getClosestNode(*m_overrideFocalPoint); + else if (!m_overrideFocalPoint && ACTIVE_WINDOW && !ACTIVE_WINDOW->m_isFloating && ACTIVE_WINDOW != target->window() && ACTIVE_WINDOW->m_workspace == PWORKSPACE && + ACTIVE_WINDOW->m_isMapped) + OPENINGON = getNodeFromWindow(ACTIVE_WINDOW); + + if (!OPENINGON) + OPENINGON = getClosestNode(MOUSECOORDS, target); + } else + OPENINGON = getFirstNode(); + + // first, check if OPENINGON isn't too big. + const auto PREDSIZEMAX = OPENINGON ? Vector2D(OPENINGON->box.w, OPENINGON->box.h) : PMONITOR->m_size; + if (const auto MAXSIZE = target->maxSize().value_or(Math::VECTOR2D_MAX); MAXSIZE.x < PREDSIZEMAX.x || MAXSIZE.y < PREDSIZEMAX.y) { + // we can't continue. make it floating. + std::erase(m_dwindleNodesData, PNODE); + m_parent->setFloating(target, true, true); + return; + } + + // last fail-safe to avoid duplicate fullscreens + if ((!OPENINGON || OPENINGON->pTarget.lock() == target) && getNodes() > 1) { + for (auto& node : m_dwindleNodesData) { + if (node->pTarget.lock() && node->pTarget.lock() != target) { + OPENINGON = node; + break; + } + } + } + + // if it's the first, it's easy. Make it fullscreen. + if (!OPENINGON || OPENINGON->pTarget.lock() == target) { + PNODE->box = WORK_AREA; + PNODE->pTarget->setPositionGlobal(PNODE->box); + return; + } + + // get the node under our cursor + + const auto NEWPARENT = m_dwindleNodesData.emplace_back(makeShared()); + + // make the parent have the OPENINGON's stats + NEWPARENT->box = OPENINGON->box; + NEWPARENT->pParent = OPENINGON->pParent; + NEWPARENT->isNode = true; // it is a node + NEWPARENT->splitRatio = std::clamp(*PDEFAULTSPLIT, 0.1F, 1.9F); + + static auto PWIDTHMULTIPLIER = CConfigValue("dwindle:split_width_multiplier"); + + // if cursor over first child, make it first, etc + const auto SIDEBYSIDE = NEWPARENT->box.w > NEWPARENT->box.h * *PWIDTHMULTIPLIER; + NEWPARENT->splitTop = !SIDEBYSIDE; + + static auto PFORCESPLIT = CConfigValue("dwindle:force_split"); + static auto PERMANENTDIRECTIONOVERRIDE = CConfigValue("dwindle:permanent_direction_override"); + static auto PSMARTSPLIT = CConfigValue("dwindle:smart_split"); + static auto PSPLITBIAS = CConfigValue("dwindle:split_bias"); + + bool horizontalOverride = false; + bool verticalOverride = false; + + // let user select position -> top, right, bottom, left + if (m_overrideDirection != Math::DIRECTION_DEFAULT) { + + // this is horizontal + if (m_overrideDirection % 2 == 0) + verticalOverride = true; + else + horizontalOverride = true; + + // 0 -> top and left | 1,2 -> right and bottom + if (m_overrideDirection % 3 == 0) { + NEWPARENT->children[1] = OPENINGON; + NEWPARENT->children[0] = PNODE; + } else { + NEWPARENT->children[0] = OPENINGON; + NEWPARENT->children[1] = PNODE; + } + + // whether or not the override persists after opening one window + if (*PERMANENTDIRECTIONOVERRIDE == 0) + m_overrideDirection = Math::DIRECTION_DEFAULT; + } else if (*PSMARTSPLIT == 1 || m_overrideFocalPoint) { + const auto PARENT_CENTER = NEWPARENT->box.pos() + NEWPARENT->box.size() / 2; + const auto PARENT_PROPORTIONS = NEWPARENT->box.h / NEWPARENT->box.w; + const auto DELTA = MOUSECOORDS - PARENT_CENTER; + const auto DELTA_SLOPE = DELTA.y / DELTA.x; + + if (abs(DELTA_SLOPE) < PARENT_PROPORTIONS) { + if (DELTA.x > 0) { + // right + NEWPARENT->splitTop = false; + NEWPARENT->children[0] = OPENINGON; + NEWPARENT->children[1] = PNODE; + } else { + // left + NEWPARENT->splitTop = false; + NEWPARENT->children[0] = PNODE; + NEWPARENT->children[1] = OPENINGON; + } + } else { + if (DELTA.y > 0) { + // bottom + NEWPARENT->splitTop = true; + NEWPARENT->children[0] = OPENINGON; + NEWPARENT->children[1] = PNODE; + } else { + // top + NEWPARENT->splitTop = true; + NEWPARENT->children[0] = PNODE; + NEWPARENT->children[1] = OPENINGON; + } + } + } else if (*PFORCESPLIT == 0 || !newTarget) { + if ((SIDEBYSIDE && MOUSECOORDS.x < NEWPARENT->box.x + (NEWPARENT->box.w / 2.F)) || (!SIDEBYSIDE && MOUSECOORDS.y < NEWPARENT->box.y + (NEWPARENT->box.h / 2.F))) { + // we are hovering over the first node, make PNODE first. + NEWPARENT->children[1] = OPENINGON; + NEWPARENT->children[0] = PNODE; + } else { + // we are hovering over the second node, make PNODE second. + NEWPARENT->children[0] = OPENINGON; + NEWPARENT->children[1] = PNODE; + } + } else { + if (*PFORCESPLIT == 1) { + NEWPARENT->children[1] = OPENINGON; + NEWPARENT->children[0] = PNODE; + } else { + NEWPARENT->children[0] = OPENINGON; + NEWPARENT->children[1] = PNODE; + } + } + + // split in favor of a specific window + if (*PSPLITBIAS && NEWPARENT->children[0] == PNODE) + NEWPARENT->splitRatio = 2.f - NEWPARENT->splitRatio; + + // and update the previous parent if it exists + if (OPENINGON->pParent) { + if (OPENINGON->pParent->children[0] == OPENINGON) + OPENINGON->pParent->children[0] = NEWPARENT; + else + OPENINGON->pParent->children[1] = NEWPARENT; + } + + // Update the children + if (!verticalOverride && (NEWPARENT->box.w * *PWIDTHMULTIPLIER > NEWPARENT->box.h || horizontalOverride)) { + // split left/right -> forced + OPENINGON->box = {NEWPARENT->box.pos(), Vector2D(NEWPARENT->box.w / 2.f, NEWPARENT->box.h)}; + PNODE->box = {Vector2D(NEWPARENT->box.x + NEWPARENT->box.w / 2.f, NEWPARENT->box.y), Vector2D(NEWPARENT->box.w / 2.f, NEWPARENT->box.h)}; + } else { + // split top/bottom + OPENINGON->box = {NEWPARENT->box.pos(), Vector2D(NEWPARENT->box.w, NEWPARENT->box.h / 2.f)}; + PNODE->box = {Vector2D(NEWPARENT->box.x, NEWPARENT->box.y + NEWPARENT->box.h / 2.f), Vector2D(NEWPARENT->box.w, NEWPARENT->box.h / 2.f)}; + } + + OPENINGON->pParent = NEWPARENT; + PNODE->pParent = NEWPARENT; + + NEWPARENT->recalcSizePosRecursive(false, horizontalOverride, verticalOverride); + + calculateWorkspace(); +} + +void CDwindleAlgorithm::movedTarget(SP target, std::optional focalPoint) { + m_overrideFocalPoint = focalPoint; + addTarget(target, false); + m_overrideFocalPoint.reset(); +} + +void CDwindleAlgorithm::removeTarget(SP target) { + const auto PNODE = getNodeFromTarget(target); + + if (!PNODE) { + Log::logger->log(Log::ERR, "onWindowRemovedTiling node null?"); + return; + } + + if (target->fullscreenMode() != FSMODE_NONE) + g_pCompositor->setWindowFullscreenInternal(target->window(), FSMODE_NONE); + + const auto PPARENT = PNODE->pParent; + + if (!PPARENT) { + Log::logger->log(Log::DEBUG, "Removing last node (dwindle)"); + std::erase(m_dwindleNodesData, PNODE); + return; + } + + const auto PSIBLING = PPARENT->children[0] == PNODE ? PPARENT->children[1] : PPARENT->children[0]; + + PSIBLING->pParent = PPARENT->pParent; + + if (PPARENT->pParent != nullptr) { + if (PPARENT->pParent->children[0] == PPARENT) + PPARENT->pParent->children[0] = PSIBLING; + else + PPARENT->pParent->children[1] = PSIBLING; + } + + PPARENT->valid = false; + PNODE->valid = false; + + std::erase(m_dwindleNodesData, PPARENT); + std::erase(m_dwindleNodesData, PNODE); + + recalculate(); +} + +void CDwindleAlgorithm::resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner) { + if (!validMapped(target->window())) + return; + + const auto PNODE = getNodeFromTarget(target); + + if (!PNODE) + return; + + static auto PANIMATE = CConfigValue("misc:animate_manual_resizes"); + static auto PSMARTRESIZING = CConfigValue("dwindle:smart_resizing"); + + // get some data about our window + const auto PMONITOR = m_parent->space()->workspace()->m_monitor; + const auto MONITOR_WORKAREA = PMONITOR->logicalBoxMinusReserved(); + const auto BOX = target->position(); + const bool DISPLAYLEFT = STICKS(BOX.x, MONITOR_WORKAREA.x); + const bool DISPLAYRIGHT = STICKS(BOX.x + BOX.w, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w); + const bool DISPLAYTOP = STICKS(BOX.y, MONITOR_WORKAREA.y); + const bool DISPLAYBOTTOM = STICKS(BOX.y + BOX.h, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h); + + // construct allowed movement + Vector2D allowedMovement = Δ; + if (DISPLAYLEFT && DISPLAYRIGHT) + allowedMovement.x = 0; + + if (DISPLAYBOTTOM && DISPLAYTOP) + allowedMovement.y = 0; + + if (*PSMARTRESIZING == 1) { + // Identify inner and outer nodes for both directions + SP PVOUTER = nullptr; + SP PVINNER = nullptr; + SP PHOUTER = nullptr; + SP PHINNER = nullptr; + + const auto LEFT = corner == CORNER_TOPLEFT || corner == CORNER_BOTTOMLEFT || DISPLAYRIGHT; + const auto TOP = corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT || DISPLAYBOTTOM; + const auto RIGHT = corner == CORNER_TOPRIGHT || corner == CORNER_BOTTOMRIGHT || DISPLAYLEFT; + const auto BOTTOM = corner == CORNER_BOTTOMLEFT || corner == CORNER_BOTTOMRIGHT || DISPLAYTOP; + const auto NONE = corner == CORNER_NONE; + + for (auto PCURRENT = PNODE; PCURRENT && PCURRENT->pParent; PCURRENT = PCURRENT->pParent.lock()) { + const auto PPARENT = PCURRENT->pParent; + + if (!PVOUTER && PPARENT->splitTop && (NONE || (TOP && PPARENT->children[1] == PCURRENT) || (BOTTOM && PPARENT->children[0] == PCURRENT))) + PVOUTER = PCURRENT; + else if (!PVOUTER && !PVINNER && PPARENT->splitTop) + PVINNER = PCURRENT; + else if (!PHOUTER && !PPARENT->splitTop && (NONE || (LEFT && PPARENT->children[1] == PCURRENT) || (RIGHT && PPARENT->children[0] == PCURRENT))) + PHOUTER = PCURRENT; + else if (!PHOUTER && !PHINNER && !PPARENT->splitTop) + PHINNER = PCURRENT; + + if (PVOUTER && PHOUTER) + break; + } + + if (PHOUTER) { + PHOUTER->pParent->splitRatio = std::clamp(PHOUTER->pParent->splitRatio + allowedMovement.x * 2.f / PHOUTER->pParent->box.w, 0.1, 1.9); + + if (PHINNER) { + const auto ORIGINAL = PHINNER->box.w; + PHOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0); + if (PHINNER->pParent->children[0] == PHINNER) + PHINNER->pParent->splitRatio = std::clamp((ORIGINAL - allowedMovement.x) / PHINNER->pParent->box.w * 2.f, 0.1, 1.9); + else + PHINNER->pParent->splitRatio = std::clamp(2 - (ORIGINAL + allowedMovement.x) / PHINNER->pParent->box.w * 2.f, 0.1, 1.9); + PHINNER->pParent->recalcSizePosRecursive(*PANIMATE == 0); + } else + PHOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0); + } + + if (PVOUTER) { + PVOUTER->pParent->splitRatio = std::clamp(PVOUTER->pParent->splitRatio + allowedMovement.y * 2.f / PVOUTER->pParent->box.h, 0.1, 1.9); + + if (PVINNER) { + const auto ORIGINAL = PVINNER->box.h; + PVOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0); + if (PVINNER->pParent->children[0] == PVINNER) + PVINNER->pParent->splitRatio = std::clamp((ORIGINAL - allowedMovement.y) / PVINNER->pParent->box.h * 2.f, 0.1, 1.9); + else + PVINNER->pParent->splitRatio = std::clamp(2 - (ORIGINAL + allowedMovement.y) / PVINNER->pParent->box.h * 2.f, 0.1, 1.9); + PVINNER->pParent->recalcSizePosRecursive(*PANIMATE == 0); + } else + PVOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0); + } + } else { + // get the correct containers to apply splitratio to + const auto PPARENT = PNODE->pParent; + + if (!PPARENT) + return; // the only window on a workspace, ignore + + const bool PARENTSIDEBYSIDE = !PPARENT->splitTop; + + // Get the parent's parent + auto PPARENT2 = PPARENT->pParent; + + Hyprutils::Utils::CScopeGuard x([target, this] { + // snap all windows, don't animate resizes if they are manual + if (target == g_layoutManager->dragController()->target()) { + for (const auto& w : m_dwindleNodesData) { + if (w->isNode) + continue; + + w->pTarget->warpPositionSize(); + } + } + }); + + // No parent means we have only 2 windows, and thus one axis of freedom + if (!PPARENT2) { + if (PARENTSIDEBYSIDE) { + allowedMovement.x *= 2.f / PPARENT->box.w; + PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.x, 0.1, 1.9); + PPARENT->recalcSizePosRecursive(*PANIMATE == 0); + } else { + allowedMovement.y *= 2.f / PPARENT->box.h; + PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.y, 0.1, 1.9); + PPARENT->recalcSizePosRecursive(*PANIMATE == 0); + } + + return; + } + + // Get first parent with other split + while (PPARENT2 && PPARENT2->splitTop == !PARENTSIDEBYSIDE) + PPARENT2 = PPARENT2->pParent; + + // no parent, one axis of freedom + if (!PPARENT2) { + if (PARENTSIDEBYSIDE) { + allowedMovement.x *= 2.f / PPARENT->box.w; + PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.x, 0.1, 1.9); + PPARENT->recalcSizePosRecursive(*PANIMATE == 0); + } else { + allowedMovement.y *= 2.f / PPARENT->box.h; + PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.y, 0.1, 1.9); + PPARENT->recalcSizePosRecursive(*PANIMATE == 0); + } + + return; + } + + // 2 axes of freedom + const auto SIDECONTAINER = PARENTSIDEBYSIDE ? PPARENT : PPARENT2; + const auto TOPCONTAINER = PARENTSIDEBYSIDE ? PPARENT2 : PPARENT; + + allowedMovement.x *= 2.f / SIDECONTAINER->box.w; + allowedMovement.y *= 2.f / TOPCONTAINER->box.h; + + SIDECONTAINER->splitRatio = std::clamp(SIDECONTAINER->splitRatio + allowedMovement.x, 0.1, 1.9); + TOPCONTAINER->splitRatio = std::clamp(TOPCONTAINER->splitRatio + allowedMovement.y, 0.1, 1.9); + SIDECONTAINER->recalcSizePosRecursive(*PANIMATE == 0); + TOPCONTAINER->recalcSizePosRecursive(*PANIMATE == 0); + } + + // snap all windows, don't animate resizes if they are manual + if (target == g_layoutManager->dragController()->target()) { + for (const auto& w : m_dwindleNodesData) { + if (w->isNode) + continue; + + w->pTarget->warpPositionSize(); + } + } +} + +SP CDwindleAlgorithm::getNextCandidate(SP old) { + const auto MIDDLE = old->position().middle(); + + if (const auto NODE = getClosestNode(MIDDLE); NODE) + return NODE->pTarget.lock(); + + if (const auto NODE = getFirstNode(); NODE) + return NODE->pTarget.lock(); + + return nullptr; +} + +void CDwindleAlgorithm::swapTargets(SP a, SP b) { + auto nodeA = getNodeFromTarget(a); + auto nodeB = getNodeFromTarget(b); + + if (nodeA) + nodeA->pTarget = b; + if (nodeB) + nodeB->pTarget = a; +} + +void CDwindleAlgorithm::recalculate() { + calculateWorkspace(); +} + +std::optional CDwindleAlgorithm::predictSizeForNewTarget() { + // get window candidate + PHLWINDOW candidate = Desktop::focusState()->window(); + + if (!candidate || candidate->m_workspace != m_parent->space()->workspace()) + candidate = m_parent->space()->workspace()->getFirstWindow(); + + // create a fake node + SDwindleNodeData node; + + if (!candidate) + return Desktop::focusState()->monitor()->m_size; + else { + const auto PNODE = getNodeFromWindow(candidate); + + if (!PNODE) + return {}; + + node = *PNODE; + node.pTarget.reset(); + + CBox box = PNODE->box; + + static auto PFLMULT = CConfigValue("dwindle:split_width_multiplier"); + + bool splitTop = box.h * *PFLMULT > box.w; + + const auto SPLITSIDE = !splitTop; + + if (SPLITSIDE) + node.box = {{}, {box.w / 2.0, box.h}}; + else + node.box = {{}, {box.w, box.h / 2.0}}; + + // TODO: make this better and more accurate + + return node.box.size(); + } + + return {}; +} + +void CDwindleAlgorithm::moveTargetInDirection(SP t, Math::eDirection dir, bool silent) { + static auto PMONITORFALLBACK = CConfigValue("binds:window_direction_monitor_fallback"); + + const auto PNODE = getNodeFromTarget(t); + const Vector2D originalPos = t->position().middle(); + + if (!PNODE || !t->window()) + return; + + const auto FOCAL_POINT = focalPointForDir(t, dir); + + const auto PMONITORFOCAL = g_pCompositor->getMonitorFromVector(FOCAL_POINT.value_or(t->position().middle())); + + if (PMONITORFOCAL != m_parent->space()->workspace()->m_monitor && !*PMONITORFALLBACK) + return; // noop + + t->window()->setAnimationsToMove(); + + removeTarget(t); + + if (PMONITORFOCAL != m_parent->space()->workspace()->m_monitor) { + // move with a focal point + + if (PMONITORFOCAL->m_activeWorkspace) + t->assignToSpace(PMONITORFOCAL->m_activeWorkspace->m_space, FOCAL_POINT); + + return; + } + + movedTarget(t, FOCAL_POINT); + + // restore focus to the previous position + if (silent) { + const auto PNODETOFOCUS = getClosestNode(originalPos); + if (PNODETOFOCUS && PNODETOFOCUS->pTarget) + Desktop::focusState()->fullWindowFocus(PNODETOFOCUS->pTarget->window(), Desktop::FOCUS_REASON_KEYBIND); + } +} + +// --------- internal --------- // + +void CDwindleAlgorithm::calculateWorkspace() { + const auto PWORKSPACE = m_parent->space()->workspace(); + const auto PMONITOR = PWORKSPACE->m_monitor; + + if (!PMONITOR || PWORKSPACE->m_hasFullscreenWindow) + return; + + const auto TOPNODE = getMasterNode(); + + if (TOPNODE) { + TOPNODE->box = m_parent->space()->workArea(); + TOPNODE->recalcSizePosRecursive(); + } +} + +SP CDwindleAlgorithm::getNodeFromTarget(SP t) { + for (const auto& n : m_dwindleNodesData) { + if (n->pTarget == t) + return n; + } + + return nullptr; +} + +SP CDwindleAlgorithm::getNodeFromWindow(PHLWINDOW w) { + return w ? getNodeFromTarget(w->layoutTarget()) : nullptr; +} + +int CDwindleAlgorithm::getNodes() { + return m_dwindleNodesData.size(); +} + +SP CDwindleAlgorithm::getFirstNode() { + return m_dwindleNodesData.empty() ? nullptr : m_dwindleNodesData.at(0); +} + +SP CDwindleAlgorithm::getClosestNode(const Vector2D& point, SP skip) { + SP res = nullptr; + double distClosest = -1; + for (auto& n : m_dwindleNodesData) { + if (skip && n->pTarget == skip) + continue; + + if (n->pTarget && Desktop::View::validMapped(n->pTarget->window())) { + auto distAnother = vecToRectDistanceSquared(point, n->box.pos(), n->box.pos() + n->box.size()); + if (!res || distAnother < distClosest) { + res = n; + distClosest = distAnother; + } + } + } + return res; +} + +SP CDwindleAlgorithm::getMasterNode() { + for (auto& n : m_dwindleNodesData) { + if (!n->pParent) + return n; + } + return nullptr; +} + +std::expected CDwindleAlgorithm::layoutMsg(const std::string_view& sv) { + const auto ARGS = CVarList2(std::string{sv}, 0, ' '); + + const auto CURRENT_NODE = getNodeFromWindow(Desktop::focusState()->window()); + + if (ARGS[0] == "togglesplit") { + if (CURRENT_NODE) { + if (!toggleSplit(CURRENT_NODE)) + return std::unexpected("can't togglesplit in the current workspace"); + } + } else if (ARGS[0] == "swapsplit") { + if (CURRENT_NODE) { + if (!swapSplit(CURRENT_NODE)) + return std::unexpected("can't swapsplit in the current workspace"); + } + } else if (ARGS[0] == "rotatesplit") { + if (CURRENT_NODE) { + int angle = 90; + if (!ARGS[1].empty()) { + try { + angle = std::stoi(std::string{ARGS[1]}); + } catch (const std::exception& e) { + Log::logger->log(Log::WARN, "Invalid angle argument for rotatesplit: {}", ARGS[1]); + return std::unexpected("Invalid angle argument"); + } + } + rotateSplit(CURRENT_NODE, angle); + } + } else if (ARGS[0] == "movetoroot") { + auto node = CURRENT_NODE; + if (!ARGS[1].empty()) { + auto w = g_pCompositor->getWindowByRegex(std::string{ARGS[1]}); + if (w) + node = getNodeFromWindow(w); + } + + const auto STABLE = ARGS[2].empty() || ARGS[2] != "unstable"; + if (!moveToRoot(node, STABLE)) + return std::unexpected("can't movetoroot in the current workspace"); + } else if (ARGS[0] == "preselect") { + auto direction = ARGS[1]; + + if (direction.empty()) { + Log::logger->log(Log::ERR, "Expected direction for preselect"); + return std::unexpected("No direction for preselect"); + } + + switch (direction.front()) { + case 'u': + case 't': { + m_overrideDirection = Math::DIRECTION_UP; + break; + } + case 'd': + case 'b': { + m_overrideDirection = Math::DIRECTION_DOWN; + break; + } + case 'r': { + m_overrideDirection = Math::DIRECTION_RIGHT; + break; + } + case 'l': { + m_overrideDirection = Math::DIRECTION_LEFT; + break; + } + default: { + // any other character resets the focus direction + // needed for the persistent mode + m_overrideDirection = Math::DIRECTION_DEFAULT; + break; + } + } + } else if (ARGS[0] == "splitratio") { + auto ratio = ARGS[1]; + bool exact = ARGS[2].starts_with("exact"); + + if (ratio.empty()) + return std::unexpected("splitratio requires an arg"); + + auto delta = getPlusMinusKeywordResult(std::string{ratio}, 0.F); + + if (!CURRENT_NODE || !CURRENT_NODE->pParent) + return std::unexpected("cannot alter split ratio on no / single node"); + + if (!delta) + return std::unexpected(std::format("failed to parse \"{}\" as a delta", ratio)); + + const float newRatio = exact ? *delta : CURRENT_NODE->pParent->splitRatio + *delta; + CURRENT_NODE->pParent->splitRatio = std::clamp(newRatio, 0.1F, 1.9F); + + CURRENT_NODE->pParent->recalcSizePosRecursive(); + } + + return {}; +} + +bool CDwindleAlgorithm::toggleSplit(SP x) { + if (!x || !x->pParent) + return false; + + if (x->pTarget->fullscreenMode() != FSMODE_NONE) + return false; + + x->pParent->splitTop = !x->pParent->splitTop; + + x->pParent->recalcSizePosRecursive(); + + return true; +} + +bool CDwindleAlgorithm::swapSplit(SP x) { + if (x->pTarget->fullscreenMode() != FSMODE_NONE || !x->pParent) + return false; + + std::swap(x->pParent->children[0], x->pParent->children[1]); + + x->pParent->recalcSizePosRecursive(); + + return true; +} + +void CDwindleAlgorithm::rotateSplit(SP x, int angle) { + if (!x || !x->pParent) + return; + + if (x->pTarget->fullscreenMode() != FSMODE_NONE) + return; + + // normalize the angle to multiples of 90 degrees + int normalizedAngle = ((sc(angle / 90) % 4) + 4) % 4; // ensures positive modulo + + auto pParent = x->pParent; + + bool shouldSwap = false; + + switch (normalizedAngle) { + case 0: // 0 degrees - no change + break; + case 1: + if (pParent->splitTop) + shouldSwap = true; + pParent->splitTop = !pParent->splitTop; + break; + case 2: shouldSwap = true; break; + case 3: + if (!pParent->splitTop) + shouldSwap = true; + pParent->splitTop = !pParent->splitTop; + break; + default: break; // should never happen + } + + if (shouldSwap) + std::swap(pParent->children[0], pParent->children[1]); + + pParent->recalcSizePosRecursive(); +} + +bool CDwindleAlgorithm::moveToRoot(SP x, bool stable) { + if (!x || !x->pParent) + return false; + + if (x->pTarget->fullscreenMode() != FSMODE_NONE) + return false; + + // already at root + if (!x->pParent->pParent) + return false; + + auto& pNode = x->pParent->children[0] == x ? x->pParent->children[0] : x->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 = x, pRoot = x->pParent.lock(); + while (pRoot->pParent) { + pAncestor = pRoot; + pRoot = pRoot->pParent.lock(); + } + + 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]); + + pRoot->recalcSizePosRecursive(); + + return true; +} diff --git a/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.hpp b/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.hpp new file mode 100644 index 00000000..41cbf8bb --- /dev/null +++ b/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.hpp @@ -0,0 +1,58 @@ +#include "../../TiledAlgorithm.hpp" + +namespace Layout { + class CAlgorithm; +} + +namespace Layout::Tiled { + struct SDwindleNodeData; + + class CDwindleAlgorithm : public ITiledAlgorithm { + public: + CDwindleAlgorithm() = default; + virtual ~CDwindleAlgorithm() = default; + + virtual void newTarget(SP target); + virtual void movedTarget(SP target, std::optional focalPoint = std::nullopt); + virtual void removeTarget(SP target); + + virtual void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); + virtual void recalculate(); + + virtual SP getNextCandidate(SP old); + + virtual std::expected layoutMsg(const std::string_view& sv); + virtual std::optional predictSizeForNewTarget(); + + virtual void swapTargets(SP a, SP b); + virtual void moveTargetInDirection(SP t, Math::eDirection dir, bool silent); + + private: + std::vector> m_dwindleNodesData; + + struct { + bool started = false; + bool pseudo = false; + bool xExtent = false; + bool yExtent = false; + } m_pseudoDragFlags; + + std::optional m_overrideFocalPoint; // for onWindowCreatedTiling. + + void addTarget(SP target, bool newTarget = true); + void calculateWorkspace(); + SP getNodeFromTarget(SP); + SP getNodeFromWindow(PHLWINDOW w); + int getNodes(); + SP getFirstNode(); + SP getClosestNode(const Vector2D&, SP skip = nullptr); + SP getMasterNode(); + + bool toggleSplit(SP); + bool swapSplit(SP); + void rotateSplit(SP, int angle = 90); + bool moveToRoot(SP, bool stable = true); + + Math::eDirection m_overrideDirection = Math::DIRECTION_DEFAULT; + }; +}; \ No newline at end of file diff --git a/src/layout/algorithm/tiled/master/MasterAlgorithm.cpp b/src/layout/algorithm/tiled/master/MasterAlgorithm.cpp new file mode 100644 index 00000000..7c436b31 --- /dev/null +++ b/src/layout/algorithm/tiled/master/MasterAlgorithm.cpp @@ -0,0 +1,1307 @@ +#include "MasterAlgorithm.hpp" + +#include "../../Algorithm.hpp" +#include "../../../space/Space.hpp" +#include "../../../target/WindowTarget.hpp" + +#include "../../../../config/ConfigValue.hpp" +#include "../../../../config/ConfigManager.hpp" +#include "../../../../desktop/state/FocusState.hpp" +#include "../../../../helpers/Monitor.hpp" +#include "../../../../Compositor.hpp" +#include "../../../../render/Renderer.hpp" + +#include + +using namespace Layout; +using namespace Layout::Tiled; + +struct Layout::Tiled::SMasterNodeData { + bool isMaster = false; + float percMaster = 0.5f; + + WP pTarget; + + Vector2D position; + Vector2D size; + + float percSize = 1.f; // size multiplier for resizing children + + bool ignoreFullscreenChecks = false; + + // + bool operator==(const SMasterNodeData& rhs) const { + return pTarget.lock() == rhs.pTarget.lock(); + } +}; + +void CMasterAlgorithm::newTarget(SP target) { + addTarget(target, true); +} + +void CMasterAlgorithm::movedTarget(SP target, std::optional focalPoint) { + addTarget(target, false); +} + +void CMasterAlgorithm::addTarget(SP target, bool firstMap) { + static auto PNEWONACTIVE = CConfigValue("master:new_on_active"); + static auto PNEWONTOP = CConfigValue("master:new_on_top"); + static auto PNEWSTATUS = CConfigValue("master:new_status"); + + const auto PWORKSPACE = m_parent->space()->workspace(); + const auto PMONITOR = PWORKSPACE->m_monitor; + + bool dragOntoMaster = false; + + if (g_layoutManager->dragController()->wasDraggingWindow()) { + if (const auto n = getClosestNode(g_pInputManager->getMouseCoordsInternal()); n && n->isMaster) + dragOntoMaster = true; + } + + const bool BNEWBEFOREACTIVE = *PNEWONACTIVE == "before"; + const bool BNEWISMASTER = dragOntoMaster || *PNEWSTATUS == "master"; + + const auto PNODE = [&]() -> SP { + if (*PNEWONACTIVE != "none" && !BNEWISMASTER) { + const auto pLastNode = getNodeFromWindow(Desktop::focusState()->window()); + if (pLastNode && !(pLastNode->isMaster && (getMastersNo() == 1 || *PNEWSTATUS == "slave"))) { + auto it = std::ranges::find(m_masterNodesData, pLastNode); + if (!BNEWBEFOREACTIVE) + ++it; + return *m_masterNodesData.emplace(it, makeShared()); + } + } + return *PNEWONTOP ? *m_masterNodesData.emplace(m_masterNodesData.begin(), makeShared()) : m_masterNodesData.emplace_back(makeShared()); + }(); + + PNODE->pTarget = target; + + const auto WINDOWSONWORKSPACE = getNodesNo(); + static auto PMFACT = CConfigValue("master:mfact"); + float lastSplitPercent = *PMFACT; + + auto OPENINGON = isWindowTiled(Desktop::focusState()->window()) && Desktop::focusState()->window()->m_workspace == PWORKSPACE ? + getNodeFromWindow(Desktop::focusState()->window()) : + getMasterNode(); + + const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal(); + static auto PDROPATCURSOR = CConfigValue("master:drop_at_cursor"); + eOrientation orientation = getDynamicOrientation(); + const auto NODEIT = std::ranges::find(m_masterNodesData, PNODE); + + bool forceDropAsMaster = false; + // if dragging window to move, drop it at the cursor position instead of bottom/top of stack + if (*PDROPATCURSOR && g_layoutManager->dragController()->mode() == MBIND_MOVE) { + if (WINDOWSONWORKSPACE > 2) { + auto& v = m_masterNodesData; + + const std::size_t srcIndex = static_cast(std::distance(v.begin(), NODEIT)); + + for (std::size_t i = 0; i < v.size(); ++i) { + const CBox box = v[i]->pTarget->position(); + if (!box.containsPoint(MOUSECOORDS)) + continue; + + std::size_t insertIndex = i; + + switch (orientation) { + case ORIENTATION_LEFT: + case ORIENTATION_RIGHT: + if (MOUSECOORDS.y > box.middle().y) + ++insertIndex; // insert after + break; + + case ORIENTATION_TOP: + case ORIENTATION_BOTTOM: + if (MOUSECOORDS.x > box.middle().x) + ++insertIndex; // insert after + break; + + case ORIENTATION_CENTER: break; + + default: UNREACHABLE(); + } + + if (insertIndex > srcIndex) + --insertIndex; + + if (insertIndex == srcIndex) + break; + + auto node = std::move(v[srcIndex]); + v.erase(v.begin() + static_cast(srcIndex)); + v.insert(v.begin() + static_cast(insertIndex), std::move(node)); + + break; + } + } 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 const& nd : m_masterNodesData) { + if (nd->isMaster) { + const auto MIDDLE = nd->pTarget->position().middle(); + switch (orientation) { + case ORIENTATION_LEFT: + case ORIENTATION_CENTER: + if (MOUSECOORDS.x < MIDDLE.x) + forceDropAsMaster = true; + break; + case ORIENTATION_RIGHT: + if (MOUSECOORDS.x > MIDDLE.x) + forceDropAsMaster = true; + break; + case ORIENTATION_TOP: + if (MOUSECOORDS.y < MIDDLE.y) + forceDropAsMaster = true; + break; + case ORIENTATION_BOTTOM: + if (MOUSECOORDS.y > MIDDLE.y) + forceDropAsMaster = true; + break; + default: UNREACHABLE(); + } + break; + } + } + } + } + + if (BNEWISMASTER // + || WINDOWSONWORKSPACE == 1 // + || (WINDOWSONWORKSPACE > 2 && !firstMap && OPENINGON && OPENINGON->isMaster) // + || forceDropAsMaster // + || (*PNEWSTATUS == "inherit" && OPENINGON && OPENINGON->isMaster && g_layoutManager->dragController()->mode() != MBIND_MOVE)) { + + if (BNEWBEFOREACTIVE) { + for (auto& nd : m_masterNodesData | std::views::reverse) { + if (nd->isMaster) { + nd->isMaster = false; + lastSplitPercent = nd->percMaster; + break; + } + } + } else { + for (auto& nd : m_masterNodesData) { + if (nd->isMaster) { + nd->isMaster = false; + lastSplitPercent = nd->percMaster; + break; + } + } + } + + PNODE->isMaster = true; + PNODE->percMaster = lastSplitPercent; + + // first, check if it isn't too big. + if (const auto MAXSIZE = target->maxSize().value_or(Math::VECTOR2D_MAX); MAXSIZE.x < PMONITOR->m_size.x * lastSplitPercent || MAXSIZE.y < PMONITOR->m_size.y) { + // we can't continue. make it floating. + m_parent->setFloating(target, true, true); + std::erase(m_masterNodesData, PNODE); + return; + } + } else { + PNODE->isMaster = false; + PNODE->percMaster = lastSplitPercent; + + // first, check if it isn't too big. + if (const auto MAXSIZE = target->maxSize().value_or(Math::VECTOR2D_MAX); + MAXSIZE.x < PMONITOR->m_size.x * (1 - lastSplitPercent) || MAXSIZE.y < PMONITOR->m_size.y * (1.f / (WINDOWSONWORKSPACE - 1))) { + // we can't continue. make it floating. + m_parent->setFloating(target, true); + std::erase(m_masterNodesData, PNODE); + return; + } + } + + // recalc + calculateWorkspace(); +} + +void CMasterAlgorithm::removeTarget(SP target) { + const auto MASTERSLEFT = getMastersNo(); + static auto SMALLSPLIT = CConfigValue("master:allow_small_split"); + + const auto PNODE = getNodeFromTarget(target); + + if (target->fullscreenMode() != FSMODE_NONE) + g_pCompositor->setWindowFullscreenInternal(target->window(), FSMODE_NONE); + + if (PNODE->isMaster && (MASTERSLEFT <= 1 || *SMALLSPLIT == 1)) { + // find a new master from top of the list + for (auto& nd : m_masterNodesData) { + if (!nd->isMaster) { + nd->isMaster = true; + nd->percMaster = PNODE->percMaster; + break; + } + } + } + + std::erase(m_masterNodesData, PNODE); + + if (getMastersNo() == getNodesNo() && MASTERSLEFT > 1) { + for (auto& nd : m_masterNodesData | std::views::reverse) { + nd->isMaster = false; + break; + } + } + // BUGFIX: correct bug where closing one master in a stack of 2 would leave + // the screen half bare, and make it difficult to select remaining window + if (getNodesNo() == 1) { + for (auto& nd : m_masterNodesData) { + if (!nd->isMaster) { + nd->isMaster = true; + break; + } + } + } + + calculateWorkspace(); +} + +void CMasterAlgorithm::resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner) { + const auto PNODE = getNodeFromTarget(target); + + if (!PNODE) + return; + + const auto PMONITOR = m_parent->space()->workspace()->m_monitor; + static auto SLAVECOUNTFORCENTER = CConfigValue("master:slave_count_for_center_master"); + static auto PSMARTRESIZING = CConfigValue("master:smart_resizing"); + + const auto WORKAREA = PMONITOR->logicalBoxMinusReserved(); + const bool DISPLAYBOTTOM = STICKS(PNODE->position.y + PNODE->size.y, WORKAREA.y + WORKAREA.h); + const bool DISPLAYRIGHT = STICKS(PNODE->position.x + PNODE->size.x, WORKAREA.x + WORKAREA.w); + const bool DISPLAYTOP = STICKS(PNODE->position.y, WORKAREA.y); + const bool DISPLAYLEFT = STICKS(PNODE->position.x, WORKAREA.x); + + const bool LEFT = corner == CORNER_TOPLEFT || corner == CORNER_BOTTOMLEFT; + const bool TOP = corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT; + const bool NONE = corner == CORNER_NONE; + + const auto MASTERS = getMastersNo(); + const auto WINDOWS = getNodesNo(); + const auto STACKWINDOWS = WINDOWS - MASTERS; + + eOrientation orientation = getDynamicOrientation(); + bool centered = orientation == ORIENTATION_CENTER && (STACKWINDOWS >= *SLAVECOUNTFORCENTER); + double delta = 0; + + if (getNodesNo() == 1 && !centered) + return; + + m_forceWarps = true; + + switch (orientation) { + case ORIENTATION_LEFT: delta = Δ.x / PMONITOR->m_size.x; break; + case ORIENTATION_RIGHT: delta = -Δ.x / PMONITOR->m_size.x; break; + case ORIENTATION_BOTTOM: delta = -Δ.y / PMONITOR->m_size.y; break; + case ORIENTATION_TOP: delta = Δ.y / PMONITOR->m_size.y; break; + case ORIENTATION_CENTER: + delta = Δ.x / PMONITOR->m_size.x; + if (STACKWINDOWS >= *SLAVECOUNTFORCENTER) { + if (!NONE || !PNODE->isMaster) + delta *= 2; + if ((!PNODE->isMaster && DISPLAYLEFT) || (PNODE->isMaster && LEFT && *PSMARTRESIZING)) + delta = -delta; + } + break; + default: UNREACHABLE(); + } + + for (auto& n : m_masterNodesData) { + if (n->isMaster) + n->percMaster = std::clamp(n->percMaster + delta, 0.05, 0.95); + } + + // check the up/down resize + const bool isStackVertical = orientation == ORIENTATION_LEFT || orientation == ORIENTATION_RIGHT || orientation == ORIENTATION_CENTER; + + const auto RESIZEDELTA = isStackVertical ? Δ.y : Δ.x; + + auto nodesInSameColumn = PNODE->isMaster ? MASTERS : STACKWINDOWS; + if (orientation == ORIENTATION_CENTER && !PNODE->isMaster) + nodesInSameColumn = DISPLAYRIGHT ? (nodesInSameColumn + 1) / 2 : nodesInSameColumn / 2; + + const auto SIZE = isStackVertical ? WORKAREA.h / nodesInSameColumn : WORKAREA.w / nodesInSameColumn; + + if (RESIZEDELTA != 0 && nodesInSameColumn > 1) { + if (!*PSMARTRESIZING) { + PNODE->percSize = std::clamp(PNODE->percSize + RESIZEDELTA / SIZE, 0.05, 1.95); + } else { + const auto NODEIT = std::ranges::find(m_masterNodesData, PNODE); + const auto REVNODEIT = std::ranges::find(m_masterNodesData | std::views::reverse, PNODE); + + const float totalSize = isStackVertical ? WORKAREA.h : WORKAREA.w; + const float minSize = totalSize / nodesInSameColumn * 0.2; + const bool resizePrevNodes = isStackVertical ? (TOP || DISPLAYBOTTOM) && !DISPLAYTOP : (LEFT || DISPLAYRIGHT) && !DISPLAYLEFT; + + int nodesLeft = 0; + float sizeLeft = 0; + int nodeCount = 0; + // check the sizes of all the nodes to be resized for later calculation + auto checkNodesLeft = [&sizeLeft, &nodesLeft, orientation, isStackVertical, &nodeCount, PNODE](auto it) { + if (it->isMaster != PNODE->isMaster) + return; + nodeCount++; + if (!it->isMaster && orientation == ORIENTATION_CENTER && nodeCount % 2 == 1) + return; + sizeLeft += isStackVertical ? it->size.y : it->size.x; + nodesLeft++; + }; + float resizeDiff; + if (resizePrevNodes) { + std::for_each(std::next(REVNODEIT), m_masterNodesData.rend(), checkNodesLeft); + resizeDiff = -RESIZEDELTA; + } else { + std::for_each(std::next(NODEIT), m_masterNodesData.end(), checkNodesLeft); + resizeDiff = RESIZEDELTA; + } + + const float nodeSize = isStackVertical ? PNODE->size.y : PNODE->size.x; + const float maxSizeIncrease = sizeLeft - nodesLeft * minSize; + const float maxSizeDecrease = minSize - nodeSize; + + // leaves enough room for the other nodes + resizeDiff = std::clamp(resizeDiff, maxSizeDecrease, maxSizeIncrease); + PNODE->percSize += resizeDiff / SIZE; + + // resize the other nodes + nodeCount = 0; + auto resizeNodesLeft = [maxSizeIncrease, resizeDiff, minSize, orientation, isStackVertical, SIZE, &nodeCount, nodesLeft, PNODE](auto& it) { + if (it->isMaster != PNODE->isMaster) + return; + nodeCount++; + // if center orientation, only resize when on the same side + if (!it->isMaster && orientation == ORIENTATION_CENTER && nodeCount % 2 == 1) + return; + const float size = isStackVertical ? it->size.y : it->size.x; + const float resizeDeltaForEach = maxSizeIncrease != 0 ? resizeDiff * (size - minSize) / maxSizeIncrease : resizeDiff / nodesLeft; + it->percSize -= resizeDeltaForEach / SIZE; + }; + if (resizePrevNodes) + std::for_each(std::next(REVNODEIT), m_masterNodesData.rend(), resizeNodesLeft); + else + std::for_each(std::next(NODEIT), m_masterNodesData.end(), resizeNodesLeft); + } + } + + recalculate(); + + m_forceWarps = false; +} + +void CMasterAlgorithm::swapTargets(SP a, SP b) { + auto nodeA = getNodeFromTarget(a); + auto nodeB = getNodeFromTarget(b); + + if (nodeA) + nodeA->pTarget = b; + if (nodeB) + nodeB->pTarget = a; +} + +void CMasterAlgorithm::moveTargetInDirection(SP t, Math::eDirection dir, bool silent) { + static auto PMONITORFALLBACK = CConfigValue("binds:window_direction_monitor_fallback"); + + const auto PWINDOW2 = g_pCompositor->getWindowInDirection(t->window(), dir); + + if (!t->window()) + return; + + PHLWORKSPACE targetWs; + + if (!PWINDOW2 && t->space() && t->space()->workspace()) { + // try to find a monitor in dir + const auto PMONINDIR = g_pCompositor->getMonitorInDirection(t->space()->workspace()->m_monitor.lock(), dir); + if (PMONINDIR) + targetWs = PMONINDIR->m_activeWorkspace; + } else + targetWs = PWINDOW2->m_workspace; + + if (!targetWs) + return; + + t->window()->setAnimationsToMove(); + + if (t->window()->m_workspace != targetWs) { + if (!*PMONITORFALLBACK) + return; // noop + + t->assignToSpace(targetWs->m_space, focalPointForDir(t, dir)); + } else if (PWINDOW2) { + // if same monitor, switch windows + g_layoutManager->switchTargets(t, PWINDOW2->layoutTarget()); + if (silent) + Desktop::focusState()->fullWindowFocus(PWINDOW2, Desktop::FOCUS_REASON_KEYBIND); + + recalculate(); + } +} + +void CMasterAlgorithm::recalculate() { + calculateWorkspace(); +} + +std::expected CMasterAlgorithm::layoutMsg(const std::string_view& sv) { + auto switchToWindow = [&](SP target) { + if (!target || !validMapped(target->window())) + return; + + Desktop::focusState()->fullWindowFocus(target->window(), Desktop::FOCUS_REASON_KEYBIND); + g_pCompositor->warpCursorTo(target->position().middle()); + + g_pInputManager->m_forcedFocus = target->window(); + g_pInputManager->simulateMouseMovement(); + g_pInputManager->m_forcedFocus.reset(); + }; + + CVarList2 vars(std::string{sv}, 0, 's'); + + if (vars.size() < 1 || vars[0].empty()) { + Log::logger->log(Log::ERR, "layoutmsg called without params"); + return std::unexpected("layoutmsg without params"); + } + + auto command = vars[0]; + + // swapwithmaster + // first message argument can have the following values: + // * master - keep the focus at the new master + // * child - keep the focus at the new child + // * auto (default) - swap the focus (keep the focus of the previously selected window) + // * ignoremaster - ignore if master is focused + + const auto PWINDOW = Desktop::focusState()->window(); + + if (command == "swapwithmaster") { + if (!PWINDOW) + return std::unexpected("No focused window"); + + if (!isWindowTiled(PWINDOW)) + return std::unexpected("focused window isn't tiled"); + + const auto PMASTER = getMasterNode(); + + if (!PMASTER) + return std::unexpected("no master node"); + + const auto NEWCHILD = PMASTER->pTarget.lock(); + + const bool IGNORE_IF_MASTER = vars.size() >= 2 && std::ranges::any_of(vars, [](const auto& e) { return e == "ignoremaster"; }); + + if (PMASTER->pTarget.lock() != PWINDOW->layoutTarget()) { + const auto& NEWMASTER = PWINDOW->layoutTarget(); + const bool newFocusToChild = vars.size() >= 2 && vars[1] == "child"; + g_layoutManager->switchTargets(NEWMASTER, NEWCHILD); + const auto NEWFOCUS = newFocusToChild ? NEWCHILD : NEWMASTER; + switchToWindow(NEWFOCUS); + } else if (!IGNORE_IF_MASTER) { + for (auto const& n : m_masterNodesData) { + if (!n->isMaster) { + const auto NEWMASTER = n->pTarget.lock(); + g_layoutManager->switchTargets(NEWMASTER, NEWCHILD); + const bool newFocusToMaster = vars.size() >= 2 && vars[1] == "master"; + const auto NEWFOCUS = newFocusToMaster ? NEWMASTER : NEWCHILD; + switchToWindow(NEWFOCUS); + break; + } + } + } + + return {}; + } + // focusmaster + // first message argument can have the following values: + // * master - keep the focus at the new master, even if it was focused before + // * previous - focus window which was previously switched from using `focusmaster previous` command, otherwise fallback to `auto` + // * auto (default) - swap the focus with the first child, if the current focus was master, otherwise focus master + else if (command == "focusmaster") { + if (!PWINDOW) + return std::unexpected("no focused window"); + + const auto PMASTER = getMasterNode(); + + if (!PMASTER) + return std::unexpected("no master"); + + const auto& ARG = vars[1]; // returns empty string if out of bounds + + if (PMASTER->pTarget.lock() != PWINDOW->layoutTarget()) { + switchToWindow(PMASTER->pTarget.lock()); + // save previously focused window (only for `previous` mode) + if (ARG == "previous") + m_workspaceData.focusMasterPrev = PWINDOW->layoutTarget(); + return {}; + } + + const auto focusAuto = [&]() { + // focus first non-master window + for (auto const& n : m_masterNodesData) { + if (!n->isMaster) { + switchToWindow(n->pTarget.lock()); + break; + } + } + }; + + if (ARG == "master") + return {}; + // switch to previously saved window + else if (ARG == "previous") { + const auto PREVWINDOW = m_workspaceData.focusMasterPrev.lock(); + const bool VALID = PREVWINDOW && getNodeFromWindow(PREVWINDOW->window()) && (PWINDOW != PREVWINDOW->window()); + VALID ? switchToWindow(PREVWINDOW) : focusAuto(); + } else + focusAuto(); + } else if (command == "cyclenext") { + if (!PWINDOW) + return std::unexpected("no window"); + + const bool NOLOOP = vars.size() >= 2 && vars[1] == "noloop"; + const auto PNEXTWINDOW = getNextTarget(PWINDOW->layoutTarget(), true, !NOLOOP); + switchToWindow(PNEXTWINDOW); + } else if (command == "cycleprev") { + if (!PWINDOW) + return std::unexpected("no window"); + + const bool NOLOOP = vars.size() >= 2 && vars[1] == "noloop"; + const auto PPREVWINDOW = getNextTarget(PWINDOW->layoutTarget(), false, !NOLOOP); + switchToWindow(PPREVWINDOW); + } else if (command == "swapnext") { + if (!validMapped(PWINDOW)) + return std::unexpected("no window"); + + if (PWINDOW->layoutTarget()->floating()) { + g_pKeybindManager->m_dispatchers["swapnext"](""); + return {}; + } + + const bool NOLOOP = vars.size() >= 2 && vars[1] == "noloop"; + const auto PWINDOWTOSWAPWITH = getNextTarget(PWINDOW->layoutTarget(), true, !NOLOOP); + + if (PWINDOWTOSWAPWITH) { + g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE); + g_layoutManager->switchTargets(PWINDOW->layoutTarget(), PWINDOWTOSWAPWITH); + switchToWindow(PWINDOW->layoutTarget()); + } + } else if (command == "swapprev") { + if (!validMapped(PWINDOW)) + return std::unexpected("no window"); + + if (PWINDOW->layoutTarget()->floating()) { + g_pKeybindManager->m_dispatchers["swapnext"]("prev"); + return {}; + } + + const bool NOLOOP = vars.size() >= 2 && vars[1] == "noloop"; + const auto PWINDOWTOSWAPWITH = getNextTarget(PWINDOW->layoutTarget(), false, !NOLOOP); + + if (PWINDOWTOSWAPWITH) { + g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE); + g_layoutManager->switchTargets(PWINDOW->layoutTarget(), PWINDOWTOSWAPWITH); + switchToWindow(PWINDOW->layoutTarget()); + } + } else if (command == "addmaster") { + if (!validMapped(PWINDOW)) + return std::unexpected("no window"); + + if (PWINDOW->layoutTarget()->floating()) + return std::unexpected("window is floating"); + + const auto PNODE = getNodeFromTarget(PWINDOW->layoutTarget()); + + const auto WINDOWS = getNodesNo(); + const auto MASTERS = getMastersNo(); + static auto SMALLSPLIT = CConfigValue("master:allow_small_split"); + + if (MASTERS + 2 > WINDOWS && *SMALLSPLIT == 0) + return std::unexpected("nothing to do"); + + g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE); + + if (!PNODE || PNODE->isMaster) { + // first non-master node + for (auto& n : m_masterNodesData) { + if (!n->isMaster) { + n->isMaster = true; + break; + } + } + } else { + PNODE->isMaster = true; + } + + calculateWorkspace(); + + } else if (command == "removemaster") { + + if (!validMapped(PWINDOW)) + return std::unexpected("no window"); + + if (PWINDOW->layoutTarget()->floating()) + return std::unexpected("window isnt tiled"); + + const auto PNODE = getNodeFromTarget(PWINDOW->layoutTarget()); + + const auto WINDOWS = getNodesNo(); + const auto MASTERS = getMastersNo(); + + if (WINDOWS < 2 || MASTERS < 2) + return std::unexpected("nothing to do"); + + g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE); + + if (!PNODE || !PNODE->isMaster) { + // first non-master node + for (auto& nd : m_masterNodesData | std::views::reverse) { + if (nd->isMaster) { + nd->isMaster = false; + break; + } + } + } else { + PNODE->isMaster = false; + } + + calculateWorkspace(); + } else if (command == "orientationleft" || command == "orientationright" || command == "orientationtop" || command == "orientationbottom" || command == "orientationcenter") { + if (!PWINDOW) + return std::unexpected("no window"); + + g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE); + + if (command == "orientationleft") + m_workspaceData.explicitOrientation = ORIENTATION_LEFT; + else if (command == "orientationright") + m_workspaceData.explicitOrientation = ORIENTATION_RIGHT; + else if (command == "orientationtop") + m_workspaceData.explicitOrientation = ORIENTATION_TOP; + else if (command == "orientationbottom") + m_workspaceData.explicitOrientation = ORIENTATION_BOTTOM; + else if (command == "orientationcenter") + m_workspaceData.explicitOrientation = ORIENTATION_CENTER; + + calculateWorkspace(); + } else if (command == "orientationnext") { + runOrientationCycle(nullptr, 1); + } else if (command == "orientationprev") { + runOrientationCycle(nullptr, -1); + } else if (command == "orientationcycle") { + runOrientationCycle(&vars, 1); + } else if (command == "mfact") { + + if (!PWINDOW) + return std::unexpected("no window"); + + const bool exact = vars[1] == "exact"; + + float ratio = 0.F; + + try { + ratio = std::stof(std::string{exact ? vars[2] : vars[1]}); + } catch (...) { return std::unexpected("bad ratio"); } + + const auto PNODE = getNodeFromWindow(PWINDOW); + + const auto PMASTER = getMasterNode(); + + float newRatio = exact ? ratio : PMASTER->percMaster + ratio; + PMASTER->percMaster = std::clamp(newRatio, 0.05f, 0.95f); + + recalculate(); + } else if (command == "rollnext") { + const auto PNODE = getNodeFromWindow(PWINDOW); + + if (!PNODE) + return std::unexpected("window couldnt be found"); + + const auto OLDMASTER = PNODE->isMaster ? PNODE : getMasterNode(); + if (!OLDMASTER) + return std::unexpected("no old master"); + + auto oldMasterIt = std::ranges::find(m_masterNodesData, OLDMASTER); + + for (auto& nd : m_masterNodesData) { + if (!nd->isMaster) { + const auto& newMaster = nd; + newMaster->isMaster = true; + + auto newMasterIt = std::ranges::find(m_masterNodesData, newMaster); + + if (newMasterIt < oldMasterIt) + std::ranges::rotate(newMasterIt, std::next(newMasterIt), oldMasterIt); + else if (newMasterIt > oldMasterIt) + std::ranges::rotate(oldMasterIt, newMasterIt, std::next(newMasterIt)); + + switchToWindow(newMaster->pTarget.lock()); + OLDMASTER->isMaster = false; + + oldMasterIt = std::ranges::find(m_masterNodesData, OLDMASTER); + if (oldMasterIt != m_masterNodesData.end()) + std::ranges::rotate(oldMasterIt, std::next(oldMasterIt), m_masterNodesData.end()); + + break; + } + } + + calculateWorkspace(); + } else if (command == "rollprev") { + const auto PNODE = getNodeFromWindow(PWINDOW); + + if (!PNODE) + return std::unexpected("window couldnt be found"); + + const auto OLDMASTER = PNODE->isMaster ? PNODE : getMasterNode(); + if (!OLDMASTER) + return std::unexpected("no old master"); + + auto oldMasterIt = std::ranges::find(m_masterNodesData, OLDMASTER); + + for (auto& nd : m_masterNodesData | std::views::reverse) { + if (!nd->isMaster) { + const auto& newMaster = nd; + newMaster->isMaster = true; + + auto newMasterIt = std::ranges::find(m_masterNodesData, newMaster); + + if (newMasterIt < oldMasterIt) + std::ranges::rotate(newMasterIt, std::next(newMasterIt), oldMasterIt); + else if (newMasterIt > oldMasterIt) + std::ranges::rotate(oldMasterIt, newMasterIt, std::next(newMasterIt)); + + switchToWindow(newMaster->pTarget.lock()); + OLDMASTER->isMaster = false; + + oldMasterIt = std::ranges::find(m_masterNodesData, OLDMASTER); + if (oldMasterIt != m_masterNodesData.begin()) + std::ranges::rotate(m_masterNodesData.begin(), oldMasterIt, std::next(oldMasterIt)); + + break; + } + } + + calculateWorkspace(); + } + + return {}; +} + +std::optional CMasterAlgorithm::predictSizeForNewTarget() { + static auto PNEWSTATUS = CConfigValue("master:new_status"); + + const auto MONITOR = m_parent->space()->workspace()->m_monitor; + + if (!MONITOR) + return std::nullopt; + + const int NODES = getNodesNo(); + + if (NODES <= 0) + return Desktop::focusState()->monitor()->m_size; + + const auto MASTER = getMasterNode(); + if (!MASTER) // wtf + return std::nullopt; + + if (*PNEWSTATUS == "master") { + return MASTER->size; + } else { + const auto SLAVES = NODES - getMastersNo(); + + // TODO: make this better + if (SLAVES == 0) + return Vector2D{MONITOR->m_size.x / 2.F, MONITOR->m_size.y}; + else + return Vector2D{MONITOR->m_size.x - MASTER->size.x, MONITOR->m_size.y / (SLAVES + 1)}; + } + + return std::nullopt; +} + +void CMasterAlgorithm::buildOrientationCycleVectorFromVars(std::vector& cycle, Hyprutils::String::CVarList2* vars) { + for (size_t i = 1; i < vars->size(); ++i) { + if ((*vars)[i] == "top") { + cycle.emplace_back(ORIENTATION_TOP); + } else if ((*vars)[i] == "right") { + cycle.emplace_back(ORIENTATION_RIGHT); + } else if ((*vars)[i] == "bottom") { + cycle.emplace_back(ORIENTATION_BOTTOM); + } else if ((*vars)[i] == "left") { + cycle.emplace_back(ORIENTATION_LEFT); + } else if ((*vars)[i] == "center") { + cycle.emplace_back(ORIENTATION_CENTER); + } + } +} + +void CMasterAlgorithm::buildOrientationCycleVectorFromEOperation(std::vector& cycle) { + for (int i = 0; i <= ORIENTATION_CENTER; ++i) { + cycle.push_back(sc(i)); + } +} + +eOrientation CMasterAlgorithm::defaultOrientation() { + static auto PORIENT = CConfigValue("master:orientation"); + + const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(m_parent->space()->workspace()); + std::string orientationString; + if (WORKSPACERULE.layoutopts.contains("orientation")) + orientationString = WORKSPACERULE.layoutopts.at("orientation"); + else + orientationString = *PORIENT; + + eOrientation orientation = ORIENTATION_LEFT; + // override if workspace rule is set + if (!orientationString.empty()) { + if (orientationString == "top") + orientation = ORIENTATION_TOP; + else if (orientationString == "right") + orientation = ORIENTATION_RIGHT; + else if (orientationString == "bottom") + orientation = ORIENTATION_BOTTOM; + else if (orientationString == "center") + orientation = ORIENTATION_CENTER; + else + orientation = ORIENTATION_LEFT; + } + + return orientation; +} + +void CMasterAlgorithm::runOrientationCycle(Hyprutils::String::CVarList2* vars, int next) { + std::vector cycle; + if (vars != nullptr) + buildOrientationCycleVectorFromVars(cycle, vars); + + if (cycle.empty()) + buildOrientationCycleVectorFromEOperation(cycle); + + const auto PWINDOW = Desktop::focusState()->window(); + + if (!PWINDOW) + return; + + g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE); + + int nextOrPrev = 0; + for (size_t i = 0; i < cycle.size(); ++i) { + if (m_workspaceData.explicitOrientation.value_or(defaultOrientation()) == cycle[i]) { + nextOrPrev = i + next; + break; + } + } + + if (nextOrPrev >= sc(cycle.size())) + nextOrPrev = nextOrPrev % sc(cycle.size()); + else if (nextOrPrev < 0) + nextOrPrev = cycle.size() + (nextOrPrev % sc(cycle.size())); + + m_workspaceData.explicitOrientation = cycle.at(nextOrPrev); + calculateWorkspace(); +} + +eOrientation CMasterAlgorithm::getDynamicOrientation() { + return m_workspaceData.explicitOrientation.value_or(defaultOrientation()); +} + +int CMasterAlgorithm::getNodesNo() { + return m_masterNodesData.size(); +} + +SP CMasterAlgorithm::getNodeFromWindow(PHLWINDOW x) { + return x ? getNodeFromTarget(x->layoutTarget()) : nullptr; +} + +SP CMasterAlgorithm::getNodeFromTarget(SP x) { + for (const auto& n : m_masterNodesData) { + if (n->pTarget == x) + return n; + } + + return nullptr; +} + +SP CMasterAlgorithm::getMasterNode() { + for (const auto& n : m_masterNodesData) { + if (n->isMaster) + return n; + } + + return nullptr; +} + +void CMasterAlgorithm::calculateWorkspace() { + const auto PMASTERNODE = getMasterNode(); + + if (!PMASTERNODE) + return; + + Hyprutils::Utils::CScopeGuard x([this] { + g_pHyprRenderer->damageMonitor(m_parent->space()->workspace()->m_monitor.lock()); + + if (!m_forceWarps) + return; + + for (const auto& n : m_masterNodesData) { + n->pTarget->warpPositionSize(); + } + }); + + eOrientation orientation = getDynamicOrientation(); + bool centerMasterWindow = false; + static auto SLAVECOUNTFORCENTER = CConfigValue("master:slave_count_for_center_master"); + static auto CMFALLBACK = CConfigValue("master:center_master_fallback"); + static auto PIGNORERESERVED = CConfigValue("master:center_ignores_reserved"); + static auto PSMARTRESIZING = CConfigValue("master:smart_resizing"); + + const auto MASTERS = getMastersNo(); + const auto WINDOWS = getNodesNo(); + const auto STACKWINDOWS = WINDOWS - MASTERS; + const auto WORKAREA = m_parent->space()->workArea(); + const auto PMONITOR = m_parent->space()->workspace()->m_monitor; + const auto reservedLeft = PMONITOR ? PMONITOR->m_reservedArea.left() : 0; + const auto reservedRight = PMONITOR ? PMONITOR->m_reservedArea.right() : 0; + const auto UNRESERVED_WIDTH = WORKAREA.width + reservedLeft + reservedRight; + + if (orientation == ORIENTATION_CENTER) { + if (STACKWINDOWS >= *SLAVECOUNTFORCENTER) + centerMasterWindow = true; + else { + if (*CMFALLBACK == "left") + orientation = ORIENTATION_LEFT; + else if (*CMFALLBACK == "right") + orientation = ORIENTATION_RIGHT; + else if (*CMFALLBACK == "top") + orientation = ORIENTATION_TOP; + else if (*CMFALLBACK == "bottom") + orientation = ORIENTATION_BOTTOM; + else + orientation = ORIENTATION_LEFT; + } + } + + const float totalSize = (orientation == ORIENTATION_TOP || orientation == ORIENTATION_BOTTOM) ? WORKAREA.w : WORKAREA.h; + const float masterAverageSize = totalSize / MASTERS; + const float slaveAverageSize = totalSize / STACKWINDOWS; + float masterAccumulatedSize = 0; + float slaveAccumulatedSize = 0; + + if (*PSMARTRESIZING) { + // check the total width and height so that later + // if larger/smaller than screen size them down/up + for (auto const& nd : m_masterNodesData) { + if (nd->isMaster) + masterAccumulatedSize += totalSize / MASTERS * nd->percSize; + else + slaveAccumulatedSize += totalSize / STACKWINDOWS * nd->percSize; + } + } + + // compute placement of master window(s) + if (WINDOWS == 1 && !centerMasterWindow) { + static auto PALWAYSKEEPPOSITION = CConfigValue("master:always_keep_position"); + if (*PALWAYSKEEPPOSITION) { + const float WIDTH = WORKAREA.w * PMASTERNODE->percMaster; + float nextX = 0; + + if (orientation == ORIENTATION_RIGHT) + nextX = WORKAREA.w - WIDTH; + else if (orientation == ORIENTATION_CENTER) + nextX = (WORKAREA.w - WIDTH) / 2; + + PMASTERNODE->size = Vector2D(WIDTH, WORKAREA.h); + PMASTERNODE->position = WORKAREA.pos() + Vector2D(nextX, 0.0); + } else { + PMASTERNODE->size = WORKAREA.size(); + PMASTERNODE->position = WORKAREA.pos(); + } + + PMASTERNODE->pTarget->setPositionGlobal({PMASTERNODE->position, PMASTERNODE->size}); + return; + } else if (orientation == ORIENTATION_TOP || orientation == ORIENTATION_BOTTOM) { + const float HEIGHT = STACKWINDOWS != 0 ? WORKAREA.h * PMASTERNODE->percMaster : WORKAREA.h; + float widthLeft = WORKAREA.w; + int mastersLeft = MASTERS; + float nextX = 0; + float nextY = 0; + + if (orientation == ORIENTATION_BOTTOM) + nextY = WORKAREA.h - HEIGHT; + + for (auto& nd : m_masterNodesData) { + if (!nd->isMaster) + continue; + + float WIDTH = mastersLeft > 1 ? widthLeft / mastersLeft * nd->percSize : widthLeft; + if (WIDTH > widthLeft * 0.9f && mastersLeft > 1) + WIDTH = widthLeft * 0.9f; + + if (*PSMARTRESIZING) { + nd->percSize *= WORKAREA.w / masterAccumulatedSize; + WIDTH = masterAverageSize * nd->percSize; + } + + nd->size = Vector2D(WIDTH, HEIGHT); + nd->position = WORKAREA.pos() + Vector2D(nextX, nextY); + nd->pTarget->setPositionGlobal({nd->position, nd->size}); + + mastersLeft--; + widthLeft -= WIDTH; + nextX += WIDTH; + } + } else { // orientation left, right or center + const float TOTAL_WIDTH = *PIGNORERESERVED && centerMasterWindow ? UNRESERVED_WIDTH : WORKAREA.w; + float WIDTH = TOTAL_WIDTH; + float heightLeft = WORKAREA.h; + int mastersLeft = MASTERS; + float nextX = 0; + float nextY = 0; + + if (STACKWINDOWS > 0 || centerMasterWindow) + WIDTH *= PMASTERNODE->percMaster; + + if (orientation == ORIENTATION_RIGHT) + nextX = WORKAREA.w - WIDTH; + else if (centerMasterWindow) + nextX += (TOTAL_WIDTH - WIDTH) / 2; + + for (auto& nd : m_masterNodesData) { + if (!nd->isMaster) + continue; + + float HEIGHT = mastersLeft > 1 ? heightLeft / mastersLeft * nd->percSize : heightLeft; + if (HEIGHT > heightLeft * 0.9f && mastersLeft > 1) + HEIGHT = heightLeft * 0.9f; + + if (*PSMARTRESIZING) { + nd->percSize *= WORKAREA.h / masterAccumulatedSize; + HEIGHT = masterAverageSize * nd->percSize; + } + + nd->size = Vector2D(WIDTH, HEIGHT); + nd->position = (*PIGNORERESERVED && centerMasterWindow ? WORKAREA.pos() - Vector2D(reservedLeft, 0.0) : WORKAREA.pos()) + Vector2D(nextX, nextY); + nd->pTarget->setPositionGlobal({nd->position, nd->size}); + + mastersLeft--; + heightLeft -= HEIGHT; + nextY += HEIGHT; + } + } + + if (STACKWINDOWS == 0) + return; + + // compute placement of slave window(s) + int slavesLeft = STACKWINDOWS; + if (orientation == ORIENTATION_TOP || orientation == ORIENTATION_BOTTOM) { + const float HEIGHT = WORKAREA.h - PMASTERNODE->size.y; + float widthLeft = WORKAREA.w; + float nextX = 0; + float nextY = 0; + + if (orientation == ORIENTATION_TOP) + nextY = PMASTERNODE->size.y; + + for (auto& nd : m_masterNodesData) { + if (nd->isMaster) + continue; + + float WIDTH = slavesLeft > 1 ? widthLeft / slavesLeft * nd->percSize : widthLeft; + if (WIDTH > widthLeft * 0.9f && slavesLeft > 1) + WIDTH = widthLeft * 0.9f; + + if (*PSMARTRESIZING) { + nd->percSize *= WORKAREA.w / slaveAccumulatedSize; + WIDTH = slaveAverageSize * nd->percSize; + } + + nd->size = Vector2D(WIDTH, HEIGHT); + nd->position = WORKAREA.pos() + Vector2D(nextX, nextY); + nd->pTarget->setPositionGlobal({nd->position, nd->size}); + + slavesLeft--; + widthLeft -= WIDTH; + nextX += WIDTH; + } + } else if (orientation == ORIENTATION_LEFT || orientation == ORIENTATION_RIGHT) { + const float WIDTH = WORKAREA.w - PMASTERNODE->size.x; + float heightLeft = WORKAREA.h; + float nextY = 0; + float nextX = 0; + + if (orientation == ORIENTATION_LEFT) + nextX = PMASTERNODE->size.x; + + for (auto& nd : m_masterNodesData) { + if (nd->isMaster) + continue; + + float HEIGHT = slavesLeft > 1 ? heightLeft / slavesLeft * nd->percSize : heightLeft; + if (HEIGHT > heightLeft * 0.9f && slavesLeft > 1) + HEIGHT = heightLeft * 0.9f; + + if (*PSMARTRESIZING) { + nd->percSize *= WORKAREA.h / slaveAccumulatedSize; + HEIGHT = slaveAverageSize * nd->percSize; + } + + nd->size = Vector2D(WIDTH, HEIGHT); + nd->position = WORKAREA.pos() + Vector2D(nextX, nextY); + nd->pTarget->setPositionGlobal({nd->position, nd->size}); + + slavesLeft--; + heightLeft -= HEIGHT; + nextY += HEIGHT; + } + } else { // slaves for centered master window(s) + const float WIDTH = ((*PIGNORERESERVED ? UNRESERVED_WIDTH : WORKAREA.w) - PMASTERNODE->size.x) / 2.0; + float heightLeft = 0; + float heightLeftL = WORKAREA.h; + float heightLeftR = WORKAREA.h; + float nextX = 0; + float nextY = 0; + float nextYL = 0; + float nextYR = 0; + bool onRight = *CMFALLBACK == "right"; + int slavesLeftL = 1 + (slavesLeft - 1) / 2; + int slavesLeftR = slavesLeft - slavesLeftL; + + if (onRight) { + slavesLeftR = 1 + (slavesLeft - 1) / 2; + slavesLeftL = slavesLeft - slavesLeftR; + } + + const float slaveAverageHeightL = WORKAREA.h / slavesLeftL; + const float slaveAverageHeightR = WORKAREA.h / slavesLeftR; + float slaveAccumulatedHeightL = 0; + float slaveAccumulatedHeightR = 0; + + if (*PSMARTRESIZING) { + for (auto const& nd : m_masterNodesData) { + if (nd->isMaster) + continue; + + if (onRight) + slaveAccumulatedHeightR += slaveAverageHeightR * nd->percSize; + else + slaveAccumulatedHeightL += slaveAverageHeightL * nd->percSize; + + onRight = !onRight; + } + + onRight = *CMFALLBACK == "right"; + } + + for (auto& nd : m_masterNodesData) { + if (nd->isMaster) + continue; + + if (onRight) { + nextX = WIDTH + PMASTERNODE->size.x - (*PIGNORERESERVED ? reservedLeft : 0); + nextY = nextYR; + heightLeft = heightLeftR; + slavesLeft = slavesLeftR; + } else { + nextX = 0; + nextY = nextYL; + heightLeft = heightLeftL; + slavesLeft = slavesLeftL; + } + + float HEIGHT = slavesLeft > 1 ? heightLeft / slavesLeft * nd->percSize : heightLeft; + if (HEIGHT > heightLeft * 0.9f && slavesLeft > 1) + HEIGHT = heightLeft * 0.9f; + + if (*PSMARTRESIZING) { + if (onRight) { + nd->percSize *= WORKAREA.h / slaveAccumulatedHeightR; + HEIGHT = slaveAverageHeightR * nd->percSize; + } else { + nd->percSize *= WORKAREA.h / slaveAccumulatedHeightL; + HEIGHT = slaveAverageHeightL * nd->percSize; + } + } + + nd->size = Vector2D(*PIGNORERESERVED ? (WIDTH - (onRight ? reservedRight : reservedLeft)) : WIDTH, HEIGHT); + nd->position = WORKAREA.pos() + Vector2D(nextX, nextY); + nd->pTarget->setPositionGlobal({nd->position, nd->size}); + + if (onRight) { + heightLeftR -= HEIGHT; + nextYR += HEIGHT; + slavesLeftR--; + } else { + heightLeftL -= HEIGHT; + nextYL += HEIGHT; + slavesLeftL--; + } + + onRight = !onRight; + } + } +} + +SP CMasterAlgorithm::getNextCandidate(SP old) { + const auto MIDDLE = old->position().middle(); + + if (const auto NODE = getClosestNode(MIDDLE); NODE) + return NODE->pTarget.lock(); + + if (const auto NODE = getMasterNode(); NODE) + return NODE->pTarget.lock(); + + return nullptr; +} + +SP CMasterAlgorithm::getNextTarget(SP t, bool next, bool loop) { + if (t->floating()) + return nullptr; + + const auto PNODE = getNodeFromTarget(t); + + auto nodes = m_masterNodesData; + if (!next) + std::ranges::reverse(nodes); + + const auto NODEIT = std::ranges::find(nodes, PNODE); + + const bool ISMASTER = PNODE->isMaster; + + auto CANDIDATE = std::find_if(NODEIT, nodes.end(), [&](const auto& other) { return other != PNODE && ISMASTER == other->isMaster; }); + if (CANDIDATE == nodes.end()) + CANDIDATE = std::ranges::find_if(nodes, [&](const auto& other) { return other != PNODE && ISMASTER != other->isMaster; }); + + if (CANDIDATE != nodes.end() && !loop) { + if ((*CANDIDATE)->isMaster && next) + return nullptr; + if (!(*CANDIDATE)->isMaster && ISMASTER && !next) + return nullptr; + } + + return CANDIDATE == nodes.end() ? nullptr : (*CANDIDATE)->pTarget.lock(); +} + +int CMasterAlgorithm::getMastersNo() { + return std::ranges::count_if(m_masterNodesData, [](const auto& n) { return n->isMaster; }); +} + +bool CMasterAlgorithm::isWindowTiled(PHLWINDOW x) { + return x && !x->layoutTarget()->floating(); +} + +SP CMasterAlgorithm::getClosestNode(const Vector2D& point) { + SP res = nullptr; + double distClosest = -1; + for (auto& n : m_masterNodesData) { + if (n->pTarget && Desktop::View::validMapped(n->pTarget->window())) { + auto distAnother = vecToRectDistanceSquared(point, n->position, n->position + n->size); + if (!res || distAnother < distClosest) { + res = n; + distClosest = distAnother; + } + } + } + return res; +} diff --git a/src/layout/algorithm/tiled/master/MasterAlgorithm.hpp b/src/layout/algorithm/tiled/master/MasterAlgorithm.hpp new file mode 100644 index 00000000..5cfa6b36 --- /dev/null +++ b/src/layout/algorithm/tiled/master/MasterAlgorithm.hpp @@ -0,0 +1,76 @@ +#include "../../TiledAlgorithm.hpp" + +#include + +namespace Layout { + class CAlgorithm; +} + +namespace Layout::Tiled { + struct SMasterNodeData; + + //orientation determines which side of the screen the master area resides + enum eOrientation : uint8_t { + ORIENTATION_LEFT = 0, + ORIENTATION_TOP, + ORIENTATION_RIGHT, + ORIENTATION_BOTTOM, + ORIENTATION_CENTER + }; + + struct SMasterWorkspaceData { + WORKSPACEID workspaceID = WORKSPACE_INVALID; + std::optional explicitOrientation; + // Previously focused non-master window when `focusmaster previous` command was issued + WP focusMasterPrev; + + // + bool operator==(const SMasterWorkspaceData& rhs) const { + return workspaceID == rhs.workspaceID; + } + }; + + class CMasterAlgorithm : public ITiledAlgorithm { + public: + CMasterAlgorithm() = default; + virtual ~CMasterAlgorithm() = default; + + virtual void newTarget(SP target); + virtual void movedTarget(SP target, std::optional focalPoint = std::nullopt); + virtual void removeTarget(SP target); + + virtual void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); + virtual void recalculate(); + + virtual SP getNextCandidate(SP old); + + virtual std::expected layoutMsg(const std::string_view& sv); + virtual std::optional predictSizeForNewTarget(); + + virtual void swapTargets(SP a, SP b); + virtual void moveTargetInDirection(SP t, Math::eDirection dir, bool silent); + + private: + std::vector> m_masterNodesData; + SMasterWorkspaceData m_workspaceData; + + void addTarget(SP target, bool firstMap); + + bool m_forceWarps = false; + + void buildOrientationCycleVectorFromVars(std::vector& cycle, Hyprutils::String::CVarList2* vars); + void buildOrientationCycleVectorFromEOperation(std::vector& cycle); + void runOrientationCycle(Hyprutils::String::CVarList2* vars, int next); + eOrientation getDynamicOrientation(); + int getNodesNo(); + SP getNodeFromWindow(PHLWINDOW); + SP getNodeFromTarget(SP); + SP getMasterNode(); + SP getClosestNode(const Vector2D&); + void calculateWorkspace(); + SP getNextTarget(SP, bool, bool); + int getMastersNo(); + bool isWindowTiled(PHLWINDOW); + eOrientation defaultOrientation(); + }; +}; \ No newline at end of file diff --git a/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.cpp b/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.cpp new file mode 100644 index 00000000..fe92f27c --- /dev/null +++ b/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.cpp @@ -0,0 +1,278 @@ +#include "MonocleAlgorithm.hpp" + +#include "../../Algorithm.hpp" +#include "../../../space/Space.hpp" +#include "../../../target/WindowTarget.hpp" +#include "../../../LayoutManager.hpp" + +#include "../../../../config/ConfigValue.hpp" +#include "../../../../desktop/state/FocusState.hpp" +#include "../../../../desktop/history/WindowHistoryTracker.hpp" +#include "../../../../helpers/Monitor.hpp" +#include "../../../../Compositor.hpp" +#include "../../../../event/EventBus.hpp" + +#include +#include +#include + +using namespace Hyprutils::String; +using namespace Hyprutils::Utils; +using namespace Layout; +using namespace Layout::Tiled; + +CMonocleAlgorithm::CMonocleAlgorithm() { + // hook into focus changes to bring focused window to front + m_focusCallback = Event::bus()->m_events.window.active.listen([this](PHLWINDOW pWindow, Desktop::eFocusReason reason) { + if (!pWindow) + return; + + if (!pWindow->m_workspace->isVisible()) + return; + + const auto TARGET = pWindow->layoutTarget(); + if (!TARGET) + return; + + focusTargetUpdate(TARGET); + }); +} + +CMonocleAlgorithm::~CMonocleAlgorithm() { + // unhide all windows before destruction + for (const auto& data : m_targetDatas) { + const auto TARGET = data->target.lock(); + if (!TARGET) + continue; + + const auto WINDOW = TARGET->window(); + if (WINDOW) + WINDOW->setHidden(false); + } + + m_focusCallback.reset(); +} + +SP CMonocleAlgorithm::dataFor(SP t) { + for (auto& data : m_targetDatas) { + if (data->target.lock() == t) + return data; + } + return nullptr; +} + +void CMonocleAlgorithm::newTarget(SP target) { + const auto DATA = m_targetDatas.emplace_back(makeShared(target)); + + m_currentVisibleIndex = m_targetDatas.size() - 1; + + recalculate(); +} + +void CMonocleAlgorithm::movedTarget(SP target, std::optional focalPoint) { + newTarget(target); +} + +void CMonocleAlgorithm::removeTarget(SP target) { + auto it = std::ranges::find_if(m_targetDatas, [target](const auto& data) { return data->target.lock() == target; }); + + if (it == m_targetDatas.end()) + return; + + // unhide window when removing from monocle layout + const auto WINDOW = target->window(); + if (WINDOW) + WINDOW->setHidden(false); + + const auto INDEX = std::distance(m_targetDatas.begin(), it); + m_targetDatas.erase(it); + + if (m_targetDatas.empty()) { + m_currentVisibleIndex = 0; + return; + } + + // try to use the last window in history if we can + for (const auto& historyWindow : Desktop::History::windowTracker()->historyForWorkspace(m_parent->space()->workspace()) | std::views::reverse) { + auto it = std::ranges::find_if(m_targetDatas, [&historyWindow](const auto& d) { return d->target == historyWindow->layoutTarget(); }); + + if (it == m_targetDatas.end()) + continue; + + // we found a historical target, use that first + m_currentVisibleIndex = std::distance(m_targetDatas.begin(), it); + + recalculate(); + + return; + } + + // if we didn't find history, fall back to last + + if (m_currentVisibleIndex >= (int)m_targetDatas.size()) + m_currentVisibleIndex = m_targetDatas.size() - 1; + else if (INDEX <= m_currentVisibleIndex && m_currentVisibleIndex > 0) + m_currentVisibleIndex--; + + recalculate(); +} + +void CMonocleAlgorithm::resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner) { + // monocle layout doesn't support manual resizing, all windows are fullscreen +} + +void CMonocleAlgorithm::recalculate() { + if (m_targetDatas.empty()) + return; + + const auto WORK_AREA = m_parent->space()->workArea(); + + for (size_t i = 0; i < m_targetDatas.size(); ++i) { + const auto& DATA = m_targetDatas[i]; + const auto TARGET = DATA->target.lock(); + + if (!TARGET) + continue; + + const auto WINDOW = TARGET->window(); + if (!WINDOW) + continue; + + DATA->layoutBox = WORK_AREA; + TARGET->setPositionGlobal(WORK_AREA); + + const bool SHOULD_BE_VISIBLE = ((int)i == m_currentVisibleIndex); + WINDOW->setHidden(!SHOULD_BE_VISIBLE); + } +} + +SP CMonocleAlgorithm::getNextCandidate(SP old) { + if (m_targetDatas.empty()) + return nullptr; + + auto it = std::ranges::find_if(m_targetDatas, [old](const auto& data) { return data->target.lock() == old; }); + + if (it == m_targetDatas.end()) { + if (m_currentVisibleIndex >= 0 && m_currentVisibleIndex < (int)m_targetDatas.size()) + return m_targetDatas[m_currentVisibleIndex]->target.lock(); + return nullptr; + } + + auto next = std::next(it); + if (next == m_targetDatas.end()) + next = m_targetDatas.begin(); + + return next->get()->target.lock(); +} + +std::expected CMonocleAlgorithm::layoutMsg(const std::string_view& sv) { + CVarList2 vars(std::string{sv}, 0, 's'); + + if (vars.size() < 1) + return std::unexpected("layoutmsg requires at least 1 argument"); + + const auto COMMAND = vars[0]; + + if (COMMAND == "cyclenext") { + cycleNext(); + return {}; + } else if (COMMAND == "cycleprev") { + cyclePrev(); + return {}; + } + + return std::unexpected(std::format("Unknown monocle layoutmsg: {}", COMMAND)); +} + +std::optional CMonocleAlgorithm::predictSizeForNewTarget() { + const auto WORK_AREA = m_parent->space()->workArea(); + return WORK_AREA.size(); +} + +void CMonocleAlgorithm::swapTargets(SP a, SP b) { + auto nodeA = dataFor(a); + auto nodeB = dataFor(b); + + if (nodeA) + nodeA->target = b; + if (nodeB) + nodeB->target = a; + + recalculate(); +} + +void CMonocleAlgorithm::moveTargetInDirection(SP t, Math::eDirection dir, bool silent) { + static auto PMONITORFALLBACK = CConfigValue("binds:window_direction_monitor_fallback"); + + if (!*PMONITORFALLBACK) + return; // noop + + // try to find a monitor in the specified direction, thats the logical thing + if (!t || !t->space() || !t->space()->workspace()) + return; + + const auto PMONINDIR = g_pCompositor->getMonitorInDirection(t->space()->workspace()->m_monitor.lock(), dir); + + // if we found a monitor, move the window there + if (PMONINDIR && PMONINDIR != t->space()->workspace()->m_monitor.lock()) { + const auto TARGETWS = PMONINDIR->m_activeWorkspace; + + if (t->window()) + t->window()->setAnimationsToMove(); + + t->assignToSpace(TARGETWS->m_space, focalPointForDir(t, dir)); + } +} + +void CMonocleAlgorithm::cycleNext() { + if (m_targetDatas.empty()) + return; + + m_currentVisibleIndex = (m_currentVisibleIndex + 1) % m_targetDatas.size(); + updateVisible(); +} + +void CMonocleAlgorithm::cyclePrev() { + if (m_targetDatas.empty()) + return; + + m_currentVisibleIndex--; + if (m_currentVisibleIndex < 0) + m_currentVisibleIndex = m_targetDatas.size() - 1; + updateVisible(); +} + +void CMonocleAlgorithm::focusTargetUpdate(SP target) { + auto it = std::ranges::find_if(m_targetDatas, [target](const auto& data) { return data->target.lock() == target; }); + + if (it == m_targetDatas.end()) + return; + + const auto NEW_INDEX = std::distance(m_targetDatas.begin(), it); + + if (m_currentVisibleIndex != NEW_INDEX) { + m_currentVisibleIndex = NEW_INDEX; + updateVisible(); + } +} + +void CMonocleAlgorithm::updateVisible() { + recalculate(); + + const auto VISIBLE_TARGET = getVisibleTarget(); + if (!VISIBLE_TARGET) + return; + + const auto WINDOW = VISIBLE_TARGET->window(); + if (!WINDOW) + return; + + Desktop::focusState()->fullWindowFocus(WINDOW, Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE); +} + +SP CMonocleAlgorithm::getVisibleTarget() { + if (m_currentVisibleIndex < 0 || m_currentVisibleIndex >= (int)m_targetDatas.size()) + return nullptr; + + return m_targetDatas[m_currentVisibleIndex]->target.lock(); +} diff --git a/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.hpp b/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.hpp new file mode 100644 index 00000000..b23f85be --- /dev/null +++ b/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include "../../TiledAlgorithm.hpp" +#include "../../../../helpers/signal/Signal.hpp" + +#include + +namespace Layout::Tiled { + + struct SMonocleTargetData { + SMonocleTargetData(SP t) : target(t) { + ; + } + + WP target; + CBox layoutBox; + }; + + class CMonocleAlgorithm : public ITiledAlgorithm { + public: + CMonocleAlgorithm(); + virtual ~CMonocleAlgorithm(); + + virtual void newTarget(SP target); + virtual void movedTarget(SP target, std::optional focalPoint = std::nullopt); + virtual void removeTarget(SP target); + + virtual void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); + virtual void recalculate(); + + virtual SP getNextCandidate(SP old); + + virtual std::expected layoutMsg(const std::string_view& sv); + virtual std::optional predictSizeForNewTarget(); + + virtual void swapTargets(SP a, SP b); + virtual void moveTargetInDirection(SP t, Math::eDirection dir, bool silent); + + private: + std::vector> m_targetDatas; + CHyprSignalListener m_focusCallback; + + int m_currentVisibleIndex = 0; + + SP dataFor(SP t); + void cycleNext(); + void cyclePrev(); + void focusTargetUpdate(SP target); + void updateVisible(); + SP getVisibleTarget(); + }; +}; diff --git a/src/layout/algorithm/tiled/scrolling/ScrollTapeController.cpp b/src/layout/algorithm/tiled/scrolling/ScrollTapeController.cpp new file mode 100644 index 00000000..93a7dac1 --- /dev/null +++ b/src/layout/algorithm/tiled/scrolling/ScrollTapeController.cpp @@ -0,0 +1,298 @@ +#include "ScrollTapeController.hpp" +#include "ScrollingAlgorithm.hpp" +#include +#include + +using namespace Layout::Tiled; + +CScrollTapeController::CScrollTapeController(eScrollDirection direction) : m_direction(direction) { + ; +} + +void CScrollTapeController::setDirection(eScrollDirection dir) { + m_direction = dir; +} + +eScrollDirection CScrollTapeController::getDirection() const { + return m_direction; +} + +bool CScrollTapeController::isPrimaryHorizontal() const { + return m_direction == SCROLL_DIR_RIGHT || m_direction == SCROLL_DIR_LEFT; +} + +bool CScrollTapeController::isReversed() const { + return m_direction == SCROLL_DIR_LEFT || m_direction == SCROLL_DIR_UP; +} + +size_t CScrollTapeController::stripCount() const { + return m_strips.size(); +} + +SStripData& CScrollTapeController::getStrip(size_t index) { + return m_strips[index]; +} + +const SStripData& CScrollTapeController::getStrip(size_t index) const { + return m_strips[index]; +} + +void CScrollTapeController::setOffset(double offset) { + m_offset = offset; +} + +double CScrollTapeController::getOffset() const { + return m_offset; +} + +void CScrollTapeController::adjustOffset(double delta) { + m_offset += delta; +} + +size_t CScrollTapeController::addStrip(float size) { + m_strips.emplace_back(); + m_strips.back().size = size; + return m_strips.size() - 1; +} + +void CScrollTapeController::insertStrip(ssize_t afterIndex, float size) { + if (afterIndex >= sc(m_strips.size())) { + addStrip(size); + return; + } + + afterIndex = std::clamp(afterIndex, sc(-1L), sc(INT32_MAX)); + + SStripData newStrip; + newStrip.size = size; + m_strips.insert(m_strips.begin() + afterIndex + 1, newStrip); +} + +void CScrollTapeController::removeStrip(size_t index) { + if (index < m_strips.size()) + m_strips.erase(m_strips.begin() + index); +} + +double CScrollTapeController::getPrimary(const Vector2D& v) const { + return isPrimaryHorizontal() ? v.x : v.y; +} + +double CScrollTapeController::getSecondary(const Vector2D& v) const { + return isPrimaryHorizontal() ? v.y : v.x; +} + +void CScrollTapeController::setPrimary(Vector2D& v, double val) const { + if (isPrimaryHorizontal()) + v.x = val; + else + v.y = val; +} + +void CScrollTapeController::setSecondary(Vector2D& v, double val) const { + if (isPrimaryHorizontal()) + v.y = val; + else + v.x = val; +} + +Vector2D CScrollTapeController::makeVector(double primary, double secondary) const { + if (isPrimaryHorizontal()) + return {primary, secondary}; + else + return {secondary, primary}; +} + +double CScrollTapeController::calculateMaxExtent(const CBox& usableArea, bool fullscreenOnOne) const { + if (m_strips.empty()) + return 0.0; + + if (fullscreenOnOne && m_strips.size() == 1) + return getPrimary(usableArea.size()); + + double total = 0.0; + const double usablePrimary = getPrimary(usableArea.size()); + + for (const auto& strip : m_strips) { + total += usablePrimary * strip.size; + } + + return total; +} + +double CScrollTapeController::calculateStripStart(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) const { + if (stripIndex >= m_strips.size()) + return 0.0; + + const double usablePrimary = getPrimary(usableArea.size()); + double current = 0.0; + + for (size_t i = 0; i < stripIndex; ++i) { + const double stripSize = (fullscreenOnOne && m_strips.size() == 1) ? usablePrimary : usablePrimary * m_strips[i].size; + current += stripSize; + } + + return current; +} + +double CScrollTapeController::calculateStripSize(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) const { + if (stripIndex >= m_strips.size()) + return 0.0; + + const double usablePrimary = getPrimary(usableArea.size()); + + if (fullscreenOnOne && m_strips.size() == 1) + return usablePrimary; + + return usablePrimary * m_strips[stripIndex].size; +} + +CBox CScrollTapeController::calculateTargetBox(size_t stripIndex, size_t targetIndex, const CBox& usableArea, const Vector2D& workspaceOffset, bool fullscreenOnOne) { + if (stripIndex >= m_strips.size()) + return {}; + + const auto& strip = m_strips[stripIndex]; + if (targetIndex >= strip.targetSizes.size()) + return {}; + + const double usableSecondary = getSecondary(usableArea.size()); + const double usablePrimary = getPrimary(usableArea.size()); + const double cameraOffset = calculateCameraOffset(usableArea, fullscreenOnOne); + + // calculate position along primary axis (strip position) + double primaryPos = calculateStripStart(stripIndex, usableArea, fullscreenOnOne); + double primarySize = calculateStripSize(stripIndex, usableArea, fullscreenOnOne); + + // calculate position along secondary axis (within strip) + double secondaryPos = 0.0; + for (size_t i = 0; i < targetIndex; ++i) { + secondaryPos += strip.targetSizes[i] * usableSecondary; + } + double secondarySize = strip.targetSizes[targetIndex] * usableSecondary; + + // apply camera offset based on direction + // for RIGHT/DOWN: scroll offset moves content left/up (subtract) + // for LEFT/UP: scroll offset moves content right/down (different coordinate system) + if (m_direction == SCROLL_DIR_LEFT) { + // LEFT: flip the entire primary axis, then apply offset + primaryPos = usablePrimary - primaryPos - primarySize + cameraOffset; + } else if (m_direction == SCROLL_DIR_UP) { + // UP: flip the entire primary axis, then apply offset + primaryPos = usablePrimary - primaryPos - primarySize + cameraOffset; + } else { + // RIGHT/DOWN: normal offset + primaryPos -= cameraOffset; + } + + // create the box in primary/secondary coordinates + Vector2D pos = makeVector(primaryPos, secondaryPos); + Vector2D size = makeVector(primarySize, secondarySize); + + // translate to workspace position + pos = pos + workspaceOffset; + + return CBox{pos, size}; +} + +double CScrollTapeController::calculateCameraOffset(const CBox& usableArea, bool fullscreenOnOne) { + const double maxExtent = calculateMaxExtent(usableArea, fullscreenOnOne); + const double usablePrimary = getPrimary(usableArea.size()); + + // don't adjust the offset if we are dragging + if (isBeingDragged()) + return m_offset; + + // if the content fits in viewport, center it + if (maxExtent < usablePrimary) + m_offset = std::round((maxExtent - usablePrimary) / 2.0); + + // if the offset is negative but we already extended, reset offset to 0 + if (maxExtent > usablePrimary && m_offset < 0.0) + m_offset = 0.0; + + return m_offset; +} + +Vector2D CScrollTapeController::getCameraTranslation(const CBox& usableArea, bool fullscreenOnOne) { + const double offset = calculateCameraOffset(usableArea, fullscreenOnOne); + + if (isReversed()) + return makeVector(offset, 0.0); + else + return makeVector(-offset, 0.0); +} + +void CScrollTapeController::centerStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) { + if (stripIndex >= m_strips.size()) + return; + + const double usablePrimary = getPrimary(usableArea.size()); + const double stripStart = calculateStripStart(stripIndex, usableArea, fullscreenOnOne); + const double stripSize = calculateStripSize(stripIndex, usableArea, fullscreenOnOne); + + m_offset = stripStart - (usablePrimary - stripSize) / 2.0; +} + +void CScrollTapeController::fitStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) { + if (stripIndex >= m_strips.size()) + return; + + const double usablePrimary = getPrimary(usableArea.size()); + const double stripStart = calculateStripStart(stripIndex, usableArea, fullscreenOnOne); + const double stripSize = calculateStripSize(stripIndex, usableArea, fullscreenOnOne); + + m_offset = std::clamp(m_offset, stripStart - usablePrimary + stripSize, stripStart); +} + +bool CScrollTapeController::isStripVisible(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne, bool full) const { + if (stripIndex >= m_strips.size()) + return false; + + const double stripStart = calculateStripStart(stripIndex, usableArea, fullscreenOnOne); + const double stripEnd = stripStart + calculateStripSize(stripIndex, usableArea, fullscreenOnOne); + const double viewStart = m_offset; + const double viewEnd = m_offset + getPrimary(usableArea.size()); + + if (!full) + return stripStart < viewEnd && viewStart < stripEnd; + else + return stripStart >= viewStart && stripEnd <= viewEnd; +} + +size_t CScrollTapeController::getStripAtCenter(const CBox& usableArea, bool fullscreenOnOne) const { + if (m_strips.empty()) + return 0; + + const double usablePrimary = getPrimary(usableArea.size()); + double currentPos = m_offset; + + for (size_t i = 0; i < m_strips.size(); ++i) { + const double stripSize = calculateStripSize(i, usableArea, fullscreenOnOne); + currentPos += stripSize; + + if (currentPos >= usablePrimary / 2.0 - 2.0) + return i; + } + + return m_strips.empty() ? 0 : m_strips.size() - 1; +} + +void CScrollTapeController::swapStrips(size_t a, size_t b) { + if (a >= m_strips.size() || b >= m_strips.size()) + return; + + std::swap(m_strips.at(a), m_strips.at(b)); +} + +bool CScrollTapeController::isBeingDragged() const { + for (const auto& s : m_strips) { + if (!s.userData) + continue; + + for (const auto& d : s.userData->targetDatas) { + if (d->target == g_layoutManager->dragController()->target()) + return true; + } + } + + return false; +} diff --git a/src/layout/algorithm/tiled/scrolling/ScrollTapeController.hpp b/src/layout/algorithm/tiled/scrolling/ScrollTapeController.hpp new file mode 100644 index 00000000..da2efbba --- /dev/null +++ b/src/layout/algorithm/tiled/scrolling/ScrollTapeController.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include "../../../../helpers/math/Math.hpp" +#include "../../../../helpers/memory/Memory.hpp" +#include + +namespace Layout::Tiled { + + struct SColumnData; + + enum eScrollDirection : uint8_t { + SCROLL_DIR_RIGHT = 0, + SCROLL_DIR_LEFT, + SCROLL_DIR_DOWN, + SCROLL_DIR_UP, + }; + + struct SStripData { + float size = 1.F; // size along primary axis + std::vector targetSizes; // sizes along secondary axis for each target in this strip + WP userData; + + SStripData() = default; + }; + + struct STapeLayoutResult { + CBox box; + size_t stripIndex = 0; + size_t targetIndex = 0; + }; + + class CScrollTapeController { + public: + CScrollTapeController(eScrollDirection direction = SCROLL_DIR_RIGHT); + ~CScrollTapeController() = default; + + void setDirection(eScrollDirection dir); + eScrollDirection getDirection() const; + bool isPrimaryHorizontal() const; + bool isReversed() const; + + size_t addStrip(float size = 1.0F); + void insertStrip(ssize_t afterIndex, float size = 1.0F); + void removeStrip(size_t index); + size_t stripCount() const; + SStripData& getStrip(size_t index); + const SStripData& getStrip(size_t index) const; + void swapStrips(size_t a, size_t b); + + void setOffset(double offset); + double getOffset() const; + void adjustOffset(double delta); + + double calculateMaxExtent(const CBox& usableArea, bool fullscreenOnOne = false) const; + double calculateStripStart(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false) const; + double calculateStripSize(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false) const; + + CBox calculateTargetBox(size_t stripIndex, size_t targetIndex, const CBox& usableArea, const Vector2D& workspaceOffset, bool fullscreenOnOne = false); + + double calculateCameraOffset(const CBox& usableArea, bool fullscreenOnOne = false); + Vector2D getCameraTranslation(const CBox& usableArea, bool fullscreenOnOne = false); + + void centerStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false); + void fitStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false); + + bool isStripVisible(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false, bool full = false) const; + + size_t getStripAtCenter(const CBox& usableArea, bool fullscreenOnOne = false) const; + + private: + eScrollDirection m_direction = SCROLL_DIR_RIGHT; + std::vector m_strips; + double m_offset = 0.0; + + double getPrimary(const Vector2D& v) const; + double getSecondary(const Vector2D& v) const; + void setPrimary(Vector2D& v, double val) const; + void setSecondary(Vector2D& v, double val) const; + bool isBeingDragged() const; + + Vector2D makeVector(double primary, double secondary) const; + }; +}; diff --git a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp new file mode 100644 index 00000000..ae7c6ecc --- /dev/null +++ b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp @@ -0,0 +1,1518 @@ +#include "ScrollingAlgorithm.hpp" +#include "ScrollTapeController.hpp" + +#include "../../Algorithm.hpp" +#include "../../../space/Space.hpp" +#include "../../../LayoutManager.hpp" + +#include "../../../../Compositor.hpp" +#include "../../../../desktop/state/FocusState.hpp" +#include "../../../../config/ConfigValue.hpp" +#include "../../../../config/ConfigManager.hpp" +#include "../../../../render/Renderer.hpp" +#include "../../../../managers/input/InputManager.hpp" +#include "../../../../event/EventBus.hpp" + +#include +#include +#include + +using namespace Hyprutils::String; +using namespace Hyprutils::Utils; +using namespace Layout; +using namespace Layout::Tiled; + +constexpr float MIN_COLUMN_WIDTH = 0.05F; +constexpr float MAX_COLUMN_WIDTH = 1.F; +constexpr float MIN_ROW_HEIGHT = 0.1F; +constexpr float MAX_ROW_HEIGHT = 1.F; + +// +float SColumnData::getColumnWidth() const { + if (!scrollingData || !scrollingData->controller) + return 1.F; + + auto sd = scrollingData.lock(); + if (!sd) + return 1.F; + + int64_t idx = sd->idx(self.lock()); + if (idx < 0 || (size_t)idx >= sd->controller->stripCount()) + return 1.F; + + return sd->controller->getStrip(idx).size; +} + +void SColumnData::setColumnWidth(float width) { + if (!scrollingData || !scrollingData->controller) + return; + + auto sd = scrollingData.lock(); + if (!sd) + return; + + int64_t idx = sd->idx(self.lock()); + if (idx < 0 || (size_t)idx >= sd->controller->stripCount()) + return; + + sd->controller->getStrip(idx).size = width; +} + +float SColumnData::getTargetSize(size_t idx) const { + if (!scrollingData || !scrollingData->controller) + return 1.F; + + auto sd = scrollingData.lock(); + if (!sd) + return 1.F; + + int64_t colIdx = sd->idx(self.lock()); + if (colIdx < 0 || (size_t)colIdx >= sd->controller->stripCount()) + return 1.F; + + const auto& strip = sd->controller->getStrip(colIdx); + if (idx >= strip.targetSizes.size()) + return 1.F; + + return strip.targetSizes[idx]; +} + +void SColumnData::setTargetSize(size_t idx, float size) { + if (!scrollingData || !scrollingData->controller) + return; + + auto sd = scrollingData.lock(); + if (!sd) + return; + + int64_t colIdx = sd->idx(self.lock()); + if (colIdx < 0 || (size_t)colIdx >= sd->controller->stripCount()) + return; + + auto& strip = sd->controller->getStrip(colIdx); + if (idx >= strip.targetSizes.size()) + strip.targetSizes.resize(idx + 1, 1.F); + + strip.targetSizes[idx] = size; +} + +float SColumnData::getTargetSize(SP target) const { + for (size_t i = 0; i < targetDatas.size(); ++i) { + if (targetDatas[i] == target) + return getTargetSize(i); + } + return 1.F; +} + +void SColumnData::setTargetSize(SP target, float size) { + for (size_t i = 0; i < targetDatas.size(); ++i) { + if (targetDatas[i] == target) { + setTargetSize(i, size); + return; + } + } +} + +void SColumnData::add(SP t) { + const float newSize = 1.F / (float)(targetDatas.size() + 1); + + for (size_t i = 0; i < targetDatas.size(); ++i) { + setTargetSize(i, getTargetSize(i) * (float)targetDatas.size() / (float)(targetDatas.size() + 1)); + } + + targetDatas.emplace_back(makeShared(t, self.lock())); + setTargetSize(targetDatas.size() - 1, newSize); +} + +void SColumnData::add(SP t, int after) { + const float newSize = 1.F / (float)(targetDatas.size() + 1); + + for (size_t i = 0; i < targetDatas.size(); ++i) { + setTargetSize(i, getTargetSize(i) * (float)targetDatas.size() / (float)(targetDatas.size() + 1)); + } + + targetDatas.insert(targetDatas.begin() + after + 1, makeShared(t, self.lock())); + + // Sync sizes - need to insert at the right position + if (scrollingData) { + auto sd = scrollingData.lock(); + if (sd && sd->controller) { + int64_t colIdx = sd->idx(self.lock()); + if (colIdx >= 0 && (size_t)colIdx < sd->controller->stripCount()) { + auto& strip = sd->controller->getStrip(colIdx); + strip.targetSizes.insert(strip.targetSizes.begin() + after + 1, newSize); + } + } + } +} + +void SColumnData::add(SP w) { + const float newSize = 1.F / (float)(targetDatas.size() + 1); + + for (size_t i = 0; i < targetDatas.size(); ++i) { + setTargetSize(i, getTargetSize(i) * (float)targetDatas.size() / (float)(targetDatas.size() + 1)); + } + + targetDatas.emplace_back(w); + w->column = self; + setTargetSize(targetDatas.size() - 1, newSize); +} + +void SColumnData::add(SP w, int after) { + const float newSize = 1.F / (float)(targetDatas.size() + 1); + + for (size_t i = 0; i < targetDatas.size(); ++i) { + setTargetSize(i, getTargetSize(i) * (float)targetDatas.size() / (float)(targetDatas.size() + 1)); + } + + targetDatas.insert(targetDatas.begin() + after + 1, w); + w->column = self; + + // Sync sizes + if (scrollingData) { + auto sd = scrollingData.lock(); + if (sd && sd->controller) { + int64_t colIdx = sd->idx(self.lock()); + if (colIdx >= 0 && (size_t)colIdx < sd->controller->stripCount()) { + auto& strip = sd->controller->getStrip(colIdx); + strip.targetSizes.insert(strip.targetSizes.begin() + after + 1, newSize); + } + } + } +} + +size_t SColumnData::idx(SP t) { + for (size_t i = 0; i < targetDatas.size(); ++i) { + if (targetDatas[i]->target == t) + return i; + } + return 0; +} + +size_t SColumnData::idxForHeight(float y) { + if (targetDatas.empty()) + return 0; + for (size_t i = 0; i < targetDatas.size(); ++i) { + if (targetDatas[i]->target->position().y < y) + continue; + return i == 0 ? 0 : i - 1; + } + return targetDatas.size() - 1; +} + +void SColumnData::remove(SP t) { + const auto SIZE_BEFORE = targetDatas.size(); + size_t removedIdx = 0; + bool found = false; + + for (size_t i = 0; i < targetDatas.size(); ++i) { + if (targetDatas[i]->target == t) { + removedIdx = i; + found = true; + break; + } + } + + std::erase_if(targetDatas, [&t](const auto& e) { return e->target == t; }); + + if (SIZE_BEFORE == targetDatas.size() && SIZE_BEFORE > 0) + return; + + if (found && scrollingData) { + auto sd = scrollingData.lock(); + if (sd && sd->controller) { + int64_t colIdx = sd->idx(self.lock()); + if (colIdx >= 0 && (size_t)colIdx < sd->controller->stripCount()) { + auto& strip = sd->controller->getStrip(colIdx); + if (removedIdx < strip.targetSizes.size()) { + strip.targetSizes.erase(strip.targetSizes.begin() + removedIdx); + } + } + } + } + + // Renormalize sizes + float newMaxSize = 0.F; + for (size_t i = 0; i < targetDatas.size(); ++i) { + newMaxSize += getTargetSize(i); + } + + if (newMaxSize > 0.F) { + for (size_t i = 0; i < targetDatas.size(); ++i) { + setTargetSize(i, getTargetSize(i) / newMaxSize); + } + } + + if (targetDatas.empty() && scrollingData) + scrollingData->remove(self.lock()); +} + +bool SColumnData::up(SP w) { + for (size_t i = 1; i < targetDatas.size(); ++i) { + if (targetDatas[i] != w) + continue; + + std::swap(targetDatas[i], targetDatas[i - 1]); + return true; + } + + return false; +} + +bool SColumnData::down(SP w) { + for (size_t i = 0; i < targetDatas.size() - 1; ++i) { + if (targetDatas[i] != w) + continue; + + std::swap(targetDatas[i], targetDatas[i + 1]); + return true; + } + + return false; +} + +SP SColumnData::next(SP w) { + for (size_t i = 0; i < targetDatas.size() - 1; ++i) { + if (targetDatas[i] != w) + continue; + + return targetDatas[i + 1]; + } + + return nullptr; +} + +SP SColumnData::prev(SP w) { + for (size_t i = 1; i < targetDatas.size(); ++i) { + if (targetDatas[i] != w) + continue; + + return targetDatas[i - 1]; + } + + return nullptr; +} + +bool SColumnData::has(SP t) { + return std::ranges::find_if(targetDatas, [t](const auto& e) { return e->target == t; }) != targetDatas.end(); +} + +SScrollingData::SScrollingData(CScrollingAlgorithm* algo) : algorithm(algo) { + controller = makeUnique(SCROLL_DIR_RIGHT); +} + +SP SScrollingData::add() { + auto col = columns.emplace_back(makeShared(self.lock())); + col->self = col; + + size_t stripIdx = controller->addStrip(algorithm->defaultColumnWidth()); + controller->getStrip(stripIdx).userData = col; + + return col; +} + +SP SScrollingData::add(int after) { + auto col = makeShared(self.lock()); + col->self = col; + columns.insert(columns.begin() + after + 1, col); + + controller->insertStrip(after, algorithm->defaultColumnWidth()); + controller->getStrip(after + 1).userData = col; + + return col; +} + +int64_t SScrollingData::idx(SP c) { + for (size_t i = 0; i < columns.size(); ++i) { + if (columns[i] == c) + return i; + } + + return -1; +} + +void SScrollingData::remove(SP c) { + // find index before removing + int64_t index = idx(c); + + std::erase(columns, c); + + // sync with controller + if (index >= 0) + controller->removeStrip(index); +} + +SP SScrollingData::next(SP c) { + for (size_t i = 0; i < columns.size(); ++i) { + if (columns[i] != c) + continue; + + if (i == columns.size() - 1) + return nullptr; + + return columns[i + 1]; + } + + return nullptr; +} + +SP SScrollingData::prev(SP c) { + for (size_t i = 0; i < columns.size(); ++i) { + if (columns[i] != c) + continue; + + if (i == 0) + return nullptr; + + return columns[i - 1]; + } + + return nullptr; +} + +void SScrollingData::centerCol(SP c) { + if (!c) + return; + + static const auto PFSONONE = CConfigValue("scrolling:fullscreen_on_one_column"); + const auto USABLE = algorithm->usableArea(); + int64_t colIdx = idx(c); + + if (colIdx >= 0) + controller->centerStrip(colIdx, USABLE, *PFSONONE); +} + +void SScrollingData::fitCol(SP c) { + if (!c) + return; + + static const auto PFSONONE = CConfigValue("scrolling:fullscreen_on_one_column"); + const auto USABLE = algorithm->usableArea(); + int64_t colIdx = idx(c); + + if (colIdx >= 0) + controller->fitStrip(colIdx, USABLE, *PFSONONE); +} + +void SScrollingData::centerOrFitCol(SP c) { + if (!c) + return; + + static const auto PFITMETHOD = CConfigValue("scrolling:focus_fit_method"); + + if (*PFITMETHOD == 1) + fitCol(c); + else + centerCol(c); +} + +SP SScrollingData::atCenter() { + static const auto PFSONONE = CConfigValue("scrolling:fullscreen_on_one_column"); + const auto USABLE = algorithm->usableArea(); + + size_t centerIdx = controller->getStripAtCenter(USABLE, *PFSONONE); + + if (centerIdx < columns.size()) + return columns[centerIdx]; + + return nullptr; +} + +void SScrollingData::recalculate(bool forceInstant) { + if (!algorithm->m_parent->space()->workspace() || algorithm->m_parent->space()->workspace()->m_hasFullscreenWindow) + return; + + static const auto PFSONONE = CConfigValue("scrolling:fullscreen_on_one_column"); + + const CBox USABLE = algorithm->usableArea(); + const auto WORKAREA = algorithm->m_parent->space()->workArea(); + + controller->setDirection(algorithm->getDynamicDirection()); + + for (size_t i = 0; i < columns.size(); ++i) { + const auto& COL = columns[i]; + + for (size_t j = 0; j < COL->targetDatas.size(); ++j) { + const auto& TARGET = COL->targetDatas[j]; + + TARGET->layoutBox = controller->calculateTargetBox(i, j, USABLE, WORKAREA.pos(), *PFSONONE); + + if (TARGET->target) + TARGET->target->setPositionGlobal(TARGET->layoutBox); + if (forceInstant && TARGET->target) + TARGET->target->warpPositionSize(); + } + } +} + +double SScrollingData::maxWidth() { + static const auto PFSONONE = CConfigValue("scrolling:fullscreen_on_one_column"); + const auto USABLE = algorithm->usableArea(); + + return controller->calculateMaxExtent(USABLE, *PFSONONE); +} + +bool SScrollingData::visible(SP c, bool full) { + static const auto PFSONONE = CConfigValue("scrolling:fullscreen_on_one_column"); + const auto USABLE = algorithm->usableArea(); + int64_t colIdx = idx(c); + + if (colIdx >= 0) + return controller->isStripVisible(colIdx, USABLE, *PFSONONE, full); + + return false; +} + +CScrollingAlgorithm::CScrollingAlgorithm() { + static const auto PCONFWIDTHS = CConfigValue("scrolling:explicit_column_widths"); + static const auto PCONFDIRECTION = CConfigValue("scrolling:direction"); + + m_scrollingData = makeShared(this); + m_scrollingData->self = m_scrollingData; + + // Helper to parse explicit_column_widths string + auto parseColumnWidths = [](const std::string& dir) -> std::vector { + auto widthVec = std::vector(); + + CConstVarList widths(dir, 0, ','); + for (auto& w : widths) { + try { + widthVec.emplace_back(std::clamp(std::stof(std::string{w}), MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH)); + } catch (...) { Log::logger->log(Log::ERR, "scrolling: Failed to parse width {} as float", w); } + } + if (widthVec.empty()) + widthVec = {0.333, 0.5, 0.667, 1.0}; // default + return widthVec; + }; + + // Helper to parse direction string + auto parseDirection = [](const std::string& dir) -> eScrollDirection { + if (dir == "left") + return SCROLL_DIR_LEFT; + else if (dir == "down") + return SCROLL_DIR_DOWN; + else if (dir == "up") + return SCROLL_DIR_UP; + else + return SCROLL_DIR_RIGHT; // default + }; + + m_configCallback = Event::bus()->m_events.config.reloaded.listen([this, parseColumnWidths, parseDirection] { + static const auto PCONFDIRECTION = CConfigValue("scrolling:direction"); + + m_config.configuredWidths.clear(); + m_config.configuredWidths = parseColumnWidths(*PCONFWIDTHS); + + // Update scroll direction + m_scrollingData->controller->setDirection(parseDirection(*PCONFDIRECTION)); + }); + + m_mouseButtonCallback = Event::bus()->m_events.input.mouse.button.listen([this](IPointer::SButtonEvent e, Event::SCallbackInfo&) { + static const auto PFOLLOW_FOCUS = CConfigValue("scrolling:follow_focus"); + + if (*PFOLLOW_FOCUS && e.state == WL_POINTER_BUTTON_STATE_RELEASED && Desktop::focusState()->window()) + focusOnInput(Desktop::focusState()->window()->layoutTarget(), INPUT_MODE_CLICK); + }); + + m_focusCallback = Event::bus()->m_events.window.active.listen([this](PHLWINDOW pWindow, Desktop::eFocusReason reason) { + if (!pWindow) + return; + + static const auto PFOLLOW_FOCUS = CConfigValue("scrolling:follow_focus"); + + if (!*PFOLLOW_FOCUS && !Desktop::isHardInputFocusReason(reason)) + return; + + if (pWindow->m_workspace != m_parent->space()->workspace()) + return; + + const auto TARGET = pWindow->layoutTarget(); + if (!TARGET || TARGET->floating()) + return; + + focusOnInput(TARGET, reason == Desktop::FOCUS_REASON_CLICK ? INPUT_MODE_CLICK : (Desktop::isHardInputFocusReason(reason) ? INPUT_MODE_KB : INPUT_MODE_SOFT)); + }); + + // Initialize default widths and direction + m_config.configuredWidths = parseColumnWidths(*PCONFWIDTHS); + m_scrollingData->controller->setDirection(parseDirection(*PCONFDIRECTION)); +} + +CScrollingAlgorithm::~CScrollingAlgorithm() { + m_configCallback.reset(); + m_focusCallback.reset(); +} + +void CScrollingAlgorithm::focusOnInput(SP target, eInputMode input) { + static const auto PFOLLOW_FOCUS_MIN_PERC = CConfigValue("scrolling:follow_min_visible"); + + if (!target || target->space() != m_parent->space()) + return; + + const auto TARGETDATA = dataFor(target); + if (!TARGETDATA) + return; + + if (*PFOLLOW_FOCUS_MIN_PERC > 0.F && input == INPUT_MODE_SOFT) { + // check how much of the window is visible, unless hard input focus + + const auto IS_HORIZ = m_scrollingData->controller->isPrimaryHorizontal(); + + const auto MON_BOX = m_parent->space()->workspace()->m_monitor->logicalBox(); + const auto TARGET_POS = target->position(); + const double VISIBLE_LEN = IS_HORIZ ? // + std::abs(std::min(MON_BOX.x + MON_BOX.w, TARGET_POS.x + TARGET_POS.w) - (std::max(MON_BOX.x, TARGET_POS.x))) // + : + std::abs(std::min(MON_BOX.y + MON_BOX.h, TARGET_POS.y + TARGET_POS.h) - (std::max(MON_BOX.y, TARGET_POS.y))); + + // if the amount of visible X is below minimum, reject + if (VISIBLE_LEN < (IS_HORIZ ? MON_BOX.w : MON_BOX.h) * std::clamp(*PFOLLOW_FOCUS_MIN_PERC, 0.F, 1.F)) + return; + } + + // if we moved via non-kb, and it's fully visible, ignore + if (m_scrollingData->visible(TARGETDATA->column.lock(), true) && input != INPUT_MODE_KB) + return; + + static const auto PFITMETHOD = CConfigValue("scrolling:focus_fit_method"); + if (*PFITMETHOD == 1 || input == INPUT_MODE_CLICK) + m_scrollingData->fitCol(TARGETDATA->column.lock()); + else + m_scrollingData->centerCol(TARGETDATA->column.lock()); + m_scrollingData->recalculate(); +} + +void CScrollingAlgorithm::newTarget(SP target) { + auto droppingOn = Desktop::focusState()->window(); + + if (droppingOn && droppingOn->layoutTarget() == target) + droppingOn = g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(), Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS); + + SP droppingData = droppingOn ? dataFor(droppingOn->layoutTarget()) : nullptr; + SP droppingColumn = droppingData ? droppingData->column.lock() : nullptr; + + if (!droppingColumn) { + auto col = m_scrollingData->add(); + col->add(target); + m_scrollingData->fitCol(col); + } else { + if (g_layoutManager->dragController()->wasDraggingWindow() && g_layoutManager->dragController()->draggingTiled()) { + if (droppingOn) { + const auto IDX = droppingColumn->idx(droppingOn->layoutTarget()); + const auto TOP = droppingOn->getWindowIdealBoundingBoxIgnoreReserved().middle().y > g_pInputManager->getMouseCoordsInternal().y; + droppingColumn->add(target, TOP ? (IDX == 0 ? -1 : IDX - 1) : (IDX)); + } else + droppingColumn->add(target); + m_scrollingData->fitCol(droppingColumn); + } else { + auto idx = m_scrollingData->idx(droppingColumn); + auto col = idx == -1 ? m_scrollingData->add() : m_scrollingData->add(idx); + col->add(target); + m_scrollingData->fitCol(col); + } + } + + m_scrollingData->recalculate(); +} + +void CScrollingAlgorithm::movedTarget(SP target, std::optional focalPoint) { + newTarget(target); +} + +void CScrollingAlgorithm::removeTarget(SP target) { + const auto DATA = dataFor(target); + + if (!DATA) + return; + + if (!m_scrollingData->next(DATA->column.lock()) && DATA->column->targetDatas.size() <= 1) { + // move the view if this is the last column + const auto USABLE = usableArea(); + const bool isPrimaryHoriz = m_scrollingData->controller->isPrimaryHorizontal(); + const double usablePrimary = isPrimaryHoriz ? USABLE.w : USABLE.h; + m_scrollingData->controller->adjustOffset(-(usablePrimary * DATA->column->getColumnWidth())); + } + + DATA->column->remove(target); + + if (!DATA->column) { + // column got removed, let's ensure we don't leave any cringe extra space + const auto USABLE = usableArea(); + const bool isPrimaryHoriz = m_scrollingData->controller->isPrimaryHorizontal(); + const double usablePrimary = isPrimaryHoriz ? USABLE.w : USABLE.h; + const double newOffset = std::clamp(m_scrollingData->controller->getOffset(), 0.0, std::max(m_scrollingData->maxWidth() - usablePrimary, 1.0)); + m_scrollingData->controller->setOffset(newOffset); + } + + m_scrollingData->recalculate(); +} + +void CScrollingAlgorithm::resizeTarget(const Vector2D& delta, SP target, eRectCorner corner) { + if (!validMapped(target->window())) + return; + + const auto DATA = dataFor(target); + + if (!DATA) { + const auto PWINDOW = target->window(); + *PWINDOW->m_realSize = (PWINDOW->m_realSize->goal() + delta) + .clamp(PWINDOW->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}), + PWINDOW->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY})); + PWINDOW->updateWindowDecos(); + return; + } + + if (!DATA->column || !DATA->column->scrollingData) + return; + + static const auto PFSONONE = CConfigValue("scrolling:fullscreen_on_one_column"); + + const auto ADJUSTED_DELTA = m_scrollingData->controller->isPrimaryHorizontal() ? delta : Vector2D{delta.y, delta.x}; + const auto USABLE = usableArea(); + const auto DELTA_AS_PERC = ADJUSTED_DELTA / USABLE.size(); + Vector2D modDelta = ADJUSTED_DELTA; + + const auto CURR_COLUMN = DATA->column.lock(); + const int64_t COL_IDX = m_scrollingData->idx(CURR_COLUMN); + + if (COL_IDX < 0) + return; + + const double currentStart = m_scrollingData->controller->calculateStripStart(COL_IDX, USABLE, *PFSONONE); + const double currentSize = m_scrollingData->controller->calculateStripSize(COL_IDX, USABLE, *PFSONONE); + const double currentEnd = currentStart + currentSize; + + const double cameraOffset = m_scrollingData->controller->getOffset(); + const bool isPrimaryHoriz = m_scrollingData->controller->isPrimaryHorizontal(); + const double usablePrimary = isPrimaryHoriz ? USABLE.w : USABLE.h; + + const double onScreenStart = currentStart - cameraOffset; + const double onScreenEnd = currentEnd - cameraOffset; + + // set the offset because we'll prevent centering during a drag + m_scrollingData->controller->setOffset(cameraOffset); + + const bool RESIZING_LEFT = isPrimaryHoriz ? corner == CORNER_BOTTOMLEFT || corner == CORNER_TOPLEFT : corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT; + + if (RESIZING_LEFT) { + // resize from left edge (inner edge) - grow/shrink column width and adjust offset to keep RIGHT edge stationary + const float oldWidth = CURR_COLUMN->getColumnWidth(); + const float requestedDelta = -(float)DELTA_AS_PERC.x; // negative delta means grow when dragging left + float actualDelta = requestedDelta; + + // clamp delta so we don't shrink below MIN or grow above MAX + const float newWidthUnclamped = oldWidth + actualDelta; + const float newWidthClamped = std::clamp(newWidthUnclamped, MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH); + actualDelta = newWidthClamped - oldWidth; + + if (actualDelta * usablePrimary > onScreenStart) + actualDelta = onScreenStart / usablePrimary; + + if (actualDelta != 0.F) { + CURR_COLUMN->setColumnWidth(oldWidth + actualDelta); + // adjust camera offset so the RIGHT edge stays stationary on screen + // when column grows (actualDelta > 0), we need to increase offset by the same amount + m_scrollingData->controller->adjustOffset(actualDelta * usablePrimary); + } + + } else { + // resize from right edge (outer edge) - adjust column width only, keep left edge fixed + const float oldWidth = CURR_COLUMN->getColumnWidth(); + const float requestedDelta = (float)DELTA_AS_PERC.x; + float actualDelta = requestedDelta; + + // clamp delta so we don't shrink below MIN or grow above MAX + const float newWidthUnclamped = oldWidth + actualDelta; + const float newWidthClamped = std::clamp(newWidthUnclamped, MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH); + actualDelta = newWidthClamped - oldWidth; + + // also clamp so right edge doesn't go past right viewport boundary + if (onScreenEnd + (actualDelta * usablePrimary) > usablePrimary) + actualDelta = (usablePrimary - onScreenEnd) / usablePrimary; + + if (actualDelta != 0.F) + CURR_COLUMN->setColumnWidth(oldWidth + actualDelta); + } + + if (DATA->column->targetDatas.size() > 1) { + const auto& CURR_TD = DATA; + const auto NEXT_TD = DATA->column->next(DATA); + const auto PREV_TD = DATA->column->prev(DATA); + if (corner == CORNER_NONE) { + if (!PREV_TD) + corner = CORNER_BOTTOMRIGHT; + else { + corner = CORNER_TOPRIGHT; + modDelta.y *= -1.0f; + } + } + + switch (corner) { + case CORNER_BOTTOMLEFT: + case CORNER_BOTTOMRIGHT: { + if (!NEXT_TD) + break; + + float nextSize = CURR_COLUMN->getTargetSize(NEXT_TD); + float currSize = CURR_COLUMN->getTargetSize(CURR_TD); + + if (nextSize <= MIN_ROW_HEIGHT && delta.y >= 0) + break; + + float adjust = std::clamp((float)(delta.y / USABLE.h), (-currSize + MIN_ROW_HEIGHT), (nextSize - MIN_ROW_HEIGHT)); + + CURR_COLUMN->setTargetSize(NEXT_TD, std::clamp(nextSize - adjust, MIN_ROW_HEIGHT, MAX_ROW_HEIGHT)); + CURR_COLUMN->setTargetSize(CURR_TD, std::clamp(currSize + adjust, MIN_ROW_HEIGHT, MAX_ROW_HEIGHT)); + break; + } + case CORNER_TOPLEFT: + case CORNER_TOPRIGHT: { + if (!PREV_TD) + break; + + float prevSize = CURR_COLUMN->getTargetSize(PREV_TD); + float currSize = CURR_COLUMN->getTargetSize(CURR_TD); + + if ((prevSize <= MIN_ROW_HEIGHT && modDelta.y <= 0) || (currSize <= MIN_ROW_HEIGHT && delta.y >= 0)) + break; + + float adjust = std::clamp((float)(modDelta.y / USABLE.h), -(prevSize - MIN_ROW_HEIGHT), (currSize - MIN_ROW_HEIGHT)); + + CURR_COLUMN->setTargetSize(PREV_TD, std::clamp(prevSize + adjust, MIN_ROW_HEIGHT, MAX_ROW_HEIGHT)); + CURR_COLUMN->setTargetSize(CURR_TD, std::clamp(currSize - adjust, MIN_ROW_HEIGHT, MAX_ROW_HEIGHT)); + break; + } + + default: break; + } + } + + m_scrollingData->recalculate(true); +} + +void CScrollingAlgorithm::recalculate() { + if (Desktop::focusState()->window()) { + const auto TARGET = Desktop::focusState()->window()->layoutTarget(); + + const auto TARGETDATA = dataFor(TARGET); + + if (TARGETDATA && !m_scrollingData->visible(TARGETDATA->column.lock(), true)) + focusOnInput(Desktop::focusState()->window()->layoutTarget(), INPUT_MODE_KB); + } + + m_scrollingData->recalculate(); +} + +SP CScrollingAlgorithm::closestNode(const Vector2D& posGlobglobgabgalab) { + SP res = nullptr; + double distClosest = -1; + for (auto& c : m_scrollingData->columns) { + for (auto& n : c->targetDatas) { + if (n->target && Desktop::View::validMapped(n->target->window())) { + auto distAnother = vecToRectDistanceSquared(posGlobglobgabgalab, n->layoutBox.pos(), n->layoutBox.pos() + n->layoutBox.size()); + if (!res || distAnother < distClosest) { + res = n; + distClosest = distAnother; + } + } + } + } + return res; +} + +SP CScrollingAlgorithm::getNextCandidate(SP old) { + const auto CENTER = old->position().middle(); + + const auto NODE = closestNode(CENTER); + + if (!NODE) + return nullptr; + + return NODE->target.lock(); +} + +void CScrollingAlgorithm::swapTargets(SP a, SP b) { + auto nodeA = dataFor(a); + auto nodeB = dataFor(b); + + if (nodeA) + nodeA->target = b; + if (nodeB) + nodeB->target = a; + + m_scrollingData->recalculate(); +} + +void CScrollingAlgorithm::moveTargetInDirection(SP t, Math::eDirection dir, bool silent) { + moveTargetTo(t, dir, silent); +} + +void CScrollingAlgorithm::moveTargetTo(SP t, Math::eDirection dir, bool silent) { + static auto PMONITORFALLBACK = CConfigValue("binds:window_direction_monitor_fallback"); + + const auto DATA = dataFor(t); + + if (!DATA) + return; + + const auto CURRENT_COL = DATA->column.lock(); + const auto current_idx = m_scrollingData->idx(CURRENT_COL); + + auto rotateDir = [this](Math::eDirection dir) -> Math::eDirection { + switch (m_scrollingData->controller->getDirection()) { + case SCROLL_DIR_RIGHT: return dir; + case SCROLL_DIR_LEFT: { + if (dir == Math::DIRECTION_LEFT) + return Math::DIRECTION_RIGHT; + if (dir == Math::DIRECTION_RIGHT) + return Math::DIRECTION_LEFT; + return dir; + } + case SCROLL_DIR_UP: { + switch (dir) { + case Math::DIRECTION_UP: return Math::DIRECTION_RIGHT; + case Math::DIRECTION_DOWN: return Math::DIRECTION_LEFT; + case Math::DIRECTION_LEFT: return Math::DIRECTION_DOWN; + case Math::DIRECTION_RIGHT: return Math::DIRECTION_UP; + default: break; + } + + return dir; + } + case SCROLL_DIR_DOWN: { + switch (dir) { + case Math::DIRECTION_UP: return Math::DIRECTION_LEFT; + case Math::DIRECTION_DOWN: return Math::DIRECTION_RIGHT; + case Math::DIRECTION_LEFT: return Math::DIRECTION_DOWN; + case Math::DIRECTION_RIGHT: return Math::DIRECTION_UP; + default: break; + } + + return dir; + } + default: break; + } + + return dir; + }; + + const auto ROTATED_DIR = rotateDir(dir); + + auto commenceDir = [&]() -> bool { + if (ROTATED_DIR == Math::DIRECTION_LEFT) { + const auto COL = m_scrollingData->prev(DATA->column.lock()); + + // ignore moves to the origin if we are alone + if (!COL && current_idx == 0 && DATA->column->targetDatas.size() == 1) + return false; + + DATA->column->remove(t); + + if (!COL) { + const auto NEWCOL = m_scrollingData->add(-1); + NEWCOL->add(DATA); + m_scrollingData->centerOrFitCol(NEWCOL); + } else { + if (COL->targetDatas.size() > 0) + COL->add(DATA, COL->idxForHeight(g_pInputManager->getMouseCoordsInternal().y)); + else + COL->add(DATA); + m_scrollingData->centerOrFitCol(COL); + } + + return true; + } else if (ROTATED_DIR == Math::DIRECTION_RIGHT) { + const auto COL = m_scrollingData->next(DATA->column.lock()); + + // ignore move to the right when there is no next column and we're alone + if (!COL && current_idx == (int64_t)m_scrollingData->columns.size() - 1 && DATA->column->targetDatas.size() == 1) + return false; + + DATA->column->remove(t); + + if (!COL) { + // make a new one + const auto NEWCOL = m_scrollingData->add(); + NEWCOL->add(DATA); + m_scrollingData->centerOrFitCol(NEWCOL); + } else { + if (COL->targetDatas.size() > 0) + COL->add(DATA, COL->idxForHeight(g_pInputManager->getMouseCoordsInternal().y)); + else + COL->add(DATA); + m_scrollingData->centerOrFitCol(COL); + } + + return true; + } else if (ROTATED_DIR == Math::DIRECTION_UP) + return DATA->column->up(DATA); + else if (ROTATED_DIR == Math::DIRECTION_DOWN) + return DATA->column->down(DATA); + + return false; + }; + + if (!commenceDir()) { + // dir wasn't commenced, move to a workspace if possible + // with the original dir + + if (!*PMONITORFALLBACK) + return; // noop + + const auto MONINDIR = g_pCompositor->getMonitorInDirection(m_parent->space()->workspace()->m_monitor.lock(), dir); + if (MONINDIR && MONINDIR != m_parent->space()->workspace()->m_monitor && MONINDIR->m_activeWorkspace) { + t->assignToSpace(MONINDIR->m_activeWorkspace->m_space, focalPointForDir(t, dir)); + + m_scrollingData->recalculate(); + + return; + } + } + + m_scrollingData->recalculate(); + focusTargetUpdate(t); +} + +std::expected CScrollingAlgorithm::layoutMsg(const std::string_view& sv) { + auto centerOrFit = [this](const SP COL) -> void { + static const auto PFITMETHOD = CConfigValue("scrolling:focus_fit_method"); + if (*PFITMETHOD == 1) + m_scrollingData->fitCol(COL); + else + m_scrollingData->centerCol(COL); + }; + + const auto ARGS = CVarList(std::string{sv}, 0, ' '); + if (ARGS[0] == "move") { + if (ARGS[1] == "+col" || ARGS[1] == "col") { + const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr); + if (!TDATA) + return std::unexpected("no window"); + + const auto COL = m_scrollingData->next(TDATA->column.lock()); + if (!COL) { + // move to max + double maxOffset = m_scrollingData->maxWidth(); + m_scrollingData->controller->setOffset(maxOffset); + m_scrollingData->recalculate(); + focusTargetUpdate(nullptr); + return {}; + } + + centerOrFit(COL); + m_scrollingData->recalculate(); + + focusTargetUpdate(COL->targetDatas.front()->target.lock()); + if (COL->targetDatas.front()->target->window()) + g_pCompositor->warpCursorTo(COL->targetDatas.front()->target->window()->middle()); + + return {}; + } else if (ARGS[1] == "-col") { + const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr); + if (!TDATA) { + if (m_scrollingData->columns.size() > 0) { + m_scrollingData->centerCol(m_scrollingData->columns.back()); + m_scrollingData->recalculate(); + focusTargetUpdate((m_scrollingData->columns.back()->targetDatas.back())->target.lock()); + if (m_scrollingData->columns.back()->targetDatas.back()->target->window()) + g_pCompositor->warpCursorTo((m_scrollingData->columns.back()->targetDatas.back())->target->window()->middle()); + } + + return {}; + } + + const auto COL = m_scrollingData->prev(TDATA->column.lock()); + if (!COL) + return {}; + + centerOrFit(COL); + m_scrollingData->recalculate(); + + focusTargetUpdate(COL->targetDatas.back()->target.lock()); + if (COL->targetDatas.front()->target->window()) + g_pCompositor->warpCursorTo(COL->targetDatas.front()->target->window()->middle()); + + return {}; + } + + const auto PLUSMINUS = getPlusMinusKeywordResult(ARGS[1], 0); + + if (!PLUSMINUS.has_value()) + return std::unexpected("failed to parse offset"); + + m_scrollingData->controller->adjustOffset(-(*PLUSMINUS)); + m_scrollingData->recalculate(); + + const auto ATCENTER = m_scrollingData->atCenter(); + + focusTargetUpdate(ATCENTER ? (*ATCENTER->targetDatas.begin())->target.lock() : nullptr); + } else if (ARGS[0] == "colresize") { + const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr); + + if (!TDATA) + return {}; + + if (ARGS[1] == "all") { + float abs = 0; + try { + abs = std::stof(ARGS[2]); + } catch (...) { return {}; } + + for (const auto& c : m_scrollingData->columns) { + c->setColumnWidth(abs); + } + + m_scrollingData->recalculate(); + return {}; + } + + CScopeGuard x([this, TDATA] { + auto col = TDATA->column.lock(); + if (col) { + col->setColumnWidth(std::clamp(col->getColumnWidth(), MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH)); + m_scrollingData->centerOrFitCol(col); + } + m_scrollingData->recalculate(); + }); + + if (ARGS[1][0] == '+' || ARGS[1][0] == '-') { + if (ARGS[1] == "+conf") { + auto col = TDATA->column.lock(); + if (col) { + for (size_t i = 0; i < m_config.configuredWidths.size(); ++i) { + if (m_config.configuredWidths[i] > col->getColumnWidth()) { + col->setColumnWidth(m_config.configuredWidths[i]); + break; + } + + if (i == m_config.configuredWidths.size() - 1) + col->setColumnWidth(m_config.configuredWidths[0]); + } + } + + return {}; + } else if (ARGS[1] == "-conf") { + auto col = TDATA->column.lock(); + if (col) { + for (size_t i = m_config.configuredWidths.size() - 1;; --i) { + if (m_config.configuredWidths[i] < col->getColumnWidth()) { + col->setColumnWidth(m_config.configuredWidths[i]); + break; + } + + if (i == 0) { + col->setColumnWidth(m_config.configuredWidths.back()); + break; + } + } + } + + return {}; + } + + const auto PLUSMINUS = getPlusMinusKeywordResult(ARGS[1], 0); + + if (!PLUSMINUS.has_value()) + return {}; + + auto col = TDATA->column.lock(); + if (col) + col->setColumnWidth(col->getColumnWidth() + *PLUSMINUS); + } else { + float abs = 0; + try { + abs = std::stof(ARGS[1]); + } catch (...) { return {}; } + + auto col = TDATA->column.lock(); + if (col) + col->setColumnWidth(abs); + } + } else if (ARGS[0] == "fit") { + const auto PWINDOW = Desktop::focusState()->window(); + + if (!PWINDOW) + return std::unexpected("no focused window"); + + const auto WDATA = dataFor(PWINDOW->layoutTarget()); + + if (!WDATA || m_scrollingData->columns.size() == 0) + return std::unexpected("can't fit: no window or columns"); + + if (ARGS[1] == "active") { + // fit the current column to 1.F + const auto USABLE = usableArea(); + + WDATA->column->setColumnWidth(1.F); + + double off = 0.F; + for (size_t i = 0; i < m_scrollingData->columns.size(); ++i) { + if (m_scrollingData->columns[i]->has(PWINDOW->layoutTarget())) + break; + + off += USABLE.w * m_scrollingData->columns[i]->getColumnWidth(); + } + + m_scrollingData->controller->setOffset(off); + m_scrollingData->recalculate(); + } else if (ARGS[1] == "all") { + // fit all columns on screen + const size_t LEN = m_scrollingData->columns.size(); + for (const auto& c : m_scrollingData->columns) { + c->setColumnWidth(1.F / (float)LEN); + } + + m_scrollingData->controller->setOffset(0); + m_scrollingData->recalculate(); + } else if (ARGS[1] == "toend") { + // fit all columns on screen that start from the current and end on the last + bool begun = false; + size_t foundAt = 0; + for (size_t i = 0; i < m_scrollingData->columns.size(); ++i) { + if (!begun && !m_scrollingData->columns[i]->has(PWINDOW->layoutTarget())) + continue; + + if (!begun) { + begun = true; + foundAt = i; + } + + m_scrollingData->columns[i]->setColumnWidth(1.F / (float)(m_scrollingData->columns.size() - foundAt)); + } + + if (!begun) + return std::unexpected("couldn't find beginning"); + + const auto USABLE = usableArea(); + + double off = 0; + for (size_t i = 0; i < foundAt; ++i) { + off += USABLE.w * m_scrollingData->columns[i]->getColumnWidth(); + } + + m_scrollingData->controller->setOffset(off); + m_scrollingData->recalculate(); + } else if (ARGS[1] == "tobeg") { + // fit all columns on screen that start from the current and end on the last + bool begun = false; + size_t foundAt = 0; + for (int64_t i = (int64_t)m_scrollingData->columns.size() - 1; i >= 0; --i) { + if (!begun && !m_scrollingData->columns[i]->has(PWINDOW->layoutTarget())) + continue; + + if (!begun) { + begun = true; + foundAt = i; + } + + m_scrollingData->columns[i]->setColumnWidth(1.F / (float)(foundAt + 1)); + } + + if (!begun) + return {}; + + m_scrollingData->controller->setOffset(0); + m_scrollingData->recalculate(); + } else if (ARGS[1] == "visible") { + // fit all columns on screen that start from the current and end on the last + + bool begun = false; + size_t foundAt = 0; + std::vector> visible; + for (size_t i = 0; i < m_scrollingData->columns.size(); ++i) { + if (!begun && !m_scrollingData->visible(m_scrollingData->columns[i])) + continue; + + if (!begun) { + begun = true; + foundAt = i; + } + + if (!m_scrollingData->visible(m_scrollingData->columns[i])) + break; + + visible.emplace_back(m_scrollingData->columns[i]); + } + + if (!begun) + return {}; + + double off = 0; + + if (foundAt != 0) { + const auto USABLE = usableArea(); + + for (size_t i = 0; i < foundAt; ++i) { + off += USABLE.w * m_scrollingData->columns[i]->getColumnWidth(); + } + } + + for (const auto& v : visible) { + v->setColumnWidth(1.F / (float)visible.size()); + } + + m_scrollingData->controller->setOffset(off); + m_scrollingData->recalculate(); + } + } else if (ARGS[0] == "focus") { + const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr); + static const auto PNOFALLBACK = CConfigValue("general:no_focus_fallback"); + static const auto PCONFWRAPFOCUS = CConfigValue("scrolling:wrap_focus"); + + if (!TDATA || ARGS[1].empty()) + return std::unexpected("no window to focus"); + + // Determine if we're in vertical scroll mode (strips are horizontal) + const bool isVerticalScroll = (getDynamicDirection() == SCROLL_DIR_DOWN || getDynamicDirection() == SCROLL_DIR_UP); + + // Map direction keys based on scroll mode: + // Horizontal scroll (RIGHT/LEFT): u/d move within strip, l/r move between strips + // Vertical scroll (DOWN/UP): l/r move within strip, u/d move between strips + char dirChar = ARGS[1][0]; + + // Convert to semantic directions + bool isPrevInStrip = (!isVerticalScroll && (dirChar == 'u' || dirChar == 't')) || (isVerticalScroll && dirChar == 'l'); + bool isNextInStrip = (!isVerticalScroll && (dirChar == 'b' || dirChar == 'd')) || (isVerticalScroll && dirChar == 'r'); + bool isPrevStrip = (!isVerticalScroll && dirChar == 'l') || (isVerticalScroll && (dirChar == 'u' || dirChar == 't')); + bool isNextStrip = (!isVerticalScroll && dirChar == 'r') || (isVerticalScroll && (dirChar == 'b' || dirChar == 'd')); + + if (isPrevInStrip) { + // Move to previous target within current strip + auto PREV = TDATA->column->prev(TDATA); + if (!PREV) { + if (!*PNOFALLBACK) + PREV = TDATA->column->targetDatas.back(); + else + return std::unexpected("fallback disabled (no target)"); + } + + focusTargetUpdate(PREV->target.lock()); + if (PREV->target->window()) + g_pCompositor->warpCursorTo(PREV->target->window()->middle()); + } else if (isNextInStrip) { + // Move to next target within current strip + auto NEXT = TDATA->column->next(TDATA); + if (!NEXT) { + if (!*PNOFALLBACK) + NEXT = TDATA->column->targetDatas.front(); + else + return std::unexpected("fallback disabled (no target)"); + } + + focusTargetUpdate(NEXT->target.lock()); + if (NEXT->target->window()) + g_pCompositor->warpCursorTo(NEXT->target->window()->middle()); + } else if (isPrevStrip) { + // Move to previous strip + auto PREV = m_scrollingData->prev(TDATA->column.lock()); + if (!PREV) { + if (*PNOFALLBACK) { + centerOrFit(TDATA->column.lock()); + m_scrollingData->recalculate(); + if (TDATA->target->window()) + g_pCompositor->warpCursorTo(TDATA->target->window()->middle()); + return {}; + } else + PREV = (*PCONFWRAPFOCUS == 1) ? m_scrollingData->columns.back() : m_scrollingData->columns.front(); + } + + auto pTargetData = findBestNeighbor(TDATA, PREV); + if (pTargetData) { + focusTargetUpdate(pTargetData->target.lock()); + centerOrFit(PREV); + m_scrollingData->recalculate(); + if (pTargetData->target->window()) + g_pCompositor->warpCursorTo(pTargetData->target->window()->middle()); + } + } else if (isNextStrip) { + // Move to next strip + auto NEXT = m_scrollingData->next(TDATA->column.lock()); + if (!NEXT) { + if (*PNOFALLBACK) { + centerOrFit(TDATA->column.lock()); + m_scrollingData->recalculate(); + if (TDATA->target->window()) + g_pCompositor->warpCursorTo(TDATA->target->window()->middle()); + return {}; + } else + NEXT = (*PCONFWRAPFOCUS == 1) ? m_scrollingData->columns.front() : m_scrollingData->columns.back(); + } + + auto pTargetData = findBestNeighbor(TDATA, NEXT); + if (pTargetData) { + focusTargetUpdate(pTargetData->target.lock()); + centerOrFit(NEXT); + m_scrollingData->recalculate(); + if (pTargetData->target->window()) + g_pCompositor->warpCursorTo(pTargetData->target->window()->middle()); + } + } + } else if (ARGS[0] == "promote") { + const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr); + + if (!TDATA) + return std::unexpected("no window focused"); + + auto idx = m_scrollingData->idx(TDATA->column.lock()); + auto col = idx == -1 ? m_scrollingData->add() : m_scrollingData->add(idx); + + TDATA->column->remove(TDATA->target.lock()); + + col->add(TDATA); + + m_scrollingData->recalculate(); + } else if (ARGS[0] == "swapcol") { + static const auto PCONFWRAPSWAPCOL = CConfigValue("scrolling:wrap_swapcol"); + + if (ARGS.size() < 2) + return std::unexpected("not enough args"); + + const auto TDATA = dataFor(Desktop::focusState()->window() ? Desktop::focusState()->window()->layoutTarget() : nullptr); + if (!TDATA) + return std::unexpected("no window"); + + const auto CURRENT_COL = TDATA->column.lock(); + if (!CURRENT_COL) + return std::unexpected("no current col"); + + if (m_scrollingData->columns.size() < 2) + return std::unexpected("not enough columns to swap"); + + const int64_t currentIdx = m_scrollingData->idx(CURRENT_COL); + const size_t colCount = m_scrollingData->columns.size(); + + if (currentIdx == -1) + return std::unexpected("no current column"); + + const std::string& direction = ARGS[1]; + int64_t targetIdx = -1; + + // wrap around swaps + if (direction == "l") + if (*PCONFWRAPSWAPCOL == 1) + targetIdx = (currentIdx == 0) ? (colCount - 1) : (currentIdx - 1); + else + targetIdx = (currentIdx == 0) ? 0 : (currentIdx - 1); + else if (direction == "r") + if (*PCONFWRAPSWAPCOL == 1) + targetIdx = (currentIdx == (int64_t)colCount - 1) ? 0 : (currentIdx + 1); + else + targetIdx = (currentIdx == (int64_t)colCount - 1) ? (colCount - 1) : (currentIdx + 1); + else + return std::unexpected("no target (invalid direction?)"); + ; + + std::swap(m_scrollingData->columns.at(currentIdx), m_scrollingData->columns.at(targetIdx)); + + m_scrollingData->controller->swapStrips(currentIdx, targetIdx); + + m_scrollingData->centerOrFitCol(CURRENT_COL); + m_scrollingData->recalculate(); + } else + return std::unexpected("no such layoutmsg for scrolling"); + + return {}; +} + +std::optional CScrollingAlgorithm::predictSizeForNewTarget() { + return std::nullopt; +} + +void CScrollingAlgorithm::focusTargetUpdate(SP target) { + if (!target || !validMapped(target->window())) { + Desktop::focusState()->fullWindowFocus(nullptr, Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE); + return; + } + Desktop::focusState()->fullWindowFocus(target->window(), Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE); + const auto TARGETDATA = dataFor(target); + if (TARGETDATA) { + if (auto col = TARGETDATA->column.lock()) + col->lastFocusedTarget = TARGETDATA; + } +} + +SP CScrollingAlgorithm::findBestNeighbor(SP pCurrent, SP pTargetCol) { + if (!pCurrent || !pTargetCol || pTargetCol->targetDatas.empty()) + return nullptr; + + const double currentTop = pCurrent->layoutBox.y; + const double currentBottom = pCurrent->layoutBox.y + pCurrent->layoutBox.h; + std::vector> overlappingTargets; + for (const auto& candidate : pTargetCol->targetDatas) { + const double candidateTop = candidate->layoutBox.y; + const double candidateBottom = candidate->layoutBox.y + candidate->layoutBox.h; + const bool overlaps = (candidateTop < currentBottom) && (candidateBottom > currentTop); + + if (overlaps) + overlappingTargets.emplace_back(candidate); + } + if (!overlappingTargets.empty()) { + auto lastFocused = pTargetCol->lastFocusedTarget.lock(); + + if (lastFocused) { + auto it = std::ranges::find(overlappingTargets, lastFocused); + if (it != overlappingTargets.end()) + return lastFocused; + } + + auto topmost = std::ranges::min_element(overlappingTargets, std::less<>{}, [](const SP& t) { return t->layoutBox.y; }); + return *topmost; + } + if (!pTargetCol->targetDatas.empty()) + return pTargetCol->targetDatas.front(); + return nullptr; +} + +SP CScrollingAlgorithm::dataFor(SP t) { + if (!t) + return nullptr; + + for (const auto& c : m_scrollingData->columns) { + for (const auto& d : c->targetDatas) { + if (d->target == t) + return d; + } + } + + return nullptr; +} + +eScrollDirection CScrollingAlgorithm::getDynamicDirection() { + const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(m_parent->space()->workspace()); + std::string directionString; + if (WORKSPACERULE.layoutopts.contains("direction")) + directionString = WORKSPACERULE.layoutopts.at("direction"); + + static const auto PCONFDIRECTION = CConfigValue("scrolling:direction"); + std::string configDirection = *PCONFDIRECTION; + + // Workspace rule overrides global config + if (!directionString.empty()) + configDirection = directionString; + + // Parse direction string + if (configDirection == "left") + return SCROLL_DIR_LEFT; + else if (configDirection == "down") + return SCROLL_DIR_DOWN; + else if (configDirection == "up") + return SCROLL_DIR_UP; + else + return SCROLL_DIR_RIGHT; // default +} + +CBox CScrollingAlgorithm::usableArea() { + CBox box = m_parent->space()->workArea(); + + // doesn't matter, this happens when this algo is about to be destroyed + if (!m_parent->space()->workspace() || !m_parent->space()->workspace()->m_monitor) + return box; + + box.translate(-m_parent->space()->workspace()->m_monitor->m_position); + return box; +} + +float CScrollingAlgorithm::defaultColumnWidth() { + static const auto PCOLWIDTH = CConfigValue("scrolling:column_width"); + return std::clamp(*PCOLWIDTH, MIN_COLUMN_WIDTH, MAX_COLUMN_WIDTH); +} diff --git a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp new file mode 100644 index 00000000..d95b3197 --- /dev/null +++ b/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp @@ -0,0 +1,145 @@ +#pragma once + +#include "../../TiledAlgorithm.hpp" +#include "../../../../helpers/math/Direction.hpp" +#include "ScrollTapeController.hpp" +#include "../../../../helpers/signal/Signal.hpp" + +#include + +namespace Layout::Tiled { + class CScrollingAlgorithm; + struct SColumnData; + struct SScrollingData; + + struct SScrollingTargetData { + SScrollingTargetData(SP t, SP col) : target(t), column(col) { + ; + } + + WP target; + WP column; + bool ignoreFullscreenChecks = false; + + CBox layoutBox; + }; + + struct SColumnData { + SColumnData(SP data) : scrollingData(data) { + ; + } + + void add(SP t); + void add(SP t, int after); + void add(SP w); + void add(SP w, int after); + void remove(SP t); + bool has(SP t); + size_t idx(SP t); + + // index of lowest target that is above y. + size_t idxForHeight(float y); + + bool up(SP w); + bool down(SP w); + + SP next(SP w); + SP prev(SP w); + + std::vector> targetDatas; + WP scrollingData; + WP lastFocusedTarget; + + WP self; + + // Helper methods to access controller-managed data + float getColumnWidth() const; + void setColumnWidth(float width); + float getTargetSize(size_t idx) const; + void setTargetSize(size_t idx, float size); + float getTargetSize(SP target) const; + void setTargetSize(SP target, float size); + }; + + struct SScrollingData { + SScrollingData(CScrollingAlgorithm* algo); + + std::vector> columns; + + UP controller; + + SP add(); + SP add(int after); + int64_t idx(SP c); + void remove(SP c); + double maxWidth(); + SP next(SP c); + SP prev(SP c); + SP atCenter(); + + bool visible(SP c, bool full = false); + void centerCol(SP c); + void fitCol(SP c); + void centerOrFitCol(SP c); + + void recalculate(bool forceInstant = false); + + CScrollingAlgorithm* algorithm = nullptr; + WP self; + std::optional lockedCameraOffset; + }; + + class CScrollingAlgorithm : public ITiledAlgorithm { + public: + CScrollingAlgorithm(); + virtual ~CScrollingAlgorithm(); + + virtual void newTarget(SP target); + virtual void movedTarget(SP target, std::optional focalPoint = std::nullopt); + virtual void removeTarget(SP target); + + virtual void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); + virtual void recalculate(); + + virtual SP getNextCandidate(SP old); + + virtual std::expected layoutMsg(const std::string_view& sv); + virtual std::optional predictSizeForNewTarget(); + + virtual void swapTargets(SP a, SP b); + virtual void moveTargetInDirection(SP t, Math::eDirection dir, bool silent); + + CBox usableArea(); + + enum eInputMode : uint8_t { + INPUT_MODE_SOFT = 0, + INPUT_MODE_CLICK, + INPUT_MODE_KB + }; + + private: + SP m_scrollingData; + + CHyprSignalListener m_configCallback; + CHyprSignalListener m_focusCallback; + CHyprSignalListener m_mouseButtonCallback; + + struct { + std::vector configuredWidths; + } m_config; + + eScrollDirection getDynamicDirection(); + + SP findBestNeighbor(SP pCurrent, SP pTargetCol); + SP dataFor(SP t); + SP closestNode(const Vector2D& posGlobglobgabgalab); + + void focusTargetUpdate(SP target); + void moveTargetTo(SP t, Math::eDirection dir, bool silent); + void focusOnInput(SP target, eInputMode input); + + float defaultColumnWidth(); + + friend struct SScrollingData; + }; +}; diff --git a/src/layout/space/Space.cpp b/src/layout/space/Space.cpp new file mode 100644 index 00000000..db3925f6 --- /dev/null +++ b/src/layout/space/Space.cpp @@ -0,0 +1,197 @@ +#include "Space.hpp" + +#include "../target/Target.hpp" +#include "../algorithm/Algorithm.hpp" + +#include "../../debug/log/Logger.hpp" +#include "../../desktop/Workspace.hpp" +#include "../../config/ConfigManager.hpp" +#include "../../event/EventBus.hpp" + +using namespace Layout; + +SP CSpace::create(PHLWORKSPACE w) { + auto space = SP(new CSpace(w)); + space->m_self = space; + return space; +} + +CSpace::CSpace(PHLWORKSPACE parent) : m_parent(parent) { + recheckWorkArea(); + + // NOLINTNEXTLINE + m_geomUpdateCallback = Event::bus()->m_events.monitor.layoutChanged.listen([this] { + recheckWorkArea(); + m_algorithm->recalculate(); + }); +} + +void CSpace::add(SP t) { + m_targets.emplace_back(t); + + recheckWorkArea(); + + if (m_algorithm) + m_algorithm->addTarget(t); + + m_parent->updateWindows(); +} + +void CSpace::move(SP t, std::optional focalPoint) { + m_targets.emplace_back(t); + + recheckWorkArea(); + + if (m_algorithm) + m_algorithm->moveTarget(t, focalPoint); + + m_parent->updateWindows(); +} + +void CSpace::remove(SP t) { + std::erase_if(m_targets, [&t](const auto& e) { return !e || e == t; }); + + recheckWorkArea(); + + if (m_algorithm) + m_algorithm->removeTarget(t); + + if (m_parent) // can be null if the workspace is gone + m_parent->updateWindows(); +} + +void CSpace::setAlgorithmProvider(SP algo) { + m_algorithm = algo; +} + +void CSpace::recheckWorkArea() { + if (!m_parent || !m_parent->m_monitor) { + Log::logger->log(Log::ERR, "CSpace: recheckWorkArea on no parent / mon?!"); + return; + } + + const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(m_parent.lock()); + + auto workArea = m_parent->m_monitor->logicalBoxMinusReserved(); + + static auto PGAPSOUTDATA = CConfigValue("general:gaps_out"); + static auto PFLOATGAPSDATA = CConfigValue("general:float_gaps"); + auto* const PGAPSOUT = sc((PGAPSOUTDATA.ptr())->getData()); + auto* PFLOATGAPS = sc(PFLOATGAPSDATA.ptr()->getData()); + if (PFLOATGAPS->m_bottom < 0 || PFLOATGAPS->m_left < 0 || PFLOATGAPS->m_right < 0 || PFLOATGAPS->m_top < 0) + PFLOATGAPS = PGAPSOUT; + + auto gapsOut = WORKSPACERULE.gapsOut.value_or(*PGAPSOUT); + auto gapsFloat = WORKSPACERULE.gapsOut.value_or(*PFLOATGAPS); + + Desktop::CReservedArea reservedGaps{gapsOut.m_top, gapsOut.m_right, gapsOut.m_bottom, gapsOut.m_left}; + Desktop::CReservedArea reservedFloatGaps{gapsFloat.m_top, gapsFloat.m_right, gapsFloat.m_bottom, gapsFloat.m_left}; + + auto floatWorkArea = workArea; + + reservedFloatGaps.applyip(floatWorkArea); + reservedGaps.applyip(workArea); + + m_workArea = workArea; + m_floatingWorkArea = floatWorkArea; +} + +const CBox& CSpace::workArea(bool floating) const { + return floating ? m_floatingWorkArea : m_workArea; +} + +PHLWORKSPACE CSpace::workspace() const { + return m_parent.lock(); +} + +void CSpace::toggleTargetFloating(SP t) { + t->setWasTiling(true); + m_algorithm->setFloating(t, !t->floating()); + t->setWasTiling(false); + + m_parent->updateWindows(); + + recalculate(); +} + +CBox CSpace::targetPositionLocal(SP t) const { + return t->position().translate(-m_workArea.pos()); +} + +void CSpace::resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner) { + if (!m_algorithm) + return; + + m_algorithm->resizeTarget(Δ, target, corner); +} + +void CSpace::moveTarget(const Vector2D& Δ, SP target) { + if (!m_algorithm) + return; + + m_algorithm->moveTarget(Δ, target); +} + +SP CSpace::algorithm() const { + return m_algorithm; +} + +void CSpace::recalculate() { + recheckWorkArea(); + + if (m_algorithm) + m_algorithm->recalculate(); +} + +void CSpace::setFullscreen(SP t, eFullscreenMode mode) { + t->setFullscreenMode(mode); + + if (mode == FSMODE_NONE && m_algorithm && t->floating()) + m_algorithm->recenter(t); + + recalculate(); +} + +std::expected CSpace::layoutMsg(const std::string_view& sv) { + if (m_algorithm) + return m_algorithm->layoutMsg(sv); + + return {}; +} + +std::optional CSpace::predictSizeForNewTiledTarget() { + if (m_algorithm) + return m_algorithm->predictSizeForNewTiledTarget(); + + return std::nullopt; +} + +void CSpace::swap(SP a, SP b) { + for (auto& t : m_targets) { + if (t == a) + t = b; + else if (t == b) + t = a; + } + + if (m_algorithm) + m_algorithm->swapTargets(a, b); +} + +void CSpace::moveTargetInDirection(SP t, Math::eDirection dir, bool silent) { + if (m_algorithm) + m_algorithm->moveTargetInDirection(t, dir, silent); +} + +void CSpace::setTargetGeom(const CBox& box, SP target) { + if (m_algorithm) + m_algorithm->setTargetGeom(box, target); +} + +SP CSpace::getNextCandidate(SP old) { + return !m_algorithm ? nullptr : m_algorithm->getNextCandidate(old); +} + +const std::vector>& CSpace::targets() const { + return m_targets; +} diff --git a/src/layout/space/Space.hpp b/src/layout/space/Space.hpp new file mode 100644 index 00000000..e29a6d8f --- /dev/null +++ b/src/layout/space/Space.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include "../../helpers/math/Math.hpp" +#include "../../helpers/math/Direction.hpp" +#include "../../helpers/memory/Memory.hpp" + +#include "../../desktop/DesktopTypes.hpp" + +#include "../LayoutManager.hpp" + +#include +#include + +namespace Layout { + class ITarget; + class CAlgorithm; + + class CSpace { + public: + static SP create(PHLWORKSPACE w); + ~CSpace() = default; + + void add(SP t); + void remove(SP t); + void move(SP t, std::optional focalPoint = std::nullopt); + + void swap(SP a, SP b); + + SP getNextCandidate(SP old); + + void setAlgorithmProvider(SP algo); + void recheckWorkArea(); + void setFullscreen(SP t, eFullscreenMode mode); + + void moveTargetInDirection(SP t, Math::eDirection dir, bool silent); + + void recalculate(); + + void toggleTargetFloating(SP t); + + std::expected layoutMsg(const std::string_view& sv); + std::optional predictSizeForNewTiledTarget(); + + const CBox& workArea(bool floating = false) const; + PHLWORKSPACE workspace() const; + CBox targetPositionLocal(SP t) const; + + void resizeTarget(const Vector2D& Δ, SP target, eRectCorner corner = CORNER_NONE); + void moveTarget(const Vector2D& Δ, SP target); + void setTargetGeom(const CBox& box, SP target); // only for float + + SP algorithm() const; + + const std::vector>& targets() const; + + private: + CSpace(PHLWORKSPACE parent); + + WP m_self; + + std::vector> m_targets; + SP m_algorithm; + PHLWORKSPACEREF m_parent; + + // work area is in global coords + CBox m_workArea, m_floatingWorkArea; + + // for recalc + CHyprSignalListener m_geomUpdateCallback; + }; +}; \ No newline at end of file diff --git a/src/layout/supplementary/DragController.cpp b/src/layout/supplementary/DragController.cpp new file mode 100644 index 00000000..be70f4ac --- /dev/null +++ b/src/layout/supplementary/DragController.cpp @@ -0,0 +1,398 @@ +#include "DragController.hpp" + +#include "../space/Space.hpp" + +#include "../../Compositor.hpp" +#include "../../managers/cursor/CursorShapeOverrideController.hpp" +#include "../../desktop/state/FocusState.hpp" +#include "../../desktop/view/Group.hpp" +#include "../../render/Renderer.hpp" + +using namespace Layout; +using namespace Layout::Supplementary; + +SP CDragStateController::target() const { + return m_target.lock(); +} + +eMouseBindMode CDragStateController::mode() const { + return m_dragMode; +} + +bool CDragStateController::wasDraggingWindow() const { + return m_wasDraggingWindow; +} + +bool CDragStateController::dragThresholdReached() const { + return m_dragThresholdReached; +} + +void CDragStateController::resetDragThresholdReached() { + m_dragThresholdReached = false; +} + +bool CDragStateController::draggingTiled() const { + return m_draggingTiled; +} + +bool CDragStateController::updateDragWindow() { + const auto DRAGGINGTARGET = m_target.lock(); + const bool WAS_FULLSCREEN = DRAGGINGTARGET->fullscreenMode() != FSMODE_NONE; + + if (m_dragThresholdReached) { + if (WAS_FULLSCREEN) { + Log::logger->log(Log::DEBUG, "Dragging a fullscreen window"); + g_pCompositor->setWindowFullscreenInternal(DRAGGINGTARGET->window(), FSMODE_NONE); + } + + const auto PWORKSPACE = DRAGGINGTARGET->workspace(); + const auto DRAGGINGWINDOW = DRAGGINGTARGET->window(); + + if (PWORKSPACE->m_hasFullscreenWindow && (!DRAGGINGTARGET->floating() || (!DRAGGINGWINDOW->m_createdOverFullscreen && !DRAGGINGWINDOW->m_pinned))) { + Log::logger->log(Log::DEBUG, "Rejecting drag on a fullscreen workspace. (window under fullscreen)"); + CKeybindManager::changeMouseBindMode(MBIND_INVALID); + return true; + } + } + + m_draggingTiled = false; + m_draggingWindowOriginalFloatSize = DRAGGINGTARGET->lastFloatingSize(); + + if (WAS_FULLSCREEN && DRAGGINGTARGET->floating()) { + const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal(); + DRAGGINGTARGET->setPositionGlobal(CBox{MOUSECOORDS - DRAGGINGTARGET->position().size() / 2.F, DRAGGINGTARGET->position().size()}); + } else if (!DRAGGINGTARGET->floating() && m_dragMode == MBIND_MOVE) { + Vector2D MINSIZE = DRAGGINGTARGET->minSize().value_or(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}); + DRAGGINGTARGET->rememberFloatingSize((DRAGGINGTARGET->position().size() * 0.8489).clamp(MINSIZE, Vector2D{}).floor()); + DRAGGINGTARGET->setPositionGlobal(CBox{g_pInputManager->getMouseCoordsInternal() - DRAGGINGTARGET->position().size() / 2.F, DRAGGINGTARGET->position().size()}); + + if (m_dragThresholdReached) { + g_layoutManager->changeFloatingMode(DRAGGINGTARGET); + m_draggingTiled = true; + } + } + + const auto DRAG_ORIGINAL_BOX = DRAGGINGTARGET->position(); + + m_beginDragXY = g_pInputManager->getMouseCoordsInternal(); + m_beginDragPositionXY = DRAG_ORIGINAL_BOX.pos(); + m_beginDragSizeXY = DRAG_ORIGINAL_BOX.size(); + m_lastDragXY = m_beginDragXY; + + return false; +} + +void CDragStateController::dragBegin(SP target, eMouseBindMode mode) { + m_target = target; + m_dragMode = mode; + + const auto DRAGGINGTARGET = m_target.lock(); + static auto PDRAGTHRESHOLD = CConfigValue("binds:drag_threshold"); + + m_mouseMoveEventCount = 1; + m_beginDragSizeXY = Vector2D(); + + // Window will be floating. Let's check if it's valid. It should be, but I don't like crashing. + if (!validMapped(DRAGGINGTARGET->window())) { + Log::logger->log(Log::ERR, "Dragging attempted on an invalid window (not mapped)"); + CKeybindManager::changeMouseBindMode(MBIND_INVALID); + return; + } + + if (!DRAGGINGTARGET->workspace()) { + Log::logger->log(Log::ERR, "Dragging attempted on an invalid window (no workspace)"); + CKeybindManager::changeMouseBindMode(MBIND_INVALID); + return; + } + + // Try to pick up dragged window now if drag_threshold is disabled + // or at least update dragging related variables for the cursors + m_dragThresholdReached = *PDRAGTHRESHOLD <= 0; + if (updateDragWindow()) + return; + + // get the grab corner + static auto RESIZECORNER = CConfigValue("general:resize_corner"); + if (*RESIZECORNER != 0 && *RESIZECORNER <= 4 && DRAGGINGTARGET->floating()) { + switch (*RESIZECORNER) { + case 1: + m_grabbedCorner = CORNER_TOPLEFT; + Cursor::overrideController->setOverride("nw-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); + break; + case 2: + m_grabbedCorner = CORNER_TOPRIGHT; + Cursor::overrideController->setOverride("ne-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); + break; + case 3: + m_grabbedCorner = CORNER_BOTTOMRIGHT; + Cursor::overrideController->setOverride("se-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); + break; + case 4: + m_grabbedCorner = CORNER_BOTTOMLEFT; + Cursor::overrideController->setOverride("sw-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); + break; + } + } else if (m_beginDragXY.x < m_beginDragPositionXY.x + m_beginDragSizeXY.x / 2.F) { + if (m_beginDragXY.y < m_beginDragPositionXY.y + m_beginDragSizeXY.y / 2.F) { + m_grabbedCorner = CORNER_TOPLEFT; + Cursor::overrideController->setOverride("nw-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); + } else { + m_grabbedCorner = CORNER_BOTTOMLEFT; + Cursor::overrideController->setOverride("sw-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); + } + } else { + if (m_beginDragXY.y < m_beginDragPositionXY.y + m_beginDragSizeXY.y / 2.F) { + m_grabbedCorner = CORNER_TOPRIGHT; + Cursor::overrideController->setOverride("ne-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); + } else { + m_grabbedCorner = CORNER_BOTTOMRIGHT; + Cursor::overrideController->setOverride("se-resize", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); + } + } + + if (m_dragMode != MBIND_RESIZE && m_dragMode != MBIND_RESIZE_FORCE_RATIO && m_dragMode != MBIND_RESIZE_BLOCK_RATIO) + Cursor::overrideController->setOverride("grabbing", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); + + DRAGGINGTARGET->damageEntire(); + + g_pKeybindManager->shadowKeybinds(); + + if (DRAGGINGTARGET->window()) { + Desktop::focusState()->rawWindowFocus(DRAGGINGTARGET->window(), Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE); + g_pCompositor->changeWindowZOrder(DRAGGINGTARGET->window(), true); + } +} +void CDragStateController::dragEnd() { + auto draggingTarget = m_target.lock(); + + m_mouseMoveEventCount = 1; + + if (!validMapped(draggingTarget->window())) { + if (draggingTarget->window()) { + Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); + m_target.reset(); + } + return; + } + + Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION); + m_target.reset(); + m_wasDraggingWindow = true; + + if (m_dragMode == MBIND_MOVE && draggingTarget->window()) { + draggingTarget->damageEntire(); + + const auto DRAGGING_WINDOW = draggingTarget->window(); + + const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal(); + PHLWINDOW pWindow = + g_pCompositor->vectorToWindowUnified(MOUSECOORDS, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING, DRAGGING_WINDOW); + + if (pWindow) { + if (pWindow->checkInputOnDecos(INPUT_TYPE_DRAG_END, MOUSECOORDS, DRAGGING_WINDOW)) + return; + + const bool FLOATEDINTOTILED = !pWindow->m_isFloating && !m_draggingTiled; + static auto PDRAGINTOGROUP = CConfigValue("group:drag_into_group"); + + if (pWindow->m_group && DRAGGING_WINDOW->canBeGroupedInto(pWindow->m_group) && *PDRAGINTOGROUP == 1 && !FLOATEDINTOTILED) { + pWindow->m_group->add(DRAGGING_WINDOW); + // fix the draggingTarget, now it's DRAGGING_WINDOW + draggingTarget = DRAGGING_WINDOW->m_target; + } + } + } + + if (m_draggingTiled) { + // static auto PPRECISEMOUSE = CConfigValue("dwindle:precise_mouse_move"); + + // FIXME: remove or rethink + // if (*PPRECISEMOUSE) { + // eDirection direction = DIRECTION_DEFAULT; + + // const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal(); + // const PHLWINDOW pReferenceWindow = + // g_pCompositor->vectorToWindowUnified(MOUSECOORDS, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING, DRAGGINGWINDOW); + + // if (pReferenceWindow && pReferenceWindow != DRAGGINGWINDOW) { + // const Vector2D draggedCenter = DRAGGINGWINDOW->m_realPosition->goal() + DRAGGINGWINDOW->m_realSize->goal() / 2.f; + // const Vector2D referenceCenter = pReferenceWindow->m_realPosition->goal() + pReferenceWindow->m_realSize->goal() / 2.f; + // const float xDiff = draggedCenter.x - referenceCenter.x; + // const float yDiff = draggedCenter.y - referenceCenter.y; + + // if (fabs(xDiff) > fabs(yDiff)) + // direction = xDiff < 0 ? DIRECTION_LEFT : DIRECTION_RIGHT; + // else + // direction = yDiff < 0 ? DIRECTION_UP : DIRECTION_DOWN; + // } + + // onWindowRemovedTiling(DRAGGINGWINDOW); + // onWindowCreatedTiling(DRAGGINGWINDOW, direction); + // } else + + // make sure to check if we are floating because drag into group could make us tiled already + if (draggingTarget->floating()) + g_layoutManager->changeFloatingMode(draggingTarget); + + draggingTarget->rememberFloatingSize(m_draggingWindowOriginalFloatSize); + } + + draggingTarget->damageEntire(); + + g_layoutManager->setTargetGeom(draggingTarget->position(), draggingTarget); + + Desktop::focusState()->fullWindowFocus(draggingTarget->window(), Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE); + + m_wasDraggingWindow = false; + m_dragMode = MBIND_INVALID; +} + +void CDragStateController::mouseMove(const Vector2D& mousePos) { + if (m_target.expired()) + return; + + const auto DRAGGINGTARGET = m_target.lock(); + static auto PDRAGTHRESHOLD = CConfigValue("binds:drag_threshold"); + + // Window invalid or drag begin size 0,0 meaning we rejected it. + if ((!validMapped(DRAGGINGTARGET->window()) || m_beginDragSizeXY == Vector2D())) { + CKeybindManager::changeMouseBindMode(MBIND_INVALID); + return; + } + + // Yoink dragged window here instead if using drag_threshold and it has been reached + if (*PDRAGTHRESHOLD > 0 && !m_dragThresholdReached) { + if ((m_beginDragXY.distanceSq(mousePos) <= std::pow(*PDRAGTHRESHOLD, 2) && m_beginDragXY == m_lastDragXY)) + return; + m_dragThresholdReached = true; + if (updateDragWindow()) + return; + } + + static auto TIMER = std::chrono::high_resolution_clock::now(), MSTIMER = TIMER; + + const auto DELTA = Vector2D(mousePos.x - m_beginDragXY.x, mousePos.y - m_beginDragXY.y); + const auto TICKDELTA = Vector2D(mousePos.x - m_lastDragXY.x, mousePos.y - m_lastDragXY.y); + + static auto SNAPENABLED = CConfigValue("general:snap:enabled"); + + const auto TIMERDELTA = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - TIMER).count(); + const auto MSDELTA = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - MSTIMER).count(); + const auto MSMONITOR = 1000.0 / g_pHyprRenderer->m_mostHzMonitor->m_refreshRate; + static int totalMs = 0; + bool canSkipUpdate = true; + + MSTIMER = std::chrono::high_resolution_clock::now(); + + if (m_mouseMoveEventCount == 1) + totalMs = 0; + + if (MSMONITOR > 16.0) { + totalMs += MSDELTA < MSMONITOR ? MSDELTA : std::round(totalMs * 1.0 / m_mouseMoveEventCount); + m_mouseMoveEventCount += 1; + + // check if time-window is enough to skip update on 60hz monitor + canSkipUpdate = std::clamp(MSMONITOR - TIMERDELTA, 0.0, MSMONITOR) > totalMs * 1.0 / m_mouseMoveEventCount; + } + + if ((abs(TICKDELTA.x) < 1.f && abs(TICKDELTA.y) < 1.f) || (TIMERDELTA < MSMONITOR && canSkipUpdate && (m_dragMode != MBIND_MOVE))) + return; + + TIMER = std::chrono::high_resolution_clock::now(); + + m_lastDragXY = mousePos; + + DRAGGINGTARGET->damageEntire(); + + if (m_dragMode == MBIND_MOVE) { + + Vector2D newPos = m_beginDragPositionXY + DELTA; + Vector2D newSize = DRAGGINGTARGET->position().size(); + + if (*SNAPENABLED && !m_draggingTiled) + g_layoutManager->performSnap(newPos, newSize, DRAGGINGTARGET, MBIND_MOVE, -1, m_beginDragSizeXY); + + newPos = newPos.round(); + + DRAGGINGTARGET->setPositionGlobal({newPos, newSize}); + DRAGGINGTARGET->warpPositionSize(); + } else if (m_dragMode == MBIND_RESIZE || m_dragMode == MBIND_RESIZE_FORCE_RATIO || m_dragMode == MBIND_RESIZE_BLOCK_RATIO) { + if (DRAGGINGTARGET->floating()) { + + Vector2D MINSIZE = DRAGGINGTARGET->minSize().value_or(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}); + Vector2D MAXSIZE = DRAGGINGTARGET->maxSize().value_or(Math::VECTOR2D_MAX); + + Vector2D newSize = m_beginDragSizeXY; + Vector2D newPos = m_beginDragPositionXY; + + if (m_grabbedCorner == CORNER_BOTTOMRIGHT) + newSize = newSize + DELTA; + else if (m_grabbedCorner == CORNER_TOPLEFT) + newSize = newSize - DELTA; + else if (m_grabbedCorner == CORNER_TOPRIGHT) + newSize = newSize + Vector2D(DELTA.x, -DELTA.y); + else if (m_grabbedCorner == CORNER_BOTTOMLEFT) + newSize = newSize + Vector2D(-DELTA.x, DELTA.y); + + eMouseBindMode mode = m_dragMode; + if (DRAGGINGTARGET->window() && DRAGGINGTARGET->window()->m_ruleApplicator->keepAspectRatio().valueOrDefault() && mode != MBIND_RESIZE_BLOCK_RATIO) + mode = MBIND_RESIZE_FORCE_RATIO; + + if (m_beginDragSizeXY.x >= 1 && m_beginDragSizeXY.y >= 1 && mode == MBIND_RESIZE_FORCE_RATIO) { + + const float RATIO = m_beginDragSizeXY.y / m_beginDragSizeXY.x; + + if (MINSIZE.x * RATIO > MINSIZE.y) + MINSIZE = Vector2D(MINSIZE.x, MINSIZE.x * RATIO); + else + MINSIZE = Vector2D(MINSIZE.y / RATIO, MINSIZE.y); + + if (MAXSIZE.x * RATIO < MAXSIZE.y) + MAXSIZE = Vector2D(MAXSIZE.x, MAXSIZE.x * RATIO); + else + MAXSIZE = Vector2D(MAXSIZE.y / RATIO, MAXSIZE.y); + + if (newSize.x * RATIO > newSize.y) + newSize = Vector2D(newSize.x, newSize.x * RATIO); + else + newSize = Vector2D(newSize.y / RATIO, newSize.y); + } + + newSize = newSize.clamp(MINSIZE, MAXSIZE); + + if (m_grabbedCorner == CORNER_TOPLEFT) + newPos = newPos - newSize + m_beginDragSizeXY; + else if (m_grabbedCorner == CORNER_TOPRIGHT) + newPos = newPos + Vector2D(0.0, (m_beginDragSizeXY - newSize).y); + else if (m_grabbedCorner == CORNER_BOTTOMLEFT) + newPos = newPos + Vector2D((m_beginDragSizeXY - newSize).x, 0.0); + + if (*SNAPENABLED) { + g_layoutManager->performSnap(newPos, newSize, DRAGGINGTARGET, mode, m_grabbedCorner, m_beginDragSizeXY); + newSize = newSize.clamp(MINSIZE, MAXSIZE); + } + + CBox wb = {newPos, newSize}; + wb.round(); + + DRAGGINGTARGET->setPositionGlobal(wb); + DRAGGINGTARGET->warpPositionSize(); + } else { + g_layoutManager->resizeTarget(TICKDELTA, DRAGGINGTARGET, m_grabbedCorner); + DRAGGINGTARGET->warpPositionSize(); + } + } + + // get middle point + Vector2D middle = DRAGGINGTARGET->position().middle(); + + // and check its monitor + const auto PMONITOR = g_pCompositor->getMonitorFromVector(middle); + + if (PMONITOR && PMONITOR->m_activeWorkspace && DRAGGINGTARGET->floating() /* If we're resaizing a tiled target, don't do this */) { + const auto WS = PMONITOR->m_activeSpecialWorkspace ? PMONITOR->m_activeSpecialWorkspace : PMONITOR->m_activeWorkspace; + DRAGGINGTARGET->assignToSpace(WS->m_space); + } + + DRAGGINGTARGET->damageEntire(); +} diff --git a/src/layout/supplementary/DragController.hpp b/src/layout/supplementary/DragController.hpp new file mode 100644 index 00000000..3a0d8071 --- /dev/null +++ b/src/layout/supplementary/DragController.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include "../target/Target.hpp" +#include "../../managers/input/InputManager.hpp" + +namespace Layout { + enum eRectCorner : uint8_t; +} + +namespace Layout::Supplementary { + + // DragStateController contains logic to begin and end a drag, which shouldn't be part of the layout's job. It's stuff like + // toggling float when dragging tiled, remembering sizes, checking deltas, etc. + class CDragStateController { + public: + CDragStateController() = default; + ~CDragStateController() = default; + + void dragBegin(SP target, eMouseBindMode mode); + void dragEnd(); + + void mouseMove(const Vector2D& mousePos); + eMouseBindMode mode() const; + bool wasDraggingWindow() const; + bool dragThresholdReached() const; + void resetDragThresholdReached(); + bool draggingTiled() const; + + /* + Called to try to pick up window for dragging. + Updates drag related variables and floats window if threshold reached. + Return true to reject + */ + bool updateDragWindow(); + + SP target() const; + + private: + WP m_target; + + eMouseBindMode m_dragMode = MBIND_INVALID; + bool m_wasDraggingWindow = false; + bool m_dragThresholdReached = false; + bool m_draggingTiled = false; + + int m_mouseMoveEventCount = 0; + Vector2D m_beginDragXY; + Vector2D m_lastDragXY; + Vector2D m_beginDragPositionXY; + Vector2D m_beginDragSizeXY; + Vector2D m_draggingWindowOriginalFloatSize; + Layout::eRectCorner m_grabbedCorner = sc(0) /* CORNER_NONE */; + }; +}; diff --git a/src/layout/supplementary/WorkspaceAlgoMatcher.cpp b/src/layout/supplementary/WorkspaceAlgoMatcher.cpp new file mode 100644 index 00000000..b476c3a0 --- /dev/null +++ b/src/layout/supplementary/WorkspaceAlgoMatcher.cpp @@ -0,0 +1,139 @@ +#include "WorkspaceAlgoMatcher.hpp" + +#include "../../config/ConfigValue.hpp" +#include "../../config/ConfigManager.hpp" + +#include "../algorithm/Algorithm.hpp" +#include "../space/Space.hpp" + +#include "../algorithm/floating/default/DefaultFloatingAlgorithm.hpp" +#include "../algorithm/tiled/dwindle/DwindleAlgorithm.hpp" +#include "../algorithm/tiled/master/MasterAlgorithm.hpp" +#include "../algorithm/tiled/scrolling/ScrollingAlgorithm.hpp" +#include "../algorithm/tiled/monocle/MonocleAlgorithm.hpp" + +#include "../../Compositor.hpp" + +using namespace Layout; +using namespace Layout::Supplementary; + +constexpr const char* DEFAULT_FLOATING_ALGO = "default"; +constexpr const char* DEFAULT_TILED_ALGO = "dwindle"; + +const UP& Supplementary::algoMatcher() { + static UP m = makeUnique(); + return m; +} + +CWorkspaceAlgoMatcher::CWorkspaceAlgoMatcher() { + m_tiledAlgos = { + {"dwindle", [] { return makeUnique(); }}, + {"master", [] { return makeUnique(); }}, + {"scrolling", [] { return makeUnique(); }}, + {"monocle", [] { return makeUnique(); }}, + }; + + m_floatingAlgos = { + {"default", [] { return makeUnique(); }}, + }; + + m_algoNames = { + {&typeid(Tiled::CDwindleAlgorithm), "dwindle"}, + {&typeid(Tiled::CMasterAlgorithm), "master"}, + {&typeid(Tiled::CScrollingAlgorithm), "scrolling"}, + {&typeid(Tiled::CMonocleAlgorithm), "monocle"}, + {&typeid(Floating::CDefaultFloatingAlgorithm), "default"}, + }; +} + +bool CWorkspaceAlgoMatcher::registerTiledAlgo(const std::string& name, const std::type_info* typeInfo, std::function()>&& factory) { + if (m_tiledAlgos.contains(name) || m_floatingAlgos.contains(name)) + return false; + + m_tiledAlgos.emplace(name, std::move(factory)); + m_algoNames.emplace(typeInfo, name); + + updateWorkspaceLayouts(); + + return true; +} + +bool CWorkspaceAlgoMatcher::registerFloatingAlgo(const std::string& name, const std::type_info* typeInfo, std::function()>&& factory) { + if (m_tiledAlgos.contains(name) || m_floatingAlgos.contains(name)) + return false; + + m_floatingAlgos.emplace(name, std::move(factory)); + m_algoNames.emplace(typeInfo, name); + + updateWorkspaceLayouts(); + + return true; +} + +bool CWorkspaceAlgoMatcher::unregisterAlgo(const std::string& name) { + if (!m_tiledAlgos.contains(name) && !m_floatingAlgos.contains(name)) + return false; + + std::erase_if(m_algoNames, [&name](const auto& e) { return e.second == name; }); + + if (m_floatingAlgos.contains(name)) + m_floatingAlgos.erase(name); + else + m_tiledAlgos.erase(name); + + // this is needed here to avoid situations where a plugin unloads and we still have a UP + // to a plugin layout + updateWorkspaceLayouts(); + + return true; +} + +UP CWorkspaceAlgoMatcher::algoForNameTiled(const std::string& s) { + if (m_tiledAlgos.contains(s)) + return m_tiledAlgos.at(s)(); + return m_tiledAlgos.at(DEFAULT_TILED_ALGO)(); +} + +UP CWorkspaceAlgoMatcher::algoForNameFloat(const std::string& s) { + if (m_floatingAlgos.contains(s)) + return m_floatingAlgos.at(s)(); + return m_floatingAlgos.at(DEFAULT_FLOATING_ALGO)(); +} + +std::string CWorkspaceAlgoMatcher::tiledAlgoForWorkspace(const PHLWORKSPACE& w) { + static auto PLAYOUT = CConfigValue("general:layout"); + + auto rule = g_pConfigManager->getWorkspaceRuleFor(w); + return rule.layout.value_or(*PLAYOUT); +} + +SP CWorkspaceAlgoMatcher::createAlgorithmForWorkspace(PHLWORKSPACE w) { + return CAlgorithm::create(algoForNameTiled(tiledAlgoForWorkspace(w)), makeUnique(), w->m_space); +} + +void CWorkspaceAlgoMatcher::updateWorkspaceLayouts() { + // TODO: make this ID-based, string comparison is slow + for (const auto& ws : g_pCompositor->getWorkspaces()) { + if (!ws) + continue; + + const auto& TILED_ALGO = ws->m_space->algorithm()->tiledAlgo(); + + if (!TILED_ALGO) + continue; + + const auto LAYOUT_TO_USE = tiledAlgoForWorkspace(ws.lock()); + + if (m_algoNames.contains(&typeid(*TILED_ALGO.get())) && m_algoNames.at(&typeid(*TILED_ALGO.get())) == LAYOUT_TO_USE) + continue; + + // needs a switchup + ws->m_space->algorithm()->updateTiledAlgo(algoForNameTiled(LAYOUT_TO_USE)); + } +} + +std::string CWorkspaceAlgoMatcher::getNameForTiledAlgo(const std::type_info* type) { + if (m_algoNames.contains(type)) + return m_algoNames.at(type); + return "unknown"; +} diff --git a/src/layout/supplementary/WorkspaceAlgoMatcher.hpp b/src/layout/supplementary/WorkspaceAlgoMatcher.hpp new file mode 100644 index 00000000..d39e2998 --- /dev/null +++ b/src/layout/supplementary/WorkspaceAlgoMatcher.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "../../desktop/DesktopTypes.hpp" + +#include +#include +#include +#include + +namespace Layout { + class CAlgorithm; + class ITiledAlgorithm; + class IFloatingAlgorithm; +} + +namespace Layout::Supplementary { + class CWorkspaceAlgoMatcher { + public: + CWorkspaceAlgoMatcher(); + ~CWorkspaceAlgoMatcher() = default; + + SP createAlgorithmForWorkspace(PHLWORKSPACE w); + void updateWorkspaceLayouts(); + std::string getNameForTiledAlgo(const std::type_info* type); + + // these fns can fail due to name collisions + bool registerTiledAlgo(const std::string& name, const std::type_info* typeInfo, std::function()>&& factory); + bool registerFloatingAlgo(const std::string& name, const std::type_info* typeInfo, std::function()>&& factory); + + // this fn fails if the algo isn't registered + bool unregisterAlgo(const std::string& name); + + private: + UP algoForNameTiled(const std::string& s); + UP algoForNameFloat(const std::string& s); + + std::string tiledAlgoForWorkspace(const PHLWORKSPACE&); + + std::map()>> m_tiledAlgos; + std::map()>> m_floatingAlgos; + + std::map m_algoNames; + }; + + const UP& algoMatcher(); +} \ No newline at end of file diff --git a/src/layout/target/Target.cpp b/src/layout/target/Target.cpp new file mode 100644 index 00000000..e433c237 --- /dev/null +++ b/src/layout/target/Target.cpp @@ -0,0 +1,146 @@ +#include "Target.hpp" +#include "../space/Space.hpp" +#include "../../debug/log/Logger.hpp" + +#include + +using namespace Layout; +using namespace Hyprutils::Utils; + +void ITarget::setPositionGlobal(const CBox& box) { + m_box = box; + m_box.round(); +} + +void ITarget::assignToSpace(const SP& space, std::optional focalPoint) { + if (m_space == space && !m_ghostSpace) + return; + + const bool HAD_SPACE = !!m_space; + + if (m_space && !m_ghostSpace) + m_space->remove(m_self.lock()); + + m_space = space; + + if (space && HAD_SPACE) + space->move(m_self.lock(), focalPoint); + else if (space) + space->add(m_self.lock()); + + if (!space) + Log::logger->log(Log::WARN, "ITarget: assignToSpace with a null space?"); + + m_ghostSpace = false; + + onUpdateSpace(); +} + +void ITarget::setSpaceGhost(const SP& space) { + if (m_space) + assignToSpace(nullptr); + + m_space = space; + + m_ghostSpace = true; +} + +SP ITarget::space() const { + return m_space; +} + +PHLWORKSPACE ITarget::workspace() const { + if (!m_space) + return nullptr; + + return m_space->workspace(); +} + +CBox ITarget::position() const { + return m_box; +} + +void ITarget::rememberFloatingSize(const Vector2D& size) { + m_floatingSize = size; +} + +Vector2D ITarget::lastFloatingSize() const { + return m_floatingSize; +} + +void ITarget::recalc() { + setPositionGlobal(m_box); +} + +void ITarget::setPseudo(bool x) { + if (m_pseudo == x) + return; + + m_pseudo = x; + + recalc(); +} + +bool ITarget::isPseudo() const { + return m_pseudo; +} + +void ITarget::setPseudoSize(const Vector2D& size) { + m_pseudoSize = size; + + recalc(); +} + +Vector2D ITarget::pseudoSize() { + return m_pseudoSize; +} + +void ITarget::swap(SP b) { + const auto IS_FLOATING = floating(); + const auto IS_FLOATING_B = b->floating(); + + // Keep workspaces alive during a swap: moving one window will unref the ws + + // NOLINTNEXTLINE + const auto PWS1 = workspace(); + // NOLINTNEXTLINE + const auto PWS2 = b->workspace(); + + CScopeGuard x([&] { + b->setFloating(IS_FLOATING); + setFloating(IS_FLOATING_B); + + // update the spaces + b->onUpdateSpace(); + onUpdateSpace(); + }); + + if (b->space() == m_space) { + // simplest + m_space->swap(m_self.lock(), b); + m_space->recalculate(); + return; + } + + // spaces differ + if (m_space) + m_space->swap(m_self.lock(), b); + if (b->space()) + b->space()->swap(b, m_self.lock()); + + std::swap(m_space, b->m_space); + + // recalc both + if (m_space) + m_space->recalculate(); + if (b->space()) + b->space()->recalculate(); +} + +bool ITarget::wasTiling() const { + return m_wasTiling; +} + +void ITarget::setWasTiling(bool x) { + m_wasTiling = x; +} diff --git a/src/layout/target/Target.hpp b/src/layout/target/Target.hpp new file mode 100644 index 00000000..dcaefdb4 --- /dev/null +++ b/src/layout/target/Target.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include "../../helpers/math/Math.hpp" +#include "../../helpers/memory/Memory.hpp" +#include "../../desktop/Workspace.hpp" + +#include +#include + +namespace Layout { + enum eTargetType : uint8_t { + TARGET_TYPE_WINDOW = 0, + TARGET_TYPE_GROUP, + }; + + enum eGeometryFailure : uint8_t { + GEOMETRY_NO_DESIRED = 0, + GEOMETRY_INVALID_DESIRED = 1, + }; + + class CSpace; + + struct SGeometryRequested { + Vector2D size; + std::optional pos; + }; + + class ITarget { + public: + virtual ~ITarget() = default; + + virtual eTargetType type() = 0; + + // position is within its space + virtual void setPositionGlobal(const CBox& box); + virtual CBox position() const; + virtual void assignToSpace(const SP& space, std::optional focalPoint = std::nullopt); + virtual void setSpaceGhost(const SP& space); + virtual SP space() const; + virtual PHLWORKSPACE workspace() const; + virtual PHLWINDOW window() const = 0; + virtual void recalc(); + virtual bool wasTiling() const; + virtual void setWasTiling(bool x); + + virtual void rememberFloatingSize(const Vector2D& size); + virtual Vector2D lastFloatingSize() const; + + virtual void setPseudo(bool x); + virtual bool isPseudo() const; + virtual void setPseudoSize(const Vector2D& size); + virtual Vector2D pseudoSize(); + virtual void swap(SP b); + + // + virtual bool floating() = 0; + virtual void setFloating(bool x) = 0; + virtual std::expected desiredGeometry() = 0; + virtual eFullscreenMode fullscreenMode() = 0; + virtual void setFullscreenMode(eFullscreenMode mode) = 0; + virtual std::optional minSize() = 0; + virtual std::optional maxSize() = 0; + virtual void damageEntire() = 0; + virtual void warpPositionSize() = 0; + virtual void onUpdateSpace() = 0; + + protected: + ITarget() = default; + + CBox m_box; + SP m_space; + WP m_self; + Vector2D m_floatingSize; + bool m_pseudo = false; + bool m_ghostSpace = false; // ghost space means a target belongs to a space, but isn't sent to the layout + Vector2D m_pseudoSize = {1280, 720}; + bool m_wasTiling = false; + }; +}; \ No newline at end of file diff --git a/src/layout/target/WindowGroupTarget.cpp b/src/layout/target/WindowGroupTarget.cpp new file mode 100644 index 00000000..ae883751 --- /dev/null +++ b/src/layout/target/WindowGroupTarget.cpp @@ -0,0 +1,92 @@ +#include "WindowGroupTarget.hpp" + +#include "../space/Space.hpp" +#include "../algorithm/Algorithm.hpp" +#include "WindowTarget.hpp" +#include "Target.hpp" + +#include "../../render/Renderer.hpp" + +using namespace Layout; + +SP CWindowGroupTarget::create(SP g) { + auto target = SP(new CWindowGroupTarget(g)); + target->m_self = target; + return target; +} + +CWindowGroupTarget::CWindowGroupTarget(SP g) : m_group(g) { + ; +} + +eTargetType CWindowGroupTarget::type() { + return TARGET_TYPE_GROUP; +} + +void CWindowGroupTarget::setPositionGlobal(const CBox& box) { + ITarget::setPositionGlobal(box); + + updatePos(); +} + +void CWindowGroupTarget::updatePos() { + for (const auto& w : m_group->windows()) { + w->m_target->setPositionGlobal(m_box); + } +} + +void CWindowGroupTarget::assignToSpace(const SP& space, std::optional focalPoint) { + ITarget::assignToSpace(space, focalPoint); + + m_group->updateWorkspace(space->workspace()); +} + +bool CWindowGroupTarget::floating() { + return m_group->current()->m_target->floating(); +} + +void CWindowGroupTarget::setFloating(bool x) { + for (const auto& w : m_group->windows()) { + w->m_target->setFloating(x); + } +} + +std::expected CWindowGroupTarget::desiredGeometry() { + return m_group->current()->m_target->desiredGeometry(); +} + +PHLWINDOW CWindowGroupTarget::window() const { + return m_group->current(); +} + +eFullscreenMode CWindowGroupTarget::fullscreenMode() { + return m_group->current()->m_fullscreenState.internal; +} + +void CWindowGroupTarget::setFullscreenMode(eFullscreenMode mode) { + m_group->current()->m_fullscreenState.internal = mode; +} + +std::optional CWindowGroupTarget::minSize() { + return m_group->current()->minSize(); +} + +std::optional CWindowGroupTarget::maxSize() { + return m_group->current()->maxSize(); +} + +void CWindowGroupTarget::damageEntire() { + g_pHyprRenderer->damageWindow(m_group->current()); +} + +void CWindowGroupTarget::warpPositionSize() { + for (const auto& w : m_group->windows()) { + w->m_target->warpPositionSize(); + } +} + +void CWindowGroupTarget::onUpdateSpace() { + for (const auto& w : m_group->windows()) { + w->m_target->onUpdateSpace(); + } +} diff --git a/src/layout/target/WindowGroupTarget.hpp b/src/layout/target/WindowGroupTarget.hpp new file mode 100644 index 00000000..3d4b85a0 --- /dev/null +++ b/src/layout/target/WindowGroupTarget.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include "Target.hpp" + +#include "../../desktop/view/Window.hpp" +#include "../../desktop/view/Group.hpp" + +namespace Layout { + + class CWindowGroupTarget : public ITarget { + public: + static SP create(SP g); + virtual ~CWindowGroupTarget() = default; + + virtual eTargetType type(); + + virtual void setPositionGlobal(const CBox& box); + virtual void assignToSpace(const SP& space, std::optional focalPoint = std::nullopt); + virtual PHLWINDOW window() const; + + virtual bool floating(); + virtual void setFloating(bool x); + virtual std::expected desiredGeometry(); + virtual eFullscreenMode fullscreenMode(); + virtual void setFullscreenMode(eFullscreenMode mode); + virtual std::optional minSize(); + virtual std::optional maxSize(); + virtual void damageEntire(); + virtual void warpPositionSize(); + virtual void onUpdateSpace(); + + private: + CWindowGroupTarget(SP g); + + void updatePos(); + + WP m_group; + }; +}; \ No newline at end of file diff --git a/src/layout/target/WindowTarget.cpp b/src/layout/target/WindowTarget.cpp new file mode 100644 index 00000000..db03a385 --- /dev/null +++ b/src/layout/target/WindowTarget.cpp @@ -0,0 +1,382 @@ +#include "WindowTarget.hpp" + +#include "../space/Space.hpp" +#include "../algorithm/Algorithm.hpp" + +#include "../../protocols/core/Compositor.hpp" +#include "../../config/ConfigManager.hpp" +#include "../../helpers/Monitor.hpp" +#include "../../xwayland/XSurface.hpp" +#include "../../Compositor.hpp" +#include "../../render/Renderer.hpp" + +#include + +using namespace Hyprutils::Utils; +using namespace Layout; + +SP CWindowTarget::create(PHLWINDOW w) { + auto target = SP(new CWindowTarget(w)); + target->m_self = target; + return target; +} + +CWindowTarget::CWindowTarget(PHLWINDOW w) : m_window(w) { + ; +} + +eTargetType CWindowTarget::type() { + return TARGET_TYPE_WINDOW; +} + +void CWindowTarget::setPositionGlobal(const CBox& box) { + ITarget::setPositionGlobal(box); + + updatePos(); +} + +void CWindowTarget::updatePos() { + + g_pHyprRenderer->damageWindow(m_window.lock()); + CScopeGuard x([this] { g_pHyprRenderer->damageWindow(m_window.lock()); }); + + if (!m_space) + return; + + if (fullscreenMode() == FSMODE_FULLSCREEN) + return; + + if (floating() && fullscreenMode() != FSMODE_MAXIMIZED) { + m_window->m_position = m_box.pos(); + m_window->m_size = m_box.size(); + + *m_window->m_realPosition = m_box.pos(); + *m_window->m_realSize = m_box.size(); + + m_window->sendWindowSize(); + m_window->updateWindowDecos(); + + return; + } + + // Tiled is more complicated. + + // if we are in maximized, force the box to be max work area. + // TODO: this shouldn't be here. + if (fullscreenMode() == FSMODE_MAXIMIZED) + ITarget::setPositionGlobal(m_space->workArea(floating())); + + const auto PMONITOR = m_space->workspace()->m_monitor; + const auto PWORKSPACE = m_space->workspace(); + + // for gaps outer + const auto MONITOR_WORKAREA = m_space->workArea(); + const bool DISPLAYLEFT = STICKS(m_box.x, MONITOR_WORKAREA.x); + const bool DISPLAYRIGHT = STICKS(m_box.x + m_box.w, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w); + const bool DISPLAYTOP = STICKS(m_box.y, MONITOR_WORKAREA.y); + const bool DISPLAYBOTTOM = STICKS(m_box.y + m_box.h, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h); + + // this is used for scrolling, so that the gaps are correct when a window is the full width and has neighbors + const bool DISPLAYINVERSELEFT = STICKS(m_box.x, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w); + const bool DISPLAYINVERSERIGHT = STICKS(m_box.x + m_box.w, MONITOR_WORKAREA.x); + + // get specific gaps and rules for this workspace, + // if user specified them in config + const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(PWORKSPACE); + + if (!validMapped(m_window)) { + if (m_window) + g_layoutManager->removeTarget(m_window->layoutTarget()); + return; + } + + if (fullscreenMode() == FSMODE_FULLSCREEN) + return; + + g_pHyprRenderer->damageWindow(window()); + + static auto PGAPSINDATA = CConfigValue("general:gaps_in"); + auto* const PGAPSIN = sc((PGAPSINDATA.ptr())->getData()); + + auto gapsIn = WORKSPACERULE.gapsIn.value_or(*PGAPSIN); + CBox nodeBox = m_box; + nodeBox.round(); + + m_window->m_size = nodeBox.size(); + m_window->m_position = nodeBox.pos(); + + m_window->updateWindowDecos(); + + auto calcPos = m_window->m_position; + auto calcSize = m_window->m_size; + + const static auto REQUESTEDRATIO = CConfigValue("layout:single_window_aspect_ratio"); + const static auto REQUESTEDRATIOTOLERANCE = CConfigValue("layout:single_window_aspect_ratio_tolerance"); + + Vector2D ratioPadding; + + if ((*REQUESTEDRATIO).y != 0 && m_space->algorithm()->tiledTargets() <= 1 && fullscreenMode() == FSMODE_NONE) { + const Vector2D originalSize = MONITOR_WORKAREA.size(); + + const double requestedRatio = (*REQUESTEDRATIO).x / (*REQUESTEDRATIO).y; + const double originalRatio = originalSize.x / originalSize.y; + + if (requestedRatio > originalRatio) { + double padding = originalSize.y - (originalSize.x / requestedRatio); + + if (padding / 2 > (*REQUESTEDRATIOTOLERANCE) * originalSize.y) + ratioPadding = Vector2D{0., padding}; + } else if (requestedRatio < originalRatio) { + double padding = originalSize.x - (originalSize.y * requestedRatio); + + if (padding / 2 > (*REQUESTEDRATIOTOLERANCE) * originalSize.x) + ratioPadding = Vector2D{padding, 0.}; + } + } + + const auto GAPOFFSETTOPLEFT = Vector2D(sc(DISPLAYLEFT ? 0 : (DISPLAYINVERSELEFT ? 2 * gapsIn.m_left : gapsIn.m_left)), sc(DISPLAYTOP ? 0 : gapsIn.m_top)); + + const auto GAPOFFSETBOTTOMRIGHT = + Vector2D(sc(DISPLAYRIGHT ? 0 : (DISPLAYINVERSERIGHT ? 2 * gapsIn.m_right : gapsIn.m_right)), sc(DISPLAYBOTTOM ? 0 : gapsIn.m_bottom)); + + calcPos = calcPos + GAPOFFSETTOPLEFT + ratioPadding / 2; + calcSize = calcSize - GAPOFFSETTOPLEFT - GAPOFFSETBOTTOMRIGHT - ratioPadding; + + if (isPseudo() && fullscreenMode() == FSMODE_NONE) { + // Calculate pseudo + float scale = 1; + + // adjust if doesn't fit + if (m_pseudoSize.x > calcSize.x || m_pseudoSize.y > calcSize.y) { + if (m_pseudoSize.x > calcSize.x) + scale = calcSize.x / m_pseudoSize.x; + + if (m_pseudoSize.y * scale > calcSize.y) + scale = calcSize.y / m_pseudoSize.y; + + auto DELTA = calcSize - m_pseudoSize * scale; + calcSize = m_pseudoSize * scale; + calcPos = calcPos + DELTA / 2.f; // center + } else { + auto DELTA = calcSize - m_pseudoSize; + calcPos = calcPos + DELTA / 2.f; // center + calcSize = m_pseudoSize; + } + } + + const auto RESERVED = m_window->getFullWindowReservedArea(); + calcPos = calcPos + RESERVED.topLeft; + calcSize = calcSize - (RESERVED.topLeft + RESERVED.bottomRight); + + Vector2D availableSpace = calcSize; + + static auto PCLAMP_TILED = CConfigValue("misc:size_limits_tiled"); + + if (*PCLAMP_TILED) { + Vector2D minSize = m_window->m_ruleApplicator->minSize().valueOr(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}); + Vector2D maxSize = m_window->isFullscreen() ? Vector2D{INFINITY, INFINITY} : m_window->m_ruleApplicator->maxSize().valueOr(Vector2D{INFINITY, INFINITY}); + calcSize = calcSize.clamp(minSize, maxSize); + + calcPos += (availableSpace - calcSize) / 2.0; + + calcPos.x = std::clamp(calcPos.x, MONITOR_WORKAREA.x, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w - calcSize.x); + calcPos.y = std::clamp(calcPos.y, MONITOR_WORKAREA.y, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h - calcSize.y); + } + + if (m_window->onSpecialWorkspace() && !m_window->isFullscreen()) { + // if special, we adjust the coords a bit + static auto PSCALEFACTOR = CConfigValue("dwindle:special_scale_factor"); + + CBox wb = {calcPos + (calcSize - calcSize * *PSCALEFACTOR) / 2.f, calcSize * *PSCALEFACTOR}; + wb.round(); // avoid rounding mess + + *m_window->m_realPosition = wb.pos(); + *m_window->m_realSize = wb.size(); + } else { + CBox wb = {calcPos, calcSize}; + wb.round(); // avoid rounding mess + + *m_window->m_realSize = wb.size(); + *m_window->m_realPosition = wb.pos(); + } + + m_window->updateWindowDecos(); +} + +void CWindowTarget::assignToSpace(const SP& space, std::optional focalPoint) { + if (!space) { + ITarget::assignToSpace(space, focalPoint); + return; + } + + // keep the ref here so that moveToWorkspace doesn't unref the workspace + // and assignToSpace doesn't think this is a new target because space wp is dead + const auto WSREF = space->workspace(); + + m_window->m_monitor = space->workspace()->m_monitor; + m_window->moveToWorkspace(space->workspace()); + + // layout and various update fns want the target to already have m_workspace set + ITarget::assignToSpace(space, focalPoint); + + m_window->updateToplevel(); + m_window->updateWindowDecos(); +} + +bool CWindowTarget::floating() { + return m_window->m_isFloating; +} + +void CWindowTarget::setFloating(bool x) { + if (x == m_window->m_isFloating) + return; + + m_window->m_isFloating = x; + m_window->m_pinned = false; + + m_window->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_FLOATING); +} + +Vector2D CWindowTarget::clampSizeForDesired(const Vector2D& size) const { + Vector2D newSize = size; + if (const auto m = m_window->minSize(); m) + newSize = newSize.clamp(*m); + if (const auto m = m_window->maxSize(); m) + newSize = newSize.clamp(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}, *m); + return newSize; +} + +std::expected CWindowTarget::desiredGeometry() { + + SGeometryRequested requested; + + CBox DESIRED_GEOM = g_pXWaylandManager->getGeometryForWindow(m_window.lock()); + const auto PMONITOR = m_window->m_monitor.lock(); + + requested.size = clampSizeForDesired(DESIRED_GEOM.size()); + + if (m_window->m_isX11) { + Vector2D xy = {DESIRED_GEOM.x, DESIRED_GEOM.y}; + xy = g_pXWaylandManager->xwaylandToWaylandCoords(xy); + requested.pos = xy; + DESIRED_GEOM.x = xy.x; + DESIRED_GEOM.y = xy.y; + } + + const auto STOREDSIZE = m_window->m_ruleApplicator->persistentSize().valueOrDefault() ? g_pConfigManager->getStoredFloatingSize(m_window.lock()) : std::nullopt; + + if (STOREDSIZE) + requested.size = clampSizeForDesired(*STOREDSIZE); + + if (!PMONITOR) { + Log::logger->log(Log::ERR, "{:m} has an invalid monitor in desiredGeometry!", m_window.lock()); + return std::unexpected(GEOMETRY_NO_DESIRED); + } + + static auto PXWLFORCESCALEZERO = CConfigValue("xwayland:force_zero_scaling"); + const auto toLogical = [&](SGeometryRequested& req) { + if (m_window->m_isX11 && *PXWLFORCESCALEZERO && PMONITOR) + req.size /= PMONITOR->m_scale; + }; + + if (DESIRED_GEOM.width <= 2 || DESIRED_GEOM.height <= 2) { + const auto SURFACE = m_window->wlSurface()->resource(); + + if (SURFACE->m_current.size.x > 5 && SURFACE->m_current.size.y > 5) { + // center on mon and call it a day + requested.pos.reset(); + requested.size = clampSizeForDesired(SURFACE->m_current.size); + toLogical(requested); + return requested; + } + + if (m_window->m_isX11 && m_window->isX11OverrideRedirect()) { + // check OR windows, they like their shit + const auto SIZE = clampSizeForDesired(m_window->m_xwaylandSurface->m_geometry.w > 0 && m_window->m_xwaylandSurface->m_geometry.h > 0 ? + m_window->m_xwaylandSurface->m_geometry.size() : + Vector2D{600, 400}); + + if (m_window->m_xwaylandSurface->m_geometry.x != 0 && m_window->m_xwaylandSurface->m_geometry.y != 0) { + requested.size = SIZE; + requested.pos = g_pXWaylandManager->xwaylandToWaylandCoords(m_window->m_xwaylandSurface->m_geometry.pos()); + toLogical(requested); + return requested; + } + } + + return std::unexpected(m_window->m_isX11 && m_window->isX11OverrideRedirect() ? GEOMETRY_INVALID_DESIRED : GEOMETRY_NO_DESIRED); + } + + // TODO: detect a popup in a more consistent way. + if ((DESIRED_GEOM.x == 0 && DESIRED_GEOM.y == 0) || !m_window->m_isX11) { + // middle of parent if available + if (!m_window->m_isX11) { + if (const auto PARENT = m_window->parent(); PARENT) { + const auto POS = PARENT->m_realPosition->goal() + PARENT->m_realSize->goal() / 2.F - DESIRED_GEOM.size() / 2.F; + requested.pos = POS; + } + } + } else { + // if it is, we respect where it wants to put itself, but apply monitor offset if outside + // most of these are popups + + Vector2D pos; + + if (const auto POPENMON = g_pCompositor->getMonitorFromVector(DESIRED_GEOM.middle()); POPENMON->m_id != PMONITOR->m_id) + pos = Vector2D(DESIRED_GEOM.x, DESIRED_GEOM.y) - POPENMON->m_position + PMONITOR->m_position; + else + pos = Vector2D(DESIRED_GEOM.x, DESIRED_GEOM.y); + + requested.pos = pos; + } + + if (DESIRED_GEOM.w <= 2 || DESIRED_GEOM.h <= 2) + return std::unexpected(GEOMETRY_NO_DESIRED); + + toLogical(requested); + return requested; +} + +PHLWINDOW CWindowTarget::window() const { + return m_window.lock(); +} + +eFullscreenMode CWindowTarget::fullscreenMode() { + return m_window->m_fullscreenState.internal; +} + +void CWindowTarget::setFullscreenMode(eFullscreenMode mode) { + if (floating() && m_window->m_fullscreenState.internal == FSMODE_NONE) + rememberFloatingSize(m_box.size()); + + m_window->m_fullscreenState.internal = mode; +} + +std::optional CWindowTarget::minSize() { + return m_window->minSize(); +} + +std::optional CWindowTarget::maxSize() { + return m_window->maxSize(); +} + +void CWindowTarget::damageEntire() { + g_pHyprRenderer->damageWindow(m_window.lock()); +} + +void CWindowTarget::warpPositionSize() { + m_window->m_realSize->warp(); + m_window->m_realPosition->warp(); + m_window->updateWindowDecos(); +} + +void CWindowTarget::onUpdateSpace() { + if (!space()) + return; + + m_window->m_monitor = space()->workspace()->m_monitor; + m_window->moveToWorkspace(space()->workspace()); + m_window->updateToplevel(); + m_window->updateWindowData(); + m_window->updateWindowDecos(); +} diff --git a/src/layout/target/WindowTarget.hpp b/src/layout/target/WindowTarget.hpp new file mode 100644 index 00000000..2939fd74 --- /dev/null +++ b/src/layout/target/WindowTarget.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include "Target.hpp" + +#include "../../desktop/view/Window.hpp" + +namespace Layout { + + class CWindowTarget : public ITarget { + public: + static SP create(PHLWINDOW w); + virtual ~CWindowTarget() = default; + + virtual eTargetType type(); + + virtual void setPositionGlobal(const CBox& box); + virtual void assignToSpace(const SP& space, std::optional focalPoint = std::nullopt); + virtual PHLWINDOW window() const; + + virtual bool floating(); + virtual void setFloating(bool x); + virtual std::expected desiredGeometry(); + virtual eFullscreenMode fullscreenMode(); + virtual void setFullscreenMode(eFullscreenMode mode); + virtual std::optional minSize(); + virtual std::optional maxSize(); + virtual void damageEntire(); + virtual void warpPositionSize(); + virtual void onUpdateSpace(); + + private: + CWindowTarget(PHLWINDOW w); + + Vector2D clampSizeForDesired(const Vector2D& size) const; + + void updatePos(); + + PHLWINDOWREF m_window; + }; +}; \ No newline at end of file diff --git a/src/macros.hpp b/src/macros.hpp index 1b55bacd..fc109296 100644 --- a/src/macros.hpp +++ b/src/macros.hpp @@ -96,10 +96,13 @@ #define GLCALL(__CALL__) \ { \ __CALL__; \ - auto err = glGetError(); \ - if (err != GL_NO_ERROR) { \ - Log::logger->log(Log::ERR, "[GLES] Error in call at {}@{}: 0x{:x}", __LINE__, \ - ([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })(), err); \ + static const auto GLDEBUG = CConfigValue("debug:gl_debugging"); \ + if (*GLDEBUG) { \ + auto err = glGetError(); \ + if (err != GL_NO_ERROR) { \ + Log::logger->log(Log::ERR, "[GLES] Error in call at {}@{}: 0x{:x}", __LINE__, \ + ([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })(), err); \ + } \ } \ } diff --git a/src/main.cpp b/src/main.cpp index a499bd48..99e64675 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -31,6 +31,8 @@ static void help() { --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) + --watchdog-fd FD - Used by start-hyprland + --safe-mode - Starts Hyprland in safe mode --systeminfo - Prints system infos --i-am-really-stupid - Omits root user privileges check (why would you do that?) --verify-config - Do not run Hyprland, only print if the config has any errors @@ -219,7 +221,7 @@ int main(int argc, char** argv) { if (safeMode) g_pCompositor->m_safeMode = true; - if (!watchdogOk) + if (!watchdogOk && !verifyConfig) Log::logger->log(Log::WARN, "WARNING: Hyprland is being launched without start-hyprland. This is highly advised against."); g_pCompositor->initServer(socketName, socketFd); diff --git a/src/managers/ANRManager.cpp b/src/managers/ANRManager.cpp index a9cff74f..9f613df8 100644 --- a/src/managers/ANRManager.cpp +++ b/src/managers/ANRManager.cpp @@ -3,13 +3,13 @@ #include "../helpers/fs/FsUtils.hpp" #include "../debug/log/Logger.hpp" #include "../macros.hpp" -#include "HookSystemManager.hpp" #include "../Compositor.hpp" #include "../protocols/XDGShell.hpp" #include "./eventLoop/EventLoopManager.hpp" #include "../config/ConfigValue.hpp" #include "../xwayland/XSurface.hpp" #include "../i18n/Engine.hpp" +#include "../event/EventBus.hpp" using namespace Hyprutils::OS; @@ -26,9 +26,7 @@ CANRManager::CANRManager() { m_active = true; - static auto P = g_pHookSystem->hookDynamic("openWindow", [this](void* self, SCallbackInfo& info, std::any data) { - auto window = std::any_cast(data); - + static auto P = Event::bus()->m_events.window.open.listen([this](PHLWINDOW window) { for (const auto& d : m_data) { // Window is ANR dialog if (d->isRunning() && d->dialogBox->getPID() == window->getPID()) @@ -41,9 +39,7 @@ CANRManager::CANRManager() { m_data.emplace_back(makeShared(window)); }); - static auto P1 = g_pHookSystem->hookDynamic("closeWindow", [this](void* self, SCallbackInfo& info, std::any data) { - auto window = std::any_cast(data); - + static auto P1 = Event::bus()->m_events.window.close.listen([this](PHLWINDOW window) { for (const auto& d : m_data) { if (!d->fitsWindow(window)) continue; @@ -53,8 +49,9 @@ CANRManager::CANRManager() { d->killDialog(); d->missedResponses = 0; d->dialogSaidWait = false; - return; } + + std::erase_if(m_data, [&window](auto& w) { return w == window; }); }); m_timer->updateTimeout(TIMER_TIMEOUT); @@ -188,21 +185,25 @@ void CANRManager::SANRData::runDialog(const std::string& appName, const std::str const auto OPTION_TERMINATE_STR = I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_OPTION_TERMINATE, {}); const auto OPTION_WAIT_STR = I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_OPTION_WAIT, {}); + const auto OPTIONS = std::vector{OPTION_TERMINATE_STR, OPTION_WAIT_STR}; + const auto CLASS_STR = appClass.empty() ? I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_PROP_UNKNOWN, {}) : appClass; + const auto TITLE_STR = appName.empty() ? I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_PROP_UNKNOWN, {}) : appName; + const auto DESCRIPTION_STR = I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_CONTENT, {{"title", TITLE_STR}, {"class", CLASS_STR}}); - dialogBox = - CAsyncDialogBox::create(I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_TITLE, {}), - I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_CONTENT, - { - // - {"class", appClass.empty() ? I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_PROP_UNKNOWN, {}) : appClass}, // - {"title", appName.empty() ? I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_PROP_UNKNOWN, {}) : appName} // - }), - std::vector{ - // - OPTION_TERMINATE_STR, // - OPTION_WAIT_STR // - } // - ); + dialogBox = CAsyncDialogBox::create(I18n::i18nEngine()->localize(I18n::TXT_KEY_ANR_TITLE, {}), DESCRIPTION_STR, OPTIONS); + + for (const auto& w : g_pCompositor->m_windows) { + if (!w->m_isMapped) + continue; + + if (!fitsWindow(w)) + continue; + + if (w->m_workspace) + dialogBox->setExecRule(std::format("workspace {} silent", w->m_workspace->getConfigName())); + + break; + } dialogBox->open()->then([dialogWmPID, this, OPTION_TERMINATE_STR, OPTION_WAIT_STR](SP> r) { if (r->hasError()) { diff --git a/src/managers/CursorManager.cpp b/src/managers/CursorManager.cpp index 8392db0a..7564ca75 100644 --- a/src/managers/CursorManager.cpp +++ b/src/managers/CursorManager.cpp @@ -3,8 +3,8 @@ #include "../config/ConfigValue.hpp" #include "PointerManager.hpp" #include "../xwayland/XWayland.hpp" -#include "../managers/HookSystemManager.hpp" #include "../helpers/Monitor.hpp" +#include "../event/EventBus.hpp" static int cursorAnimTimer(SP self, void* data) { const auto cursorMgr = sc(data); @@ -111,7 +111,7 @@ CCursorManager::CCursorManager() { updateTheme(); - static auto P = g_pHookSystem->hookDynamic("monitorLayoutChanged", [this](void* self, SCallbackInfo& info, std::any param) { this->updateTheme(); }); + static auto P = Event::bus()->m_events.monitor.layoutChanged.listen([this] { this->updateTheme(); }); } CCursorManager::~CCursorManager() { diff --git a/src/managers/HookSystemManager.cpp b/src/managers/HookSystemManager.cpp deleted file mode 100644 index 0aa2d93e..00000000 --- a/src/managers/HookSystemManager.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include "HookSystemManager.hpp" - -#include "../plugins/PluginSystem.hpp" - -CHookSystemManager::CHookSystemManager() { - ; // -} - -// returns the pointer to the function -SP CHookSystemManager::hookDynamic(const std::string& event, HOOK_CALLBACK_FN fn, HANDLE handle) { - SP hookFN = makeShared(fn); - m_registeredHooks[event].emplace_back(SCallbackFNPtr{.fn = hookFN, .handle = handle}); - return hookFN; -} - -void CHookSystemManager::unhook(SP fn) { - for (auto& [k, v] : m_registeredHooks) { - std::erase_if(v, [&](const auto& other) { - SP fn_ = other.fn.lock(); - - return fn_.get() == fn.get(); - }); - } -} - -void CHookSystemManager::emit(std::vector* const callbacks, SCallbackInfo& info, std::any data) { - if (callbacks->empty()) - return; - - std::vector faultyHandles; - volatile bool needsDeadCleanup = false; - - for (auto const& cb : *callbacks) { - - m_currentEventPlugin = false; - - if (!cb.handle) { - // we don't guard hl hooks - - if (SP fn = cb.fn.lock()) - (*fn)(fn.get(), info, data); - else - needsDeadCleanup = true; - continue; - } - - m_currentEventPlugin = true; - - if (std::ranges::find(faultyHandles, cb.handle) != faultyHandles.end()) - continue; - - try { - if (!setjmp(m_hookFaultJumpBuf)) { - if (SP fn = cb.fn.lock()) - (*fn)(fn.get(), info, data); - else - needsDeadCleanup = true; - } else { - // this module crashed. - throw std::exception(); - } - } catch (std::exception& e) { - // TODO: this works only once...? - faultyHandles.push_back(cb.handle); - Log::logger->log(Log::ERR, "[hookSystem] Hook from plugin {:x} caused a SIGSEGV, queueing for unloading.", rc(cb.handle)); - } - } - - if (needsDeadCleanup) - std::erase_if(*callbacks, [](const auto& fn) { return !fn.fn.lock(); }); - - if (!faultyHandles.empty()) { - for (auto const& h : faultyHandles) - g_pPluginSystem->unloadPlugin(g_pPluginSystem->getPluginByHandle(h), true); - } -} - -std::vector* CHookSystemManager::getVecForEvent(const std::string& event) { - if (!m_registeredHooks.contains(event)) - Log::logger->log(Log::DEBUG, "[hookSystem] New hook event registered: {}", event); - - return &m_registeredHooks[event]; -} diff --git a/src/managers/HookSystemManager.hpp b/src/managers/HookSystemManager.hpp deleted file mode 100644 index 647e9670..00000000 --- a/src/managers/HookSystemManager.hpp +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once - -#include "../defines.hpp" - -#include -#include -#include -#include - -#include - -#define HANDLE void* - -// global type alias for hooked functions. Passes itself as a ptr when called, and `data` additionally. - -using HOOK_CALLBACK_FN = std::function; - -struct SCallbackFNPtr { - WP fn; - HANDLE handle = nullptr; -}; - -#define EMIT_HOOK_EVENT(name, param) \ - { \ - static auto* const PEVENTVEC = g_pHookSystem->getVecForEvent(name); \ - SCallbackInfo info; \ - g_pHookSystem->emit(PEVENTVEC, info, param); \ - } - -#define EMIT_HOOK_EVENT_CANCELLABLE(name, param) \ - { \ - static auto* const PEVENTVEC = g_pHookSystem->getVecForEvent(name); \ - SCallbackInfo info; \ - g_pHookSystem->emit(PEVENTVEC, info, param); \ - if (info.cancelled) \ - return; \ - } - -class CHookSystemManager { - public: - CHookSystemManager(); - - // returns the pointer to the function. - // losing this pointer (letting it get destroyed) - // will equal to unregistering the callback. - [[nodiscard("Losing this pointer instantly unregisters the callback")]] SP hookDynamic(const std::string& event, HOOK_CALLBACK_FN fn, - HANDLE handle = nullptr); - void unhook(SP fn); - - void emit(std::vector* const callbacks, SCallbackInfo& info, std::any data = 0); - std::vector* getVecForEvent(const std::string& event); - - bool m_currentEventPlugin = false; - jmp_buf m_hookFaultJumpBuf; - - private: - std::unordered_map> m_registeredHooks; -}; - -inline UP g_pHookSystem; diff --git a/src/managers/KeybindManager.cpp b/src/managers/KeybindManager.cpp index a709b0ca..fd7c4e72 100644 --- a/src/managers/KeybindManager.cpp +++ b/src/managers/KeybindManager.cpp @@ -16,16 +16,22 @@ #include "TokenManager.hpp" #include "eventLoop/EventLoopManager.hpp" #include "debug/log/Logger.hpp" -#include "../managers/HookSystemManager.hpp" #include "../managers/input/InputManager.hpp" #include "../managers/animation/DesktopAnimationManager.hpp" -#include "../managers/LayoutManager.hpp" #include "../managers/EventManager.hpp" #include "../render/Renderer.hpp" #include "../hyprerror/HyprError.hpp" #include "../config/ConfigManager.hpp" #include "../desktop/rule/windowRule/WindowRule.hpp" #include "../desktop/rule/Engine.hpp" +#include "../desktop/view/Group.hpp" +#include "../layout/LayoutManager.hpp" +#include "../layout/target/WindowTarget.hpp" +#include "../layout/space/Space.hpp" +#include "../layout/algorithm/Algorithm.hpp" +#include "../layout/algorithm/tiled/master/MasterAlgorithm.hpp" +#include "../layout/algorithm/tiled/monocle/MonocleAlgorithm.hpp" +#include "../event/EventBus.hpp" #include #include @@ -103,9 +109,6 @@ CKeybindManager::CKeybindManager() { m_dispatchers["togglegroup"] = toggleGroup; m_dispatchers["changegroupactive"] = changeGroupActive; m_dispatchers["movegroupwindow"] = moveGroupWindow; - m_dispatchers["togglesplit"] = toggleSplit; - m_dispatchers["swapsplit"] = swapSplit; - m_dispatchers["splitratio"] = alterSplitRatio; m_dispatchers["focusmonitor"] = focusMonitor; m_dispatchers["movecursortocorner"] = moveCursorToCorner; m_dispatchers["movecursor"] = moveCursor; @@ -197,8 +200,7 @@ CKeybindManager::CKeybindManager() { g_pEventLoopManager->addTimer(m_repeatKeyTimer); } - static auto P = g_pHookSystem->hookDynamic("configReloaded", [this](void* hk, SCallbackInfo& info, std::any param) { - // clear cuz realloc'd + static auto P = Event::bus()->m_events.config.reloaded.listen([this] { m_activeKeybinds.clear(); m_lastLongPressKeybind.reset(); m_pressedSpecialBinds.clear(); @@ -322,10 +324,10 @@ void CKeybindManager::updateXKBTranslationState() { } bool CKeybindManager::ensureMouseBindState() { - if (!g_pInputManager->m_currentlyDraggedWindow) + if (!g_layoutManager->dragController()->target()) return false; - if (!g_pInputManager->m_currentlyDraggedWindow.expired()) { + if (g_layoutManager->dragController()->target()) { changeMouseBindMode(MBIND_INVALID); return true; } @@ -368,7 +370,7 @@ bool CKeybindManager::tryMoveFocusToMonitor(PHLMONITOR monitor) { const auto PNEWWINDOW = PNEWWORKSPACE->getLastFocusedWindow(); if (PNEWWINDOW) { updateRelativeCursorCoords(); - Desktop::focusState()->fullWindowFocus(PNEWWINDOW); + Desktop::focusState()->fullWindowFocus(PNEWWINDOW, Desktop::FOCUS_REASON_KEYBIND); PNEWWINDOW->warpCursor(); if (*PNOWARPS == 0 || *PFOLLOWMOUSE < 2) { @@ -377,7 +379,7 @@ bool CKeybindManager::tryMoveFocusToMonitor(PHLMONITOR monitor) { g_pInputManager->m_forcedFocus.reset(); } } else { - Desktop::focusState()->rawWindowFocus(nullptr); + Desktop::focusState()->rawWindowFocus(nullptr, Desktop::FOCUS_REASON_KEYBIND); g_pCompositor->warpCursorTo(monitor->middle()); } Desktop::focusState()->rawMonitorFocus(monitor); @@ -398,10 +400,10 @@ void CKeybindManager::switchToWindow(PHLWINDOW PWINDOWTOCHANGETO, bool forceFSCy g_pInputManager->unconstrainMouse(); if (PLASTWINDOW && PLASTWINDOW->m_workspace == PWINDOWTOCHANGETO->m_workspace && PLASTWINDOW->isFullscreen()) - Desktop::focusState()->fullWindowFocus(PWINDOWTOCHANGETO, nullptr, forceFSCycle); + Desktop::focusState()->fullWindowFocus(PWINDOWTOCHANGETO, Desktop::FOCUS_REASON_KEYBIND, nullptr, forceFSCycle); else { updateRelativeCursorCoords(); - Desktop::focusState()->fullWindowFocus(PWINDOWTOCHANGETO, nullptr, forceFSCycle); + Desktop::focusState()->fullWindowFocus(PWINDOWTOCHANGETO, Desktop::FOCUS_REASON_KEYBIND, nullptr, forceFSCycle); PWINDOWTOCHANGETO->warpCursor(); // Move mouse focus to the new window if required by current follow_mouse and warp modes @@ -751,9 +753,9 @@ SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SP // Require mouse to stay inside drag_threshold for clicks, outside for drags // Check if either a mouse bind has triggered or currently over the threshold (maybe there is no mouse bind on the same key) const auto THRESHOLDREACHED = key.mousePosAtPress.distanceSq(g_pInputManager->getMouseCoordsInternal()) > std::pow(*PDRAGTHRESHOLD, 2); - if (k->click && (g_pInputManager->m_dragThresholdReached || THRESHOLDREACHED)) + if (k->click && (g_layoutManager->dragController()->dragThresholdReached() || THRESHOLDREACHED)) continue; - else if (k->drag && !g_pInputManager->m_dragThresholdReached && !THRESHOLDREACHED) + else if (k->drag && !g_layoutManager->dragController()->dragThresholdReached() && !THRESHOLDREACHED) continue; } @@ -810,7 +812,7 @@ SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SP found = true; } - g_pInputManager->m_dragThresholdReached = false; + g_layoutManager->dragController()->resetDragThresholdReached(); // if keybind wasn't found (or dispatcher said to) then pass event res.passEvent |= !found; @@ -910,11 +912,13 @@ 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)}; + const auto PROC = spawnWithRules(args, nullptr); + if (!PROC.has_value()) + return {.success = false, .error = std::format("Failed to start process. No closing bracket in exec rule. {}", args)}; + return {.success = PROC.value() > 0, .error = std::format("Failed to start process {}", args)}; } -uint64_t CKeybindManager::spawnWithRules(std::string args, PHLWORKSPACE pInitialWorkspace) { +std::optional CKeybindManager::spawnWithRules(std::string args, PHLWORKSPACE pInitialWorkspace) { args = trim(args); @@ -922,22 +926,29 @@ uint64_t CKeybindManager::spawnWithRules(std::string args, PHLWORKSPACE pInitial if (args[0] == '[') { // we have exec rules - RULES = args.substr(1, args.substr(1).find_first_of(']')); - args = args.substr(args.find_first_of(']') + 1); + const auto end = args.find_first_of(']'); + if (end == std::string::npos) + return std::nullopt; + + RULES = args.substr(1, end - 1); + args = args.substr(end + 1); } std::string execToken = ""; if (!RULES.empty()) { - auto rule = Desktop::Rule::CWindowRule::buildFromExecString(std::move(RULES)); + auto rule = Desktop::Rule::CWindowRule::buildFromExecString(std::move(RULES)); - execToken = rule->execToken(); + const auto TOKEN = g_pTokenManager->registerNewToken(nullptr, std::chrono::seconds(1)); + const uint64_t PROC = spawnRawProc(args, pInitialWorkspace, TOKEN); + rule->markAsExecRule(TOKEN, PROC, false /* TODO: could be nice. */); + rule->registerMatch(Desktop::Rule::RULE_PROP_EXEC_TOKEN, TOKEN); + rule->registerMatch(Desktop::Rule::RULE_PROP_EXEC_PID, std::to_string(PROC)); Desktop::Rule::ruleEngine()->registerRule(std::move(rule)); - Log::logger->log(Log::DEBUG, "Applied rule arguments for exec."); + return PROC; } - const uint64_t PROC = spawnRawProc(args, pInitialWorkspace, execToken); return PROC; @@ -1112,32 +1123,16 @@ static SDispatchResult toggleActiveFloatingCore(std::string args, std::optional< return {}; // remove drag status - if (!g_pInputManager->m_currentlyDraggedWindow.expired()) + if (g_layoutManager->dragController()->target()) CKeybindManager::changeMouseBindMode(MBIND_INVALID); - if (PWINDOW->m_groupData.pNextWindow.lock() && PWINDOW->m_groupData.pNextWindow.lock() != PWINDOW) { - const auto PCURRENT = PWINDOW->getGroupCurrent(); - - PCURRENT->m_isFloating = !PCURRENT->m_isFloating; - g_pLayoutManager->getCurrentLayout()->changeWindowFloatingMode(PCURRENT); - - PHLWINDOW curr = PCURRENT->m_groupData.pNextWindow.lock(); - while (curr != PCURRENT) { - curr->m_isFloating = PCURRENT->m_isFloating; - curr = curr->m_groupData.pNextWindow.lock(); - } - } else { - PWINDOW->m_isFloating = !PWINDOW->m_isFloating; - - g_pLayoutManager->getCurrentLayout()->changeWindowFloatingMode(PWINDOW); - } + g_layoutManager->changeFloatingMode(PWINDOW->layoutTarget()); if (PWINDOW->m_workspace) { PWINDOW->m_workspace->updateWindows(); PWINDOW->m_workspace->updateWindowData(); } - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->monitorID()); g_pCompositor->updateAllWindowsAnimatedDecorationValues(); return {}; @@ -1163,8 +1158,7 @@ SDispatchResult CKeybindManager::centerWindow(std::string args) { const auto PMONITOR = PWINDOW->m_monitor.lock(); - *PWINDOW->m_realPosition = PMONITOR->logicalBoxMinusReserved().middle() - PWINDOW->m_realSize->goal() / 2.f; - PWINDOW->m_position = PWINDOW->m_realPosition->goal(); + PWINDOW->layoutTarget()->setPositionGlobal(CBox{PMONITOR->logicalBoxMinusReserved().middle() - PWINDOW->m_realSize->goal() / 2.F, PWINDOW->layoutTarget()->position().size()}); return {}; } @@ -1180,10 +1174,7 @@ SDispatchResult CKeybindManager::toggleActivePseudo(std::string args) { if (!PWINDOW) return {.success = false, .error = "Window not found"}; - PWINDOW->m_isPseudotiled = !PWINDOW->m_isPseudotiled; - - if (!PWINDOW->isFullscreen()) - g_pLayoutManager->getCurrentLayout()->recalculateWindow(PWINDOW); + PWINDOW->layoutTarget()->setPseudo(!PWINDOW->layoutTarget()->isPseudo()); return {}; } @@ -1276,7 +1267,7 @@ SDispatchResult CKeybindManager::changeworkspace(std::string args) { if (PMONITOR != PMONITORWORKSPACEOWNER) { Vector2D middle = PMONITORWORKSPACEOWNER->middle(); if (const auto PLAST = pWorkspaceToChangeTo->getLastFocusedWindow(); PLAST) { - Desktop::focusState()->fullWindowFocus(PLAST); + Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_KEYBIND); if (*PWORKSPACECENTERON == 1) middle = PLAST->middle(); } @@ -1421,7 +1412,7 @@ SDispatchResult CKeybindManager::moveActiveToWorkspace(std::string args) { pMonitor->changeWorkspace(pWorkspace); - Desktop::focusState()->fullWindowFocus(PWINDOW); + Desktop::focusState()->fullWindowFocus(PWINDOW, Desktop::FOCUS_REASON_KEYBIND); PWINDOW->warpCursor(); return {}; @@ -1465,7 +1456,7 @@ SDispatchResult CKeybindManager::moveActiveToWorkspaceSilent(std::string args) { if (const auto PATCOORDS = g_pCompositor->vectorToWindowUnified(OLDMIDDLE, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING, PWINDOW); PATCOORDS) - Desktop::focusState()->fullWindowFocus(PATCOORDS); + Desktop::focusState()->fullWindowFocus(PATCOORDS, Desktop::FOCUS_REASON_KEYBIND); else g_pInputManager->refocus(); } @@ -1474,38 +1465,37 @@ SDispatchResult CKeybindManager::moveActiveToWorkspaceSilent(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"); - static auto PGROUPCYCLE = CConfigValue("binds:movefocus_cycles_groupfirst"); - char arg = args[0]; + static auto PFULLCYCLE = CConfigValue("binds:movefocus_cycles_fullscreen"); + static auto PGROUPCYCLE = CConfigValue("binds:movefocus_cycles_groupfirst"); + static auto PMONITORFALLBACK = CConfigValue("binds:window_direction_monitor_fallback"); + Math::eDirection dir = Math::fromChar(args[0]); - if (!isDirection(args)) { - Log::logger->log(Log::ERR, "Cannot move focus in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg); - return {.success = false, .error = std::format("Cannot move focus in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg)}; + if (dir == Math::DIRECTION_DEFAULT) { + Log::logger->log(Log::ERR, "Cannot move focus in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0]); + return {.success = false, .error = std::format("Cannot move focus in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0])}; } const auto PLASTWINDOW = Desktop::focusState()->window(); if (!PLASTWINDOW || !PLASTWINDOW->aliveAndVisible()) { if (*PMONITORFALLBACK) - tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(arg)); - + tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(dir)); return {}; } const auto PWINDOWTOCHANGETO = *PFULLCYCLE && PLASTWINDOW->isFullscreen() ? - g_pCompositor->getWindowCycle(PLASTWINDOW, true, {}, false, arg != 'd' && arg != 'b' && arg != 'r') : - g_pCompositor->getWindowInDirection(PLASTWINDOW, arg); + g_pCompositor->getWindowCycle(PLASTWINDOW, true, {}, false, dir != Math::DIRECTION_DOWN && dir != Math::DIRECTION_RIGHT) : + g_pCompositor->getWindowInDirection(PLASTWINDOW, dir); // Prioritize focus change within groups if the window is a part of it. - if (*PGROUPCYCLE && PLASTWINDOW->m_groupData.pNextWindow) { + if (*PGROUPCYCLE && PLASTWINDOW->m_group) { auto isTheOnlyGroupOnWs = !PWINDOWTOCHANGETO && g_pCompositor->m_monitors.size() == 1; - if (arg == 'l' && (PLASTWINDOW != PLASTWINDOW->getGroupHead() || isTheOnlyGroupOnWs)) { - PLASTWINDOW->setGroupCurrent(PLASTWINDOW->getGroupPrevious()); + if (dir == Math::DIRECTION_LEFT && (PLASTWINDOW != PLASTWINDOW->m_group->head() || isTheOnlyGroupOnWs)) { + PLASTWINDOW->m_group->moveCurrent(false); return {}; } - else if (arg == 'r' && (PLASTWINDOW != PLASTWINDOW->getGroupTail() || isTheOnlyGroupOnWs)) { - PLASTWINDOW->setGroupCurrent(PLASTWINDOW->m_groupData.pNextWindow.lock()); + else if (dir == Math::DIRECTION_RIGHT && (PLASTWINDOW != PLASTWINDOW->m_group->tail() || isTheOnlyGroupOnWs)) { + PLASTWINDOW->m_group->moveCurrent(true); return {}; } } @@ -1516,52 +1506,51 @@ SDispatchResult CKeybindManager::moveFocusTo(std::string args) { return {}; } - Log::logger->log(Log::DEBUG, "No window found in direction {}, looking for a monitor", arg); + Log::logger->log(Log::DEBUG, "No window found in direction {}, looking for a monitor", Math::toString(dir)); - if (*PMONITORFALLBACK && tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(arg))) + if (*PMONITORFALLBACK && tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(dir))) return {}; static auto PNOFALLBACK = CConfigValue("general:no_focus_fallback"); if (*PNOFALLBACK) - return {.success = false, .error = std::format("Nothing to focus to in direction {}", arg)}; + return {.success = false, .error = std::format("Nothing to focus to in direction {}", Math::toString(dir))}; - Log::logger->log(Log::DEBUG, "No monitor found in direction {}, getting the inverse edge", arg); + Log::logger->log(Log::DEBUG, "No monitor found in direction {}, getting the inverse edge", Math::toString(dir)); const auto PMONITOR = PLASTWINDOW->m_monitor.lock(); if (!PMONITOR) return {.success = false, .error = "last window has no monitor?"}; - if (arg == 'l' || arg == 'r') { + if (dir == Math::DIRECTION_LEFT || dir == Math::DIRECTION_RIGHT) { if (STICKS(PLASTWINDOW->m_position.x, PMONITOR->m_position.x) && STICKS(PLASTWINDOW->m_size.x, PMONITOR->m_size.x)) return {.success = false, .error = "move does not make sense, would return back"}; } else if (STICKS(PLASTWINDOW->m_position.y, PMONITOR->m_position.y) && STICKS(PLASTWINDOW->m_size.y, PMONITOR->m_size.y)) return {.success = false, .error = "move does not make sense, would return back"}; CBox box = PMONITOR->logicalBox(); - switch (arg) { - case 'l': + switch (dir) { + case Math::DIRECTION_LEFT: box.x += box.w; box.w = 1; break; - case 'r': + case Math::DIRECTION_RIGHT: box.x -= 1; box.w = 1; break; - case 'u': - case 't': + case Math::DIRECTION_UP: box.y += box.h; box.h = 1; break; - case 'd': - case 'b': + case Math::DIRECTION_DOWN: box.y -= 1; box.h = 1; break; + default: break; } const auto PWINDOWCANDIDATE = g_pCompositor->getWindowInDirection(box, PMONITOR->m_activeSpecialWorkspace ? PMONITOR->m_activeSpecialWorkspace : PMONITOR->m_activeWorkspace, - arg, PLASTWINDOW, PLASTWINDOW->m_isFloating); + dir, PLASTWINDOW, PLASTWINDOW->m_isFloating); if (PWINDOWCANDIDATE) switchToWindow(PWINDOWCANDIDATE); @@ -1598,7 +1587,6 @@ SDispatchResult CKeybindManager::focusCurrentOrLast(std::string args) { } SDispatchResult CKeybindManager::swapActive(std::string args) { - char arg = args[0]; const auto PLASTWINDOW = Desktop::focusState()->window(); PHLWINDOW PWINDOWTOCHANGETO = nullptr; @@ -1608,9 +1596,10 @@ SDispatchResult CKeybindManager::swapActive(std::string args) { if (PLASTWINDOW->isFullscreen()) return {.success = false, .error = "Can't swap fullscreen window"}; - if (isDirection(args)) - PWINDOWTOCHANGETO = g_pCompositor->getWindowInDirection(PLASTWINDOW, arg); - else + if (isDirection(args)) { + Math::eDirection dir = Math::fromChar(args[0]); + PWINDOWTOCHANGETO = g_pCompositor->getWindowInDirection(PLASTWINDOW, dir); + } else PWINDOWTOCHANGETO = g_pCompositor->getWindowByRegex(args); if (!PWINDOWTOCHANGETO || PWINDOWTOCHANGETO == PLASTWINDOW) { @@ -1621,13 +1610,12 @@ SDispatchResult CKeybindManager::swapActive(std::string args) { Log::logger->log(Log::DEBUG, "Swapping active window with {}", args); updateRelativeCursorCoords(); - g_pLayoutManager->getCurrentLayout()->switchWindows(PLASTWINDOW, PWINDOWTOCHANGETO); + g_layoutManager->switchTargets(PLASTWINDOW->layoutTarget(), PWINDOWTOCHANGETO->layoutTarget(), true); PLASTWINDOW->warpCursor(); return {}; } SDispatchResult CKeybindManager::moveActiveTo(std::string args) { - char arg = args[0]; bool silent = args.ends_with(" silent"); if (silent) args = args.substr(0, args.length() - 7); @@ -1645,9 +1633,10 @@ SDispatchResult CKeybindManager::moveActiveTo(std::string args) { return {}; } - if (!isDirection(args)) { - Log::logger->log(Log::ERR, "Cannot move window in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg); - return {.success = false, .error = std::format("Cannot move window in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg)}; + Math::eDirection dir = Math::fromChar(args[0]); + if (dir == Math::DIRECTION_DEFAULT) { + Log::logger->log(Log::ERR, "Cannot move window in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0]); + return {.success = false, .error = std::format("Cannot move window in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0])}; } const auto PLASTWINDOW = Desktop::focusState()->window(); @@ -1658,59 +1647,11 @@ SDispatchResult CKeybindManager::moveActiveTo(std::string args) { if (PLASTWINDOW->isFullscreen()) return {.success = false, .error = "Can't move fullscreen window"}; - if (PLASTWINDOW->m_isFloating) { - std::optional vPosx, vPosy; - const auto PMONITOR = PLASTWINDOW->m_monitor.lock(); - const auto BORDERSIZE = PLASTWINDOW->getRealBorderSize(); - static auto PGAPSCUSTOMDATA = CConfigValue("general:float_gaps"); - static auto PGAPSOUTDATA = CConfigValue("general:gaps_out"); - auto* PGAPSOUT = sc(PGAPSCUSTOMDATA.ptr()->getData()); - if (PGAPSOUT->m_left < 0 || PGAPSOUT->m_right < 0 || PGAPSOUT->m_top < 0 || PGAPSOUT->m_bottom < 0) - PGAPSOUT = sc(PGAPSOUTDATA.ptr()->getData()); + updateRelativeCursorCoords(); - switch (arg) { - case 'l': vPosx = PMONITOR->m_reservedArea.left() + BORDERSIZE + PMONITOR->m_position.x + PGAPSOUT->m_left; break; - case 'r': - vPosx = PMONITOR->m_size.x - PMONITOR->m_reservedArea.right() - PLASTWINDOW->m_realSize->goal().x - BORDERSIZE + PMONITOR->m_position.x - PGAPSOUT->m_right; - break; - case 't': - case 'u': vPosy = PMONITOR->m_reservedArea.top() + BORDERSIZE + PMONITOR->m_position.y + PGAPSOUT->m_top; break; - case 'b': - case 'd': - vPosy = PMONITOR->m_size.y - PMONITOR->m_reservedArea.bottom() - PLASTWINDOW->m_realSize->goal().y - BORDERSIZE + PMONITOR->m_position.y - PGAPSOUT->m_bottom; - break; - } - - *PLASTWINDOW->m_realPosition = Vector2D(vPosx.value_or(PLASTWINDOW->m_realPosition->goal().x), vPosy.value_or(PLASTWINDOW->m_realPosition->goal().y)); - - return {}; - } - - // If the window to change to is on the same workspace, switch them - const auto PWINDOWTOCHANGETO = g_pCompositor->getWindowInDirection(PLASTWINDOW, arg); - if (PWINDOWTOCHANGETO) { - updateRelativeCursorCoords(); - - g_pLayoutManager->getCurrentLayout()->moveWindowTo(PLASTWINDOW, args, silent); - if (!silent) - PLASTWINDOW->warpCursor(); - return {}; - } - - static auto PMONITORFALLBACK = CConfigValue("binds:window_direction_monitor_fallback"); - if (!*PMONITORFALLBACK) - return {}; - - // Otherwise, we always want to move to the next monitor in that direction - const auto PMONITORTOCHANGETO = g_pCompositor->getMonitorInDirection(arg); - if (!PMONITORTOCHANGETO) - return {.success = false, .error = "Nowhere to move active window to"}; - - const auto PWORKSPACE = PMONITORTOCHANGETO->m_activeWorkspace; - if (silent) - moveActiveToWorkspaceSilent(PWORKSPACE->getConfigName()); - else - moveActiveToWorkspace(PWORKSPACE->getConfigName()); + g_layoutManager->moveInDirection(PLASTWINDOW->layoutTarget(), args, silent); + if (!silent) + PLASTWINDOW->warpCursor(); return {}; } @@ -1724,10 +1665,10 @@ SDispatchResult CKeybindManager::toggleGroup(std::string args) { if (PWINDOW->isFullscreen()) g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE); - if (PWINDOW->m_groupData.pNextWindow.expired()) - PWINDOW->createGroup(); + if (!PWINDOW->m_group) + PWINDOW->m_group = Desktop::View::CGroup::create({PWINDOW}); else - PWINDOW->destroyGroup(); + PWINDOW->m_group->destroy(); return {}; } @@ -1738,87 +1679,29 @@ SDispatchResult CKeybindManager::changeGroupActive(std::string args) { if (!PWINDOW) return {.success = false, .error = "Window not found"}; - if (PWINDOW->m_groupData.pNextWindow.expired()) - return {.success = false, .error = "No next window in group"}; + if (!PWINDOW->m_group) + return {.success = false, .error = "No group"}; - if (PWINDOW->m_groupData.pNextWindow.lock() == PWINDOW) + if (PWINDOW->m_group->size() == 1) 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 {.success = false, .error = "Index too big, there aren't that many windows in this group"}; - if (INDEX == 0) - PWINDOW->setGroupCurrent(PWINDOW->getGroupTail()); - else - PWINDOW->setGroupCurrent(PWINDOW->getGroupWindowByIndex(INDEX - 1)); + try { + const int INDEX = std::stoi(args); + if (INDEX <= 0) + PWINDOW->m_group->setCurrent(PWINDOW->m_group->size() - 1); + else + PWINDOW->m_group->setCurrent(INDEX - 1); + } catch (...) { return {.success = false, .error = "invalid idx"}; } + return {}; } if (args != "b" && args != "prev") - PWINDOW->setGroupCurrent(PWINDOW->m_groupData.pNextWindow.lock()); + PWINDOW->m_group->moveCurrent(true); else - PWINDOW->setGroupCurrent(PWINDOW->getGroupPrevious()); - - return {}; -} - -SDispatchResult CKeybindManager::toggleSplit(std::string args) { - SLayoutMessageHeader header; - header.pWindow = Desktop::focusState()->window(); - - if (!header.pWindow) - return {.success = false, .error = "Window not found"}; - - const auto PWORKSPACE = header.pWindow->m_workspace; - - if (PWORKSPACE->m_hasFullscreenWindow) - return {.success = false, .error = "Can't split windows that already split"}; - - g_pLayoutManager->getCurrentLayout()->layoutMessage(header, "togglesplit"); - - return {}; -} - -SDispatchResult CKeybindManager::swapSplit(std::string args) { - SLayoutMessageHeader header; - header.pWindow = Desktop::focusState()->window(); - - if (!header.pWindow) - return {.success = false, .error = "Window not found"}; - - const auto PWORKSPACE = header.pWindow->m_workspace; - - if (PWORKSPACE->m_hasFullscreenWindow) - return {.success = false, .error = "Can't split windows that already split"}; - - g_pLayoutManager->getCurrentLayout()->layoutMessage(header, "swapsplit"); - - return {}; -} - -SDispatchResult CKeybindManager::alterSplitRatio(std::string args) { - std::optional splitResult; - bool exact = false; - - if (args.starts_with("exact")) { - exact = true; - splitResult = getPlusMinusKeywordResult(args.substr(5), 0); - } else - splitResult = getPlusMinusKeywordResult(args, 0); - - if (!splitResult.has_value()) { - Log::logger->log(Log::ERR, "Splitratio invalid in alterSplitRatio!"); - return {.success = false, .error = "Splitratio invalid in alterSplitRatio!"}; - } - - const auto PLASTWINDOW = Desktop::focusState()->window(); - - if (!PLASTWINDOW) - return {.success = false, .error = "Window not found"}; - - g_pLayoutManager->getCurrentLayout()->alterSplitRatio(PLASTWINDOW, splitResult.value(), exact); + PWINDOW->m_group->moveCurrent(false); return {}; } @@ -1903,58 +1786,7 @@ SDispatchResult CKeybindManager::moveCursor(std::string args) { } SDispatchResult CKeybindManager::workspaceOpt(std::string args) { - - // current workspace - const auto PWORKSPACE = Desktop::focusState()->monitor()->m_activeWorkspace; - - if (!PWORKSPACE) - return {.success = false, .error = "Workspace not found"}; // ???? - - if (args == "allpseudo") { - PWORKSPACE->m_defaultPseudo = !PWORKSPACE->m_defaultPseudo; - - // apply - for (auto const& w : g_pCompositor->m_windows) { - if (!w->m_isMapped || w->m_workspace != PWORKSPACE) - continue; - - w->m_isPseudotiled = PWORKSPACE->m_defaultPseudo; - } - } else if (args == "allfloat") { - PWORKSPACE->m_defaultFloating = !PWORKSPACE->m_defaultFloating; - // apply - - // we make a copy because changeWindowFloatingMode might invalidate the iterator - std::vector ptrs(g_pCompositor->m_windows.begin(), g_pCompositor->m_windows.end()); - - for (auto const& w : ptrs) { - if (!w->m_isMapped || w->m_workspace != PWORKSPACE || w->isHidden()) - continue; - - if (!w->m_requestsFloat && w->m_isFloating != PWORKSPACE->m_defaultFloating) { - const auto SAVEDPOS = w->m_realPosition->goal(); - const auto SAVEDSIZE = w->m_realSize->goal(); - - w->m_isFloating = PWORKSPACE->m_defaultFloating; - g_pLayoutManager->getCurrentLayout()->changeWindowFloatingMode(w); - - if (PWORKSPACE->m_defaultFloating) { - w->m_realPosition->setValueAndWarp(SAVEDPOS); - w->m_realSize->setValueAndWarp(SAVEDSIZE); - *w->m_realSize = w->m_realSize->value() + Vector2D(4, 4); - *w->m_realPosition = w->m_realPosition->value() - Vector2D(2, 2); - } - } - } - } else { - Log::logger->log(Log::ERR, "Invalid arg in workspaceOpt, opt \"{}\" doesn't exist.", args); - return {.success = false, .error = std::format("Invalid arg in workspaceOpt, opt \"{}\" doesn't exist.", args)}; - } - - // recalc mon - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(Desktop::focusState()->monitor()->m_id); - - return {}; + return {.success = false, .error = "workspaceopt is deprecated"}; } SDispatchResult CKeybindManager::renameWorkspace(std::string args) { @@ -2183,7 +2015,7 @@ SDispatchResult CKeybindManager::resizeActive(std::string args) { if (SIZ.x < 1 || SIZ.y < 1) return {.success = false, .error = "Invalid size provided"}; - g_pLayoutManager->getCurrentLayout()->resizeActiveWindow(SIZ - PLASTWINDOW->m_realSize->goal()); + g_layoutManager->resizeTarget(SIZ - PLASTWINDOW->m_realSize->goal(), PLASTWINDOW->layoutTarget()); if (PLASTWINDOW->m_realSize->goal().x > 1 && PLASTWINDOW->m_realSize->goal().y > 1) PLASTWINDOW->setHidden(false); @@ -2202,7 +2034,7 @@ SDispatchResult CKeybindManager::moveActive(std::string args) { const auto POS = g_pCompositor->parseWindowVectorArgsRelative(args, PLASTWINDOW->m_realPosition->goal()); - g_pLayoutManager->getCurrentLayout()->moveActiveWindow(POS - PLASTWINDOW->m_realPosition->goal()); + g_layoutManager->moveTarget(POS - PLASTWINDOW->m_realPosition->goal(), PLASTWINDOW->layoutTarget()); return {}; } @@ -2224,7 +2056,7 @@ SDispatchResult CKeybindManager::moveWindow(std::string args) { const auto POS = g_pCompositor->parseWindowVectorArgsRelative(MOVECMD, PWINDOW->m_realPosition->goal()); - g_pLayoutManager->getCurrentLayout()->moveActiveWindow(POS - PWINDOW->m_realPosition->goal(), PWINDOW); + g_layoutManager->moveTarget(POS - PWINDOW->m_realPosition->goal(), PWINDOW->layoutTarget()); return {}; } @@ -2249,7 +2081,7 @@ SDispatchResult CKeybindManager::resizeWindow(std::string args) { if (SIZ.x < 1 || SIZ.y < 1) return {.success = false, .error = "Invalid size provided"}; - g_pLayoutManager->getCurrentLayout()->resizeActiveWindow(SIZ - PWINDOW->m_realSize->goal(), CORNER_NONE, PWINDOW); + g_layoutManager->resizeTarget(SIZ - PWINDOW->m_realSize->goal(), PWINDOW->layoutTarget(), Layout::CORNER_NONE); if (PWINDOW->m_realSize->goal().x > 1 && PWINDOW->m_realSize->goal().y > 1) PWINDOW->setHidden(false); @@ -2271,14 +2103,32 @@ SDispatchResult CKeybindManager::circleNext(std::string arg) { CVarList args{arg, 0, 's', true}; + const auto PREV = args.contains("prev") || args.contains("p") || args.contains("last") || args.contains("l"); + std::optional floatStatus = {}; - if (args.contains("tile") || args.contains("tiled")) - floatStatus = false; - else if (args.contains("float") || args.contains("floating")) + if (args.contains("tile") || args.contains("tiled")) { + // if we want just tiled, and we are on a tiled window, use layoutmsg for layouts that support it + + if (!Desktop::focusState()->window()->m_isFloating) { + if (const auto SPACE = Desktop::focusState()->window()->layoutTarget()->space(); SPACE) { + + constexpr const std::array LAYOUTS_WITH_CYCLE_NEXT = { + &typeid(Layout::Tiled::CMonocleAlgorithm), + &typeid(Layout::Tiled::CMasterAlgorithm), + }; + + if (std::ranges::contains(LAYOUTS_WITH_CYCLE_NEXT, &typeid(*SPACE->algorithm()->tiledAlgo().get()))) { + CKeybindManager::layoutmsg(PREV ? "cyclenext, b" : "cyclenext"); + return {}; + } + } + } + } + + if (args.contains("float") || args.contains("floating")) floatStatus = true; const auto VISIBLE = args.contains("visible") || args.contains("v"); - const auto PREV = args.contains("prev") || args.contains("p") || args.contains("last") || args.contains("l"); const auto NEXT = args.contains("next") || args.contains("n"); // prev is default in classic alt+tab const auto HIST = args.contains("hist") || args.contains("h"); const auto& w = HIST ? g_pCompositor->getWindowCycleHist(Desktop::focusState()->window(), true, floatStatus, VISIBLE, NEXT) : @@ -2311,7 +2161,7 @@ SDispatchResult CKeybindManager::focusWindow(std::string regexp) { changeworkspace(PWORKSPACE->getConfigName()); } - Desktop::focusState()->fullWindowFocus(PWINDOW, nullptr, false); + Desktop::focusState()->fullWindowFocus(PWINDOW, Desktop::FOCUS_REASON_KEYBIND, nullptr, false); PWINDOW->warpCursor(); @@ -2347,12 +2197,12 @@ SDispatchResult CKeybindManager::toggleSwallow(std::string args) { // Unswallow pWindow->m_swallowed->m_currentlySwallowed = false; pWindow->m_swallowed->setHidden(false); - g_pLayoutManager->getCurrentLayout()->onWindowCreated(pWindow->m_swallowed.lock()); + g_layoutManager->newTarget(pWindow->m_swallowed->layoutTarget(), pWindow->m_workspace->m_space); } else { // Reswallow pWindow->m_swallowed->m_currentlySwallowed = true; pWindow->m_swallowed->setHidden(true); - g_pLayoutManager->getCurrentLayout()->onWindowRemoved(pWindow->m_swallowed.lock()); + g_layoutManager->removeTarget(pWindow->m_swallowed->layoutTarget()); } return {}; @@ -2363,7 +2213,7 @@ SDispatchResult CKeybindManager::setSubmap(std::string submap) { m_currentSelectedSubmap.name = ""; Log::logger->log(Log::DEBUG, "Reset active submap to the default one."); g_pEventManager->postEvent(SHyprIPCEvent{"submap", ""}); - EMIT_HOOK_EVENT("submap", m_currentSelectedSubmap.name); + Event::bus()->m_events.keybinds.submap.emit(m_currentSelectedSubmap.name); return {}; } @@ -2372,7 +2222,7 @@ SDispatchResult CKeybindManager::setSubmap(std::string submap) { m_currentSelectedSubmap.name = submap; Log::logger->log(Log::DEBUG, "Changed keybind submap to {}", submap); g_pEventManager->postEvent(SHyprIPCEvent{"submap", submap}); - EMIT_HOOK_EVENT("submap", m_currentSelectedSubmap.name); + Event::bus()->m_events.keybinds.submap.emit(m_currentSelectedSubmap.name); return {}; } } @@ -2613,9 +2463,9 @@ SDispatchResult CKeybindManager::sendshortcut(std::string args) { } SDispatchResult CKeybindManager::layoutmsg(std::string msg) { - SLayoutMessageHeader hd = {Desktop::focusState()->window()}; - g_pLayoutManager->getCurrentLayout()->layoutMessage(hd, msg); - + auto ret = g_layoutManager->layoutMsg(msg); + if (!ret) + return {.success = false, .error = ret.error()}; return {}; } @@ -2669,11 +2519,11 @@ SDispatchResult CKeybindManager::swapnext(std::string arg) { if (toSwap == PLASTWINDOW) toSwap = g_pCompositor->getWindowCycle(PLASTWINDOW, true, std::nullopt, false, NEED_PREV); - g_pLayoutManager->getCurrentLayout()->switchWindows(PLASTWINDOW, toSwap); + g_layoutManager->switchTargets(PLASTWINDOW->layoutTarget(), toSwap->layoutTarget(), false); PLASTWINDOW->m_lastCycledWindow = toSwap; - Desktop::focusState()->fullWindowFocus(PLASTWINDOW); + Desktop::focusState()->fullWindowFocus(PLASTWINDOW, Desktop::FOCUS_REASON_KEYBIND); return {}; } @@ -2722,7 +2572,7 @@ SDispatchResult CKeybindManager::pinActive(std::string args) { return {.success = false, .error = "pin: window not found"}; } - PWINDOW->m_workspace = PMONITOR->m_activeWorkspace; + PWINDOW->moveToWorkspace(PMONITOR->m_activeWorkspace); PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_PINNED); @@ -2732,7 +2582,9 @@ SDispatchResult CKeybindManager::pinActive(std::string args) { g_pCompositor->vectorToWindowUnified(g_pInputManager->getMouseCoordsInternal(), Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS); g_pEventManager->postEvent(SHyprIPCEvent{"pin", std::format("{:x},{}", rc(PWINDOW.get()), sc(PWINDOW->m_pinned))}); - EMIT_HOOK_EVENT("pin", PWINDOW); + Event::bus()->m_events.window.pin.emit(PWINDOW); + + g_pHyprRenderer->damageWindow(PWINDOW, true); return {}; } @@ -2760,7 +2612,7 @@ SDispatchResult CKeybindManager::mouse(std::string args) { SDispatchResult CKeybindManager::changeMouseBindMode(const eMouseBindMode MODE) { if (MODE != MBIND_INVALID) { - if (!g_pInputManager->m_currentlyDraggedWindow.expired() || g_pInputManager->m_dragMode != MBIND_INVALID) + if (g_layoutManager->dragController()->target()) return {}; const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal(); @@ -2769,21 +2621,17 @@ SDispatchResult CKeybindManager::changeMouseBindMode(const eMouseBindMode MODE) if (!PWINDOW) return SDispatchResult{.passEvent = true}; - if (!PWINDOW->isFullscreen() && MODE == MBIND_MOVE) - PWINDOW->checkInputOnDecos(INPUT_TYPE_DRAG_START, MOUSECOORDS); + if (!PWINDOW->isFullscreen() && MODE == MBIND_MOVE) { + if (PWINDOW->checkInputOnDecos(INPUT_TYPE_DRAG_START, MOUSECOORDS)) + return SDispatchResult{.passEvent = false}; + } - if (g_pInputManager->m_currentlyDraggedWindow.expired()) - g_pInputManager->m_currentlyDraggedWindow = PWINDOW; - - g_pInputManager->m_dragMode = MODE; - - g_pLayoutManager->getCurrentLayout()->onBeginDragWindow(); + g_layoutManager->beginDragTarget(PWINDOW->layoutTarget(), MODE); } else { - if (g_pInputManager->m_currentlyDraggedWindow.expired() || g_pInputManager->m_dragMode == MBIND_INVALID) + if (!g_layoutManager->dragController()->target()) return {}; - g_pLayoutManager->getCurrentLayout()->onEndDragWindow(); - g_pInputManager->m_dragMode = MODE; + g_layoutManager->endDragTarget(); } return {}; @@ -2845,17 +2693,15 @@ SDispatchResult CKeybindManager::lockActiveGroup(std::string args) { if (!PWINDOW) return {.success = false, .error = "No window found"}; - if (!PWINDOW->m_groupData.pNextWindow.lock()) + if (!PWINDOW->m_group) return {.success = false, .error = "Not a group"}; - const auto PHEAD = PWINDOW->getGroupHead(); - if (args == "lock") - PHEAD->m_groupData.locked = true; + PWINDOW->m_group->setLocked(true); else if (args == "toggle") - PHEAD->m_groupData.locked = !PHEAD->m_groupData.locked; + PWINDOW->m_group->setLocked(!PWINDOW->m_group->locked()); else - PHEAD->m_groupData.locked = false; + PWINDOW->m_group->setLocked(false); PWINDOW->updateDecorationValues(); @@ -2863,25 +2709,21 @@ SDispatchResult CKeybindManager::lockActiveGroup(std::string args) { } void CKeybindManager::moveWindowIntoGroup(PHLWINDOW pWindow, PHLWINDOW pWindowInDirection) { - if (pWindow->m_groupData.deny) + if (!pWindowInDirection->m_group || pWindowInDirection->m_group->denied()) return; updateRelativeCursorCoords(); - g_pLayoutManager->getCurrentLayout()->onWindowRemoved(pWindow); // This removes grouped property! - if (pWindow->m_monitor != pWindowInDirection->m_monitor) { pWindow->moveToWorkspace(pWindowInDirection->m_workspace); pWindow->m_monitor = pWindowInDirection->m_monitor; } - static auto USECURRPOS = CConfigValue("group:insert_after_current"); - (*USECURRPOS ? pWindowInDirection : pWindowInDirection->getGroupTail())->insertWindowToGroup(pWindow); + pWindowInDirection->m_group->add(pWindow); - pWindowInDirection->setGroupCurrent(pWindow); + pWindowInDirection->m_group->setCurrent(pWindow); pWindow->updateWindowDecos(); - g_pLayoutManager->getCurrentLayout()->recalculateWindow(pWindow); - Desktop::focusState()->fullWindowFocus(pWindow); + Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_KEYBIND); pWindow->warpCursor(); g_pEventManager->postEvent(SHyprIPCEvent{"moveintogroup", std::format("{:x}", rc(pWindow.get()))}); @@ -2889,70 +2731,53 @@ void CKeybindManager::moveWindowIntoGroup(PHLWINDOW pWindow, PHLWINDOW pWindowIn void CKeybindManager::moveWindowOutOfGroup(PHLWINDOW pWindow, const std::string& dir) { static auto BFOCUSREMOVEDWINDOW = CConfigValue("group:focus_removed_window"); - const auto PWINDOWPREV = pWindow->getGroupPrevious(); - eDirection direction; - switch (dir[0]) { - case 't': - case 'u': direction = DIRECTION_UP; break; - case 'd': - case 'b': direction = DIRECTION_DOWN; break; - case 'l': direction = DIRECTION_LEFT; break; - case 'r': direction = DIRECTION_RIGHT; break; - default: direction = DIRECTION_DEFAULT; - } + if (!pWindow->m_group) + return; - updateRelativeCursorCoords(); + WP group = pWindow->m_group; - if (pWindow->m_groupData.pNextWindow.lock() == pWindow) { - pWindow->destroyGroup(); - } else { - g_pLayoutManager->getCurrentLayout()->onWindowRemoved(pWindow); + const auto direction = !dir.empty() ? Math::fromChar(dir[0]) : Math::DIRECTION_DEFAULT; - const auto GROUPSLOCKEDPREV = g_pKeybindManager->m_groupsLocked; - g_pKeybindManager->m_groupsLocked = true; + pWindow->m_group->remove(pWindow, direction); - g_pLayoutManager->getCurrentLayout()->onWindowCreated(pWindow, direction); - - g_pKeybindManager->m_groupsLocked = GROUPSLOCKEDPREV; - } - - if (*BFOCUSREMOVEDWINDOW) { - Desktop::focusState()->fullWindowFocus(pWindow); + if (*BFOCUSREMOVEDWINDOW || !group) { + Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_KEYBIND); pWindow->warpCursor(); } else { - Desktop::focusState()->fullWindowFocus(PWINDOWPREV); - PWINDOWPREV->warpCursor(); + Desktop::focusState()->fullWindowFocus(group->current(), Desktop::FOCUS_REASON_KEYBIND); + group->current()->warpCursor(); } g_pEventManager->postEvent(SHyprIPCEvent{"moveoutofgroup", std::format("{:x}", rc(pWindow.get()))}); } SDispatchResult CKeybindManager::moveIntoGroup(std::string args) { - char arg = args[0]; - static auto PIGNOREGROUPLOCK = CConfigValue("binds:ignore_group_lock"); if (!*PIGNOREGROUPLOCK && g_pKeybindManager->m_groupsLocked) return {}; - if (!isDirection(args)) { - Log::logger->log(Log::ERR, "Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg); - return {.success = false, .error = std::format("Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg)}; + Math::eDirection dir = Math::fromChar(args[0]); + if (dir == Math::DIRECTION_DEFAULT) { + Log::logger->log(Log::ERR, "Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0]); + return {.success = false, .error = std::format("Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0])}; } const auto PWINDOW = Desktop::focusState()->window(); - if (!PWINDOW || PWINDOW->m_groupData.deny) + if (!PWINDOW) return {}; - auto PWINDOWINDIR = g_pCompositor->getWindowInDirection(PWINDOW, arg); + auto PWINDOWINDIR = g_pCompositor->getWindowInDirection(PWINDOW, dir); - if (!PWINDOWINDIR || !PWINDOWINDIR->m_groupData.pNextWindow.lock()) + if (!PWINDOWINDIR || !PWINDOWINDIR->m_group) return {}; + const auto GROUP = PWINDOWINDIR->m_group; + // Do not move window into locked group if binds:ignore_group_lock is false - if (!*PIGNOREGROUPLOCK && (PWINDOWINDIR->getGroupHead()->m_groupData.locked || (PWINDOW->m_groupData.pNextWindow.lock() && PWINDOW->getGroupHead()->m_groupData.locked))) + if (!*PIGNOREGROUPLOCK && (GROUP->locked() || (PWINDOW->m_group && PWINDOW->m_group->locked()))) return {}; moveWindowIntoGroup(PWINDOW, PWINDOWINDIR); @@ -2976,7 +2801,7 @@ SDispatchResult CKeybindManager::moveOutOfGroup(std::string args) { if (!PWINDOW) return {.success = false, .error = "No window found"}; - if (!PWINDOW->m_groupData.pNextWindow.lock()) + if (!PWINDOW->m_group) return {.success = false, .error = "Window not in a group"}; moveWindowOutOfGroup(PWINDOW); @@ -2985,13 +2810,12 @@ SDispatchResult CKeybindManager::moveOutOfGroup(std::string args) { } SDispatchResult CKeybindManager::moveWindowOrGroup(std::string args) { - char arg = args[0]; + static auto PIGNOREGROUPLOCK = CConfigValue("binds:ignore_group_lock"); - static auto PIGNOREGROUPLOCK = CConfigValue("binds:ignore_group_lock"); - - if (!isDirection(args)) { - Log::logger->log(Log::ERR, "Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg); - return {.success = false, .error = std::format("Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", arg)}; + Math::eDirection dir = Math::fromChar(args[0]); + if (dir == Math::DIRECTION_DEFAULT) { + Log::logger->log(Log::ERR, "Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0]); + return {.success = false, .error = std::format("Cannot move into group in direction {}, unsupported direction. Supported: l,r,u/t,d/b", args[0])}; } const auto PWINDOW = Desktop::focusState()->window(); @@ -3002,35 +2826,36 @@ SDispatchResult CKeybindManager::moveWindowOrGroup(std::string args) { return {}; if (!*PIGNOREGROUPLOCK && g_pKeybindManager->m_groupsLocked) { - g_pLayoutManager->getCurrentLayout()->moveWindowTo(PWINDOW, args); + g_layoutManager->moveInDirection(PWINDOW->layoutTarget(), args); return {}; } - const auto PWINDOWINDIR = g_pCompositor->getWindowInDirection(PWINDOW, arg); + const auto PWINDOWINDIR = g_pCompositor->getWindowInDirection(PWINDOW, dir); - const bool ISWINDOWGROUP = PWINDOW->m_groupData.pNextWindow; - const bool ISWINDOWGROUPLOCKED = ISWINDOWGROUP && PWINDOW->getGroupHead()->m_groupData.locked; - const bool ISWINDOWGROUPSINGLE = ISWINDOWGROUP && PWINDOW->m_groupData.pNextWindow.lock() == PWINDOW; + const bool ISWINDOWGROUP = PWINDOW->m_group; + const bool ISWINDOWGROUPLOCKED = ISWINDOWGROUP && PWINDOW->m_group->locked(); + const bool ISWINDOWGROUPSINGLE = ISWINDOWGROUP && PWINDOW->m_group->size() == 1; + const bool ISWINDOWGROUPDENIED = ISWINDOWGROUP && PWINDOW->m_group->denied(); updateRelativeCursorCoords(); // note: PWINDOWINDIR is not null implies !PWINDOW->m_isFloating - if (PWINDOWINDIR && PWINDOWINDIR->m_groupData.pNextWindow) { // target is group - if (!*PIGNOREGROUPLOCK && (PWINDOWINDIR->getGroupHead()->m_groupData.locked || ISWINDOWGROUPLOCKED || PWINDOW->m_groupData.deny)) { - g_pLayoutManager->getCurrentLayout()->moveWindowTo(PWINDOW, args); + if (PWINDOWINDIR && PWINDOWINDIR->m_group) { // target is group + if (!*PIGNOREGROUPLOCK && (PWINDOWINDIR->m_group->locked() || ISWINDOWGROUPLOCKED || ISWINDOWGROUPDENIED)) { + g_layoutManager->moveInDirection(PWINDOW->layoutTarget(), args); PWINDOW->warpCursor(); } else moveWindowIntoGroup(PWINDOW, PWINDOWINDIR); } else if (PWINDOWINDIR) { // target is regular window if ((!*PIGNOREGROUPLOCK && ISWINDOWGROUPLOCKED) || !ISWINDOWGROUP || (ISWINDOWGROUPSINGLE && PWINDOW->m_groupRules & Desktop::View::GROUP_SET_ALWAYS)) { - g_pLayoutManager->getCurrentLayout()->moveWindowTo(PWINDOW, args); + g_layoutManager->moveInDirection(PWINDOW->layoutTarget(), args); PWINDOW->warpCursor(); } else moveWindowOutOfGroup(PWINDOW, args); } else if ((*PIGNOREGROUPLOCK || !ISWINDOWGROUPLOCKED) && ISWINDOWGROUP) { // no target window moveWindowOutOfGroup(PWINDOW, args); } else if (!PWINDOWINDIR && !ISWINDOWGROUP) { // no target in dir and not in group - g_pLayoutManager->getCurrentLayout()->moveWindowTo(PWINDOW, args); + g_layoutManager->moveInDirection(PWINDOW->layoutTarget(), args); PWINDOW->warpCursor(); } @@ -3054,13 +2879,13 @@ SDispatchResult CKeybindManager::setIgnoreGroupLock(std::string args) { SDispatchResult CKeybindManager::denyWindowFromGroup(std::string args) { const auto PWINDOW = Desktop::focusState()->window(); - if (!PWINDOW || (PWINDOW && PWINDOW->m_groupData.pNextWindow.lock())) + if (!PWINDOW || (PWINDOW && PWINDOW->m_group)) return {}; if (args == "toggle") - PWINDOW->m_groupData.deny = !PWINDOW->m_groupData.deny; + PWINDOW->m_group->setDenied(!PWINDOW->m_group->denied()); else - PWINDOW->m_groupData.deny = args == "on"; + PWINDOW->m_group->setDenied(args == "on"); PWINDOW->updateDecorationValues(); @@ -3090,16 +2915,15 @@ SDispatchResult CKeybindManager::moveGroupWindow(std::string args) { if (!PLASTWINDOW) return {.success = false, .error = "No window found"}; - if (!PLASTWINDOW->m_groupData.pNextWindow.lock()) + if (!PLASTWINDOW->m_group) return {.success = false, .error = "Window not in a group"}; - if ((!BACK && PLASTWINDOW->m_groupData.pNextWindow->m_groupData.head) || (BACK && PLASTWINDOW->m_groupData.head)) { - std::swap(PLASTWINDOW->m_groupData.head, PLASTWINDOW->m_groupData.pNextWindow->m_groupData.head); - std::swap(PLASTWINDOW->m_groupData.locked, PLASTWINDOW->m_groupData.pNextWindow->m_groupData.locked); - } else - PLASTWINDOW->switchWithWindowInGroup(BACK ? PLASTWINDOW->getGroupPrevious() : PLASTWINDOW->m_groupData.pNextWindow.lock()); + const auto GROUP = PLASTWINDOW->m_group; - PLASTWINDOW->updateWindowDecos(); + if (BACK) + GROUP->swapWithLast(); + else + GROUP->swapWithNext(); return {}; } @@ -3164,12 +2988,22 @@ SDispatchResult CKeybindManager::setProp(std::string args) { try { if (PROP == "max_size") { - PWINDOW->m_ruleApplicator->maxSizeOverride(Desktop::Types::COverridableVar(configStringToVector2D(VAL), Desktop::Types::PRIORITY_SET_PROP)); + const auto SIZE = PWINDOW->calculateExpression(VAL); + if (!SIZE) { + Log::logger->log(Log::ERR, "failed to parse {} as an expression", VAL); + throw "failed to parse expression"; + } + PWINDOW->m_ruleApplicator->maxSizeOverride(Desktop::Types::COverridableVar(*SIZE, Desktop::Types::PRIORITY_SET_PROP)); PWINDOW->clampWindowSize(std::nullopt, PWINDOW->m_ruleApplicator->maxSize().value()); PWINDOW->setHidden(false); } else if (PROP == "min_size") { - PWINDOW->m_ruleApplicator->minSizeOverride(Desktop::Types::COverridableVar(configStringToVector2D(VAL), Desktop::Types::PRIORITY_SET_PROP)); - PWINDOW->clampWindowSize(std::nullopt, PWINDOW->m_ruleApplicator->minSize().value()); + const auto SIZE = PWINDOW->calculateExpression(VAL); + if (!SIZE) { + Log::logger->log(Log::ERR, "failed to parse {} as an expression", VAL); + throw "failed to parse expression"; + } + PWINDOW->m_ruleApplicator->minSizeOverride(Desktop::Types::COverridableVar(*SIZE, Desktop::Types::PRIORITY_SET_PROP)); + PWINDOW->clampWindowSize(PWINDOW->m_ruleApplicator->minSize().value(), std::nullopt); PWINDOW->setHidden(false); } else if (PROP == "active_border_color" || PROP == "inactive_border_color") { CGradientValueData colorData = {}; @@ -3288,16 +3122,18 @@ SDispatchResult CKeybindManager::setProp(std::string args) { if (PWINDOW->m_ruleApplicator->noFocus().valueOrDefault() != noFocus) { // FIXME: what the fuck is going on here? -vax - Desktop::focusState()->rawWindowFocus(nullptr); - Desktop::focusState()->fullWindowFocus(PWINDOW); - Desktop::focusState()->fullWindowFocus(PLASTWINDOW); + Desktop::focusState()->rawWindowFocus(nullptr, Desktop::FOCUS_REASON_KEYBIND); + Desktop::focusState()->fullWindowFocus(PWINDOW, Desktop::FOCUS_REASON_KEYBIND); + Desktop::focusState()->fullWindowFocus(PLASTWINDOW, Desktop::FOCUS_REASON_KEYBIND); } if (PROP == "no_vrr") g_pConfigManager->ensureVRR(PWINDOW->m_monitor.lock()); - for (auto const& m : g_pCompositor->m_monitors) - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id); + for (auto const& m : g_pCompositor->m_monitors) { + if (m->m_activeWorkspace) + m->m_activeWorkspace->m_space->recalculate(); + } return {}; } diff --git a/src/managers/KeybindManager.hpp b/src/managers/KeybindManager.hpp index d4b1bf66..1f013606 100644 --- a/src/managers/KeybindManager.hpp +++ b/src/managers/KeybindManager.hpp @@ -166,7 +166,7 @@ class CKeybindManager { static void switchToWindow(PHLWINDOW PWINDOWTOCHANGETO, bool forceFSCycle = false); static uint64_t spawnRawProc(std::string, PHLWORKSPACE pInitialWorkspace, const std::string& execRuleToken = ""); - static uint64_t spawnWithRules(std::string, PHLWORKSPACE pInitialWorkspace); + static std::optional spawnWithRules(std::string, PHLWORKSPACE pInitialWorkspace); // -------------- Dispatchers -------------- // static SDispatchResult closeActive(std::string); @@ -194,10 +194,7 @@ class CKeybindManager { 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); diff --git a/src/managers/LayoutManager.cpp b/src/managers/LayoutManager.cpp deleted file mode 100644 index 050f1d50..00000000 --- a/src/managers/LayoutManager.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include "LayoutManager.hpp" - -CLayoutManager::CLayoutManager() { - m_layouts.emplace_back(std::make_pair<>("dwindle", &m_dwindleLayout)); - m_layouts.emplace_back(std::make_pair<>("master", &m_masterLayout)); -} - -IHyprLayout* CLayoutManager::getCurrentLayout() { - return m_layouts[m_currentLayoutID].second; -} - -void CLayoutManager::switchToLayout(std::string layout) { - for (size_t i = 0; i < m_layouts.size(); ++i) { - if (m_layouts[i].first == layout) { - if (i == sc(m_currentLayoutID)) - return; - - getCurrentLayout()->onDisable(); - m_currentLayoutID = i; - getCurrentLayout()->onEnable(); - return; - } - } - - Log::logger->log(Log::ERR, "Unknown layout!"); -} - -bool CLayoutManager::addLayout(const std::string& name, IHyprLayout* layout) { - if (std::ranges::find_if(m_layouts, [&](const auto& other) { return other.first == name || other.second == layout; }) != m_layouts.end()) - return false; - - m_layouts.emplace_back(std::make_pair<>(name, layout)); - - Log::logger->log(Log::DEBUG, "Added new layout {} at {:x}", name, rc(layout)); - - return true; -} - -bool CLayoutManager::removeLayout(IHyprLayout* layout) { - const auto IT = std::ranges::find_if(m_layouts, [&](const auto& other) { return other.second == layout; }); - - if (IT == m_layouts.end() || IT->first == "dwindle" || IT->first == "master") - return false; - - if (m_currentLayoutID == IT - m_layouts.begin()) - switchToLayout("dwindle"); - - Log::logger->log(Log::DEBUG, "Removed a layout {} at {:x}", IT->first, rc(layout)); - - std::erase(m_layouts, *IT); - - return true; -} - -std::vector CLayoutManager::getAllLayoutNames() { - std::vector results(m_layouts.size()); - for (size_t i = 0; i < m_layouts.size(); ++i) - results[i] = m_layouts[i].first; - return results; -} diff --git a/src/managers/LayoutManager.hpp b/src/managers/LayoutManager.hpp deleted file mode 100644 index 80c522fb..00000000 --- a/src/managers/LayoutManager.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -#include "../layout/DwindleLayout.hpp" -#include "../layout/MasterLayout.hpp" - -class CLayoutManager { - public: - CLayoutManager(); - - IHyprLayout* getCurrentLayout(); - - void switchToLayout(std::string); - - bool addLayout(const std::string& name, IHyprLayout* layout); - bool removeLayout(IHyprLayout* layout); - std::vector getAllLayoutNames(); - - private: - enum eHyprLayouts : uint8_t { - LAYOUT_DWINDLE = 0, - LAYOUT_MASTER - }; - - int m_currentLayoutID = LAYOUT_DWINDLE; - - CHyprDwindleLayout m_dwindleLayout; - CHyprMasterLayout m_masterLayout; - std::vector> m_layouts; -}; - -inline UP g_pLayoutManager; diff --git a/src/managers/PointerManager.cpp b/src/managers/PointerManager.cpp index d2065d69..7256e176 100644 --- a/src/managers/PointerManager.cpp +++ b/src/managers/PointerManager.cpp @@ -8,38 +8,41 @@ #include "../protocols/IdleNotify.hpp" #include "../protocols/core/Compositor.hpp" #include "../protocols/core/Seat.hpp" +#include "debug/log/Logger.hpp" #include "eventLoop/EventLoopManager.hpp" #include "../render/pass/TexPassElement.hpp" #include "../managers/input/InputManager.hpp" -#include "../managers/HookSystemManager.hpp" #include "../render/Renderer.hpp" #include "../render/OpenGL.hpp" #include "../desktop/state/FocusState.hpp" #include "SeatManager.hpp" #include "../helpers/time/Time.hpp" +#include "../helpers/Drm.hpp" +#include "../event/EventBus.hpp" +#include #include #include #include +#include +#include #include using namespace Hyprutils::Utils; CPointerManager::CPointerManager() { - m_hooks.monitorAdded = g_pHookSystem->hookDynamic("monitorAdded", [this](void* self, SCallbackInfo& info, std::any data) { - auto PMONITOR = std::any_cast(data); - + m_hooks.monitorAdded = Event::bus()->m_events.monitor.added.listen([this](PHLMONITOR monitor) { onMonitorLayoutChange(); - PMONITOR->m_events.modeChanged.listenStatic([this] { g_pEventLoopManager->doLater([this]() { onMonitorLayoutChange(); }); }); - PMONITOR->m_events.disconnect.listenStatic([this] { g_pEventLoopManager->doLater([this]() { onMonitorLayoutChange(); }); }); - PMONITOR->m_events.destroy.listenStatic([this] { + monitor->m_events.modeChanged.listenStatic([this] { g_pEventLoopManager->doLater([this]() { onMonitorLayoutChange(); }); }); + monitor->m_events.disconnect.listenStatic([this] { g_pEventLoopManager->doLater([this]() { onMonitorLayoutChange(); }); }); + monitor->m_events.destroy.listenStatic([this] { if (g_pCompositor && !g_pCompositor->m_isShuttingDown) std::erase_if(m_monitorStates, [](const auto& other) { return other->monitor.expired(); }); }); }); - m_hooks.monitorPreRender = g_pHookSystem->hookDynamic("preMonitorCommit", [this](void* self, SCallbackInfo& info, std::any data) { - auto state = stateFor(std::any_cast(data)); + m_hooks.monitorPreRender = Event::bus()->m_events.monitor.preCommit.listen([this](PHLMONITOR monitor) { + auto state = stateFor(monitor); if (!state) return; @@ -72,8 +75,10 @@ void CPointerManager::lockSoftwareForMonitor(PHLMONITOR mon) { void CPointerManager::unlockSoftwareForMonitor(PHLMONITOR mon) { auto const state = stateFor(mon); state->softwareLocks--; - if (state->softwareLocks < 0) + if (state->softwareLocks < 0) { state->softwareLocks = 0; + Log::logger->log(Log::WARN, "Unlocking SW for monitor while it's not locked"); + } if (state->softwareLocks == 0) updateCursorBackend(); @@ -81,13 +86,22 @@ void CPointerManager::unlockSoftwareForMonitor(PHLMONITOR mon) { bool CPointerManager::softwareLockedFor(PHLMONITOR mon) { auto const state = stateFor(mon); - return state->softwareLocks > 0 || state->hardwareFailed; + return state->softwareLocks > 0 || (state->hardwareFailed && hasCursor() && g_pHyprRenderer->shouldRenderCursor()); +} + +bool CPointerManager::hasVisibleHWCursor(PHLMONITOR pMonitor) { + auto const state = stateFor(pMonitor); + return state->softwareLocks == 0 && !state->hardwareFailed && hasCursor() && g_pHyprRenderer->shouldRenderCursor(); } Vector2D CPointerManager::position() { return m_pointerPos; } +Vector2D CPointerManager::hotspot() { + return m_currentCursorImage.hotspot; +} + bool CPointerManager::hasCursor() { return m_currentCursorImage.pBuffer || m_currentCursorImage.surface; } @@ -107,6 +121,7 @@ void CPointerManager::setCursorBuffer(SP buf, const Vector2 m_currentCursorImage.scale = scale; updateCursorBackend(); damageIfSoftware(); + m_events.cursorChanged.emit(); } return; @@ -124,6 +139,7 @@ void CPointerManager::setCursorBuffer(SP buf, const Vector2 updateCursorBackend(); damageIfSoftware(); + m_events.cursorChanged.emit(); } void CPointerManager::setCursorSurface(SP surf, const Vector2D& hotspot) { @@ -135,6 +151,7 @@ void CPointerManager::setCursorSurface(SP surf, const m_currentCursorImage.scale = surf && surf->resource() ? surf->resource()->m_current.scale : 1.F; updateCursorBackend(); damageIfSoftware(); + m_events.cursorChanged.emit(); } return; @@ -156,6 +173,7 @@ void CPointerManager::setCursorSurface(SP surf, const recheckEnteredOutputs(); updateCursorBackend(); damageIfSoftware(); + m_events.cursorChanged.emit(); }); if (surf->resource()->m_current.texture) { @@ -169,6 +187,7 @@ void CPointerManager::setCursorSurface(SP surf, const recheckEnteredOutputs(); updateCursorBackend(); damageIfSoftware(); + m_events.cursorChanged.emit(); } void CPointerManager::recheckEnteredOutputs() { @@ -253,6 +272,8 @@ void CPointerManager::resetCursorImage(bool apply) { ms->cursorFrontBuffer = nullptr; } } + + m_events.cursorChanged.emit(); } void CPointerManager::updateCursorBackend() { @@ -389,7 +410,7 @@ bool CPointerManager::setHWCursorBuffer(SP state, SP CPointerManager::renderHWCursorBuffer(SP state, SP texture) { +SP CPointerManager::renderHWCursorBuffer(SP state, SP texture) { auto maxSize = state->monitor->m_output->cursorPlaneSize(); auto const& cursorSize = m_currentCursorImage.size; @@ -433,7 +454,7 @@ SP CPointerManager::renderHWCursorBuffer(SPmonitor->m_output->getBackend()->preferredAllocator()->drmFD() != g_pCompositor->m_drm.fd; + options.multigpu = !DRM::sameGpu(state->monitor->m_output->getBackend()->preferredAllocator()->drmFD(), g_pCompositor->m_drm.fd); // We do not set the format (unless shm). If it's unset (DRM_FORMAT_INVALID) then the swapchain will pick for us, // but if it's set, we don't wanna change it. if (shouldUseCpuBuffer) @@ -522,24 +543,23 @@ SP CPointerManager::renderHWCursorBuffer(SPm_size / (m_currentCursorImage.size / m_currentCursorImage.scale * state->monitor->m_scale); - cairo_matrix_scale(&matrixPre, SCALE.x, SCALE.y); + const auto SX = SCALE.x, SY = SCALE.y; + const auto BW = sc(DMABUF.size.x), BH = sc(DMABUF.size.y); - if (TR) { - cairo_matrix_rotate(&matrixPre, M_PI_2 * sc(TR)); - - // FIXME: this is wrong, and doesn't work for 5, 6 and 7. (flipped + rot) - // cba to do it rn, does anyone fucking use that?? - if (TR >= WL_OUTPUT_TRANSFORM_FLIPPED) { - cairo_matrix_scale(&matrixPre, -1, 1); - cairo_matrix_translate(&matrixPre, -DMABUF.size.x, 0); - } - - if (TR == 3 || TR == 7) - cairo_matrix_translate(&matrixPre, -DMABUF.size.x, 0); - else if (TR == 2 || TR == 6) - cairo_matrix_translate(&matrixPre, -DMABUF.size.x, -DMABUF.size.y); - else if (TR == 1 || TR == 5) - cairo_matrix_translate(&matrixPre, 0, -DMABUF.size.y); + // Cairo pattern matrix maps destination coords to source coords (inverse of visual transform). + // x_src = xx * x_dst + xy * y_dst + x0 + // y_src = yx * x_dst + yy * y_dst + y0 + // cairo_matrix_init(&m, xx, yx, xy, yy, x0, y0) + switch (TR) { + case WL_OUTPUT_TRANSFORM_NORMAL: + default: cairo_matrix_init(&matrixPre, SX, 0, 0, SY, 0, 0); break; + case WL_OUTPUT_TRANSFORM_90: cairo_matrix_init(&matrixPre, 0, SY, -SX, 0, SX * BW, 0); break; + case WL_OUTPUT_TRANSFORM_180: cairo_matrix_init(&matrixPre, -SX, 0, 0, -SY, SX * BW, SY * BH); break; + case WL_OUTPUT_TRANSFORM_270: cairo_matrix_init(&matrixPre, 0, -SY, SX, 0, 0, SY * BH); break; + case WL_OUTPUT_TRANSFORM_FLIPPED: cairo_matrix_init(&matrixPre, -SX, 0, 0, SY, SX * BW, 0); break; + case WL_OUTPUT_TRANSFORM_FLIPPED_90: cairo_matrix_init(&matrixPre, 0, SY, SX, 0, 0, 0); break; + case WL_OUTPUT_TRANSFORM_FLIPPED_180: cairo_matrix_init(&matrixPre, SX, 0, 0, -SY, 0, SY * BH); break; + case WL_OUTPUT_TRANSFORM_FLIPPED_270: cairo_matrix_init(&matrixPre, 0, -SY, -SX, 0, SX * BW, SY * BH); break; } cairo_pattern_set_matrix(PATTERNPRE, &matrixPre); @@ -572,20 +592,18 @@ SP CPointerManager::renderHWCursorBuffer(SPbind(); - g_pHyprOpenGL->beginSimple(state->monitor.lock(), {0, 0, INT16_MAX, INT16_MAX}, RBO); - g_pHyprOpenGL->clear(CHyprColor{0.F, 0.F, 0.F, 0.F}); + g_pHyprOpenGL->beginSimple(state->monitor.lock(), {0, 0, INT_MAX, INT_MAX}, RBO); + g_pHyprOpenGL->clear(CHyprColor{0.F, 0.F, 0.F, 0.F}); // ensure the RBO is zero initialized. CBox xbox = {{}, Vector2D{m_currentCursorImage.size / m_currentCursorImage.scale * state->monitor->m_scale}.round()}; Log::logger->log(Log::TRACE, "[pointer] monitor: {}, size: {}, hw buf: {}, scale: {:.2f}, monscale: {:.2f}, xbox: {}", state->monitor->m_name, m_currentCursorImage.size, cursorSize, m_currentCursorImage.scale, state->monitor->m_scale, xbox.size()); - g_pHyprOpenGL->renderTexture(texture, xbox, {}); + g_pHyprOpenGL->renderTexture(texture, xbox, {.noCM = true}); g_pHyprOpenGL->end(); g_pHyprOpenGL->m_renderData.pMonitor.reset(); - g_pHyprRenderer->onRenderbufferDestroy(RBO.get()); - return buf; } @@ -738,17 +756,26 @@ Vector2D CPointerManager::closestValid(const Vector2D& pos) { } void CPointerManager::damageIfSoftware() { + if (g_pCompositor->m_unsafeState) + return; + auto b = getCursorBoxGlobal().expand(4); for (auto const& mw : m_monitorStates) { - if (mw->monitor.expired() || !mw->monitor->m_output) + auto monitor = mw->monitor.lock(); + if (!monitor || !monitor->m_output || monitor->isMirror()) continue; - if ((mw->softwareLocks > 0 || mw->hardwareFailed || g_pConfigManager->shouldUseSoftwareCursors(mw->monitor.lock())) && - b.overlaps({mw->monitor->m_position, mw->monitor->m_size})) { - g_pHyprRenderer->damageBox(b, mw->monitor->shouldSkipScheduleFrameOnMouseEvent()); - break; - } + auto usesSoftwareCursor = (mw->softwareLocks > 0 || mw->hardwareFailed || g_pConfigManager->shouldUseSoftwareCursors(monitor)); + if (!usesSoftwareCursor) + continue; + + auto shouldAddDamage = !monitor->shouldSkipScheduleFrameOnMouseEvent() && b.overlaps({monitor->m_position, monitor->m_size}); + if (!shouldAddDamage) + continue; + + CBox damageBox = b.copy().translate(-monitor->m_position).scale(monitor->m_scale).round(); + monitor->addDamage(damageBox); } } @@ -872,13 +899,17 @@ void CPointerManager::onMonitorLayoutChange() { damageIfSoftware(); } -SP CPointerManager::getCurrentCursorTexture() { +const CPointerManager::SCursorImage& CPointerManager::currentCursorImage() { + return m_currentCursorImage; +} + +SP CPointerManager::getCurrentCursorTexture() { if (!m_currentCursorImage.pBuffer && (!m_currentCursorImage.surface || !m_currentCursorImage.surface->resource()->m_current.texture)) return nullptr; if (m_currentCursorImage.pBuffer) { if (!m_currentCursorImage.bufferTex) - m_currentCursorImage.bufferTex = makeShared(m_currentCursorImage.pBuffer, true); + m_currentCursorImage.bufferTex = g_pHyprRenderer->createTexture(m_currentCursorImage.pBuffer, true); return m_currentCursorImage.bufferTex; } @@ -920,24 +951,11 @@ void CPointerManager::attachPointer(SP pointer) { PROTO::idle->onActivity(); }); - listener->axis = pointer->m_pointerEvents.axis.listen([weak = WP(pointer)](const IPointer::SAxisEvent& event) { + listener->axis = pointer->m_pointerEvents.axis.listen([weak = WP(pointer)](const IPointer::SAxisEvent& event) { g_pInputManager->onMouseWheel(event, weak.lock()); PROTO::idle->onActivity(); }); - - listener->frame = pointer->m_pointerEvents.frame.listen([] { - bool shouldSkip = false; - if (!g_pSeatManager->m_mouse.expired() && g_pInputManager->isLocked()) { - auto PMONITOR = Desktop::focusState()->monitor().get(); - if (PMONITOR && PMONITOR->shouldSkipScheduleFrameOnMouseEvent()) { - auto fsWindow = PMONITOR->m_activeWorkspace->getFullscreenWindow(); - shouldSkip = fsWindow && fsWindow->m_isX11; - } - } - g_pSeatManager->m_isPointerFrameSkipped = shouldSkip; - if (!g_pSeatManager->m_isPointerFrameSkipped) - g_pSeatManager->sendPointerFrame(); - }); + listener->frame = pointer->m_pointerEvents.frame.listen([] { g_pInputManager->onPointerFrame(); }); listener->swipeBegin = pointer->m_pointerEvents.swipeBegin.listen([](const IPointer::SSwipeBeginEvent& event) { g_pInputManager->onSwipeBegin(event); @@ -1090,7 +1108,7 @@ void CPointerManager::detachTablet(SP tablet) { std::erase_if(m_tabletListeners, [tablet](const auto& e) { return e->tablet.expired() || e->tablet == tablet; }); } -void CPointerManager::damageCursor(PHLMONITOR pMonitor) { +void CPointerManager::damageCursor(PHLMONITOR pMonitor, bool skipFrameSchedule) { for (auto const& mw : m_monitorStates) { if (mw->monitor != pMonitor) continue; @@ -1100,7 +1118,7 @@ void CPointerManager::damageCursor(PHLMONITOR pMonitor) { if (b.empty()) return; - g_pHyprRenderer->damageBox(b); + g_pHyprRenderer->damageBox(b, skipFrameSchedule); return; } @@ -1109,22 +1127,3 @@ void CPointerManager::damageCursor(PHLMONITOR pMonitor) { Vector2D CPointerManager::cursorSizeLogical() { return m_currentCursorImage.size / m_currentCursorImage.scale; } - -void CPointerManager::storeMovement(uint64_t time, const Vector2D& delta, const Vector2D& deltaUnaccel) { - m_storedTime = time; - m_storedDelta += delta; - m_storedUnaccel += deltaUnaccel; -} - -void CPointerManager::setStoredMovement(uint64_t time, const Vector2D& delta, const Vector2D& deltaUnaccel) { - m_storedTime = time; - m_storedDelta = delta; - m_storedUnaccel = deltaUnaccel; -} - -void CPointerManager::sendStoredMovement() { - PROTO::relativePointer->sendRelativeMotion(m_storedTime * 1000, m_storedDelta, m_storedUnaccel); - m_storedTime = 0; - m_storedDelta = Vector2D{}; - m_storedUnaccel = Vector2D{}; -} diff --git a/src/managers/PointerManager.hpp b/src/managers/PointerManager.hpp index e7294fd4..a4fe1971 100644 --- a/src/managers/PointerManager.hpp +++ b/src/managers/PointerManager.hpp @@ -7,11 +7,12 @@ #include "../desktop/view/WLSurface.hpp" #include "../helpers/sync/SyncTimeline.hpp" #include "../helpers/time/Time.hpp" +#include "../helpers/signal/Signal.hpp" #include class CMonitor; class IHID; -class CTexture; +class ITexture; AQUAMARINE_FORWARD(IBuffer); @@ -48,6 +49,7 @@ class CPointerManager { void lockSoftwareAll(); void unlockSoftwareAll(); bool softwareLockedFor(PHLMONITOR pMonitor); + bool hasVisibleHWCursor(PHLMONITOR pMonitor); void renderSoftwareCursorsFor(PHLMONITOR pMonitor, const Time::steady_tp& now, CRegion& damage /* logical */, std::optional overridePos = {} /* monitor-local */, bool forceRender = false); @@ -55,17 +57,38 @@ class CPointerManager { // this is needed e.g. during screensharing where // the software cursors aren't locked during the cursor move, but they // are rendered later. - void damageCursor(PHLMONITOR pMonitor); + void damageCursor(PHLMONITOR pMonitor, bool skipFrameSchedule = false); // Vector2D position(); + Vector2D hotspot(); 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(); + // returns the thing in global coords + CBox getCursorBoxGlobal(); + + struct SCursorImage { + SP pBuffer; + SP bufferTex; + WP surface; + + Vector2D hotspot; + Vector2D size; + float scale = 1.F; + + CHyprSignalListener destroySurface; + CHyprSignalListener commitSurface; + }; + + const SCursorImage& currentCursorImage(); + SP getCurrentCursorTexture(); + + struct { + CSignalT<> cursorChanged; + } m_events; + private: void recheckPointerPosition(); void onMonitorLayoutChange(); @@ -81,13 +104,9 @@ class CPointerManager { // returns the thing in device coordinates. Is NOT offset by the hotspot, relies on set_cursor with hotspot. Vector2D getCursorPosForMonitor(PHLMONITOR pMonitor); // returns the thing in logical coordinates of the monitor - CBox getCursorBoxLogicalForMonitor(PHLMONITOR pMonitor); - // returns the thing in global coords - CBox getCursorBoxGlobal(); + CBox getCursorBoxLogicalForMonitor(PHLMONITOR pMonitor); - Vector2D transformedHotspot(PHLMONITOR pMonitor); - - SP getCurrentCursorTexture(); + Vector2D transformedHotspot(PHLMONITOR pMonitor); struct SPointerListener { CHyprSignalListener destroy; @@ -139,24 +158,9 @@ class CPointerManager { std::vector monitorBoxes; } m_currentMonitorLayout; - struct { - SP pBuffer; - SP bufferTex; - WP surface; + SCursorImage m_currentCursorImage; // TODO: support various sizes per-output so we can have pixel-perfect cursors - Vector2D hotspot; - Vector2D size; - float scale = 1.F; - - CHyprSignalListener destroySurface; - CHyprSignalListener commitSurface; - } m_currentCursorImage; // TODO: support various sizes per-output so we can have pixel-perfect cursors - - Vector2D m_pointerPos = {0, 0}; - - uint64_t m_storedTime = 0; - Vector2D m_storedDelta = {0, 0}; - Vector2D m_storedUnaccel = {0, 0}; + Vector2D m_pointerPos = {0, 0}; struct SMonitorPointerState { SMonitorPointerState(const PHLMONITOR& m) : monitor(m) {} @@ -177,12 +181,12 @@ class CPointerManager { std::vector> m_monitorStates; SP stateFor(PHLMONITOR mon); bool attemptHardwareCursor(SP state); - SP renderHWCursorBuffer(SP state, SP texture); + SP renderHWCursorBuffer(SP state, SP texture); bool setHWCursorBuffer(SP state, SP buf); struct { - SP monitorAdded; - SP monitorPreRender; + CHyprSignalListener monitorAdded; + CHyprSignalListener monitorPreRender; } m_hooks; }; diff --git a/src/managers/ProtocolManager.cpp b/src/managers/ProtocolManager.cpp index ce77e2fe..c13e6e48 100644 --- a/src/managers/ProtocolManager.cpp +++ b/src/managers/ProtocolManager.cpp @@ -50,6 +50,8 @@ #include "../protocols/SecurityContext.hpp" #include "../protocols/CTMControl.hpp" #include "../protocols/HyprlandSurface.hpp" +#include "../protocols/ImageCaptureSource.hpp" +#include "../protocols/ImageCopyCapture.hpp" #include "../protocols/core/Seat.hpp" #include "../protocols/core/DataDevice.hpp" #include "../protocols/core/Compositor.hpp" @@ -67,6 +69,7 @@ #include "../protocols/CommitTiming.hpp" #include "../helpers/Monitor.hpp" +#include "../event/EventBus.hpp" #include "../render/Renderer.hpp" #include "../Compositor.hpp" #include "content-type-v1.hpp" @@ -107,10 +110,10 @@ CProtocolManager::CProtocolManager() { static const auto PENABLECM = CConfigValue("render:cm_enabled"); static const auto PDEBUGCM = CConfigValue("debug:full_cm_proto"); - // Outputs are a bit dumb, we have to agree. - static auto P = g_pHookSystem->hookDynamic("monitorAdded", [this](void* self, SCallbackInfo& info, std::any param) { - auto M = std::any_cast(param); + static const auto PENABLECT = CConfigValue("render:commit_timing_enabled"); + // Outputs are a bit dumb, we have to agree. + static auto P = Event::bus()->m_events.monitor.added.listen([this](PHLMONITOR M) { // ignore mirrored outputs. I don't think this will ever be hit as mirrors are applied after // this event is emitted iirc. // also ignore the fallback @@ -127,8 +130,7 @@ CProtocolManager::CProtocolManager() { m_modeChangeListeners[M->m_name] = M->m_events.modeChanged.listen([this, M] { onMonitorModeChange(M); }); }); - static auto P2 = g_pHookSystem->hookDynamic("monitorRemoved", [this](void* self, SCallbackInfo& info, std::any param) { - auto M = std::any_cast(param); + static auto P2 = Event::bus()->m_events.monitor.removed.listen([this](PHLMONITOR M) { if (!PROTO::outputs.contains(M->m_name)) return; PROTO::outputs.at(M->m_name)->remove(); @@ -140,7 +142,7 @@ CProtocolManager::CProtocolManager() { PROTO::data = makeUnique(&wl_data_device_manager_interface, 3, "WLDataDevice"); PROTO::compositor = makeUnique(&wl_compositor_interface, 6, "WLCompositor"); PROTO::subcompositor = makeUnique(&wl_subcompositor_interface, 1, "WLSubcompositor"); - PROTO::shm = makeUnique(&wl_shm_interface, 1, "WLSHM"); + PROTO::shm = makeUnique(&wl_shm_interface, 2, "WLSHM"); // Extensions PROTO::viewport = makeUnique(&wp_viewporter_interface, 1, "Viewporter"); @@ -178,8 +180,6 @@ CProtocolManager::CProtocolManager() { PROTO::dataWlr = makeUnique(&zwlr_data_control_manager_v1_interface, 2, "DataDeviceWlr"); PROTO::primarySelection = makeUnique(&zwp_primary_selection_device_manager_v1_interface, 1, "PrimarySelection"); PROTO::xwaylandShell = makeUnique(&xwayland_shell_v1_interface, 1, "XWaylandShell"); - PROTO::screencopy = makeUnique(&zwlr_screencopy_manager_v1_interface, 3, "Screencopy"); - PROTO::toplevelExport = makeUnique(&hyprland_toplevel_export_manager_v1_interface, 2, "ToplevelExport"); PROTO::toplevelMapping = makeUnique(&hyprland_toplevel_mapping_manager_v1_interface, 1, "ToplevelMapping"); PROTO::globalShortcuts = makeUnique(&hyprland_global_shortcuts_manager_v1_interface, 1, "GlobalShortcuts"); PROTO::xdgDialog = makeUnique(&xdg_wm_dialog_v1_interface, 1, "XDGDialog"); @@ -194,7 +194,15 @@ CProtocolManager::CProtocolManager() { PROTO::extDataDevice = makeUnique(&ext_data_control_manager_v1_interface, 1, "ExtDataDevice"); PROTO::pointerWarp = makeUnique(&wp_pointer_warp_v1_interface, 1, "PointerWarp"); PROTO::fifo = makeUnique(&wp_fifo_manager_v1_interface, 1, "Fifo"); - PROTO::commitTiming = makeUnique(&wp_commit_timing_manager_v1_interface, 1, "CommitTiming"); + + if (*PENABLECT) + PROTO::commitTiming = makeUnique(&wp_commit_timing_manager_v1_interface, 1, "CommitTiming"); + + // Screensharing Protocols + PROTO::screencopy = makeUnique(&zwlr_screencopy_manager_v1_interface, 3, "Screencopy"); + PROTO::toplevelExport = makeUnique(&hyprland_toplevel_export_manager_v1_interface, 2, "ToplevelExport"); + PROTO::imageCaptureSource = makeUnique(); // ctor inits actual protos, output and toplevel + PROTO::imageCopyCapture = makeUnique(&ext_image_copy_capture_manager_v1_interface, 1, "ImageCopyCapture"); if (*PENABLECM) PROTO::colorManagement = makeUnique(&wp_color_manager_v1_interface, 1, "ColorManagement", *PDEBUGCM); @@ -294,6 +302,7 @@ CProtocolManager::~CProtocolManager() { PROTO::pointerWarp.reset(); PROTO::fifo.reset(); PROTO::commitTiming.reset(); + PROTO::imageCaptureSource.reset(); for (auto& [_, lease] : PROTO::lease) { lease.reset(); @@ -336,9 +345,6 @@ bool CProtocolManager::isGlobalPrivileged(const wl_global* global) { 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(), diff --git a/src/managers/SeatManager.cpp b/src/managers/SeatManager.cpp index f40c55e3..a107dced 100644 --- a/src/managers/SeatManager.cpp +++ b/src/managers/SeatManager.cpp @@ -5,12 +5,12 @@ #include "../protocols/ExtDataDevice.hpp" #include "../protocols/PrimarySelection.hpp" #include "../protocols/core/Compositor.hpp" +#include "../protocols/LayerShell.hpp" #include "../Compositor.hpp" #include "../desktop/state/FocusState.hpp" #include "../devices/IKeyboard.hpp" #include "../desktop/view/LayerSurface.hpp" #include "../managers/input/InputManager.hpp" -#include "../managers/HookSystemManager.hpp" #include "wlr-layer-shell-unstable-v1.hpp" #include #include @@ -618,8 +618,9 @@ void CSeatManager::setGrab(SP grab) { if (m_seatGrab) { auto oldGrab = m_seatGrab; - // Try to find the parent window from the grab + // Try to find the parent window or layer surface from the grab PHLWINDOW parentWindow; + PHLLS parentLayer; if (oldGrab && oldGrab->m_surfs.size()) { // Try to find the surface that had focus when the grab ended SP focusedSurf; @@ -645,8 +646,11 @@ void CSeatManager::setGrab(SP grab) { auto popup = Desktop::View::CPopup::fromView(hlSurface->view()); if (popup) { auto t1Owner = popup->getT1Owner(); - if (t1Owner) + if (t1Owner) { parentWindow = Desktop::View::CWindow::fromView(t1Owner->view()); + if (!parentWindow) + parentLayer = Desktop::View::CLayerSurface::fromView(t1Owner->view()); + } } } } @@ -654,18 +658,22 @@ void CSeatManager::setGrab(SP grab) { m_seatGrab.reset(); - static auto PFOLLOWMOUSE = CConfigValue("input:follow_mouse"); - if (*PFOLLOWMOUSE == 0 || *PFOLLOWMOUSE == 2 || *PFOLLOWMOUSE == 3) { - const auto PMONITOR = g_pCompositor->getMonitorFromCursor(); + if (parentLayer && parentLayer->m_layerSurface->m_current.interactivity != ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE) { + Desktop::focusState()->rawSurfaceFocus(parentLayer->wlSurface()->resource()); + } else { + static auto PFOLLOWMOUSE = CConfigValue("input:follow_mouse"); + if (*PFOLLOWMOUSE == 0 || *PFOLLOWMOUSE == 2 || *PFOLLOWMOUSE == 3) { + const auto PMONITOR = g_pCompositor->getMonitorFromCursor(); - // If this was a popup grab, focus its parent window to maintain context - if (validMapped(parentWindow)) { - Desktop::focusState()->rawWindowFocus(parentWindow); - Log::logger->log(Log::DEBUG, "[seatmgr] Refocused popup parent window {} (follow_mouse={})", parentWindow->m_title, *PFOLLOWMOUSE); + // If this was a popup grab, focus its parent window to maintain context + if (validMapped(parentWindow)) { + Desktop::focusState()->rawWindowFocus(parentWindow, Desktop::FOCUS_REASON_FFM); + Log::logger->log(Log::DEBUG, "[seatmgr] Refocused popup parent window {} (follow_mouse={})", parentWindow->m_title, *PFOLLOWMOUSE); + } else + g_pInputManager->refocusLastWindow(PMONITOR); } else - g_pInputManager->refocusLastWindow(PMONITOR); - } else - g_pInputManager->refocus(); + g_pInputManager->refocus(); + } auto currentFocus = m_state.keyboardFocus.lock(); auto refocus = !currentFocus; @@ -693,7 +701,7 @@ void CSeatManager::setGrab(SP grab) { auto candidate = Desktop::focusState()->window(); if (candidate) - Desktop::focusState()->rawWindowFocus(candidate); + Desktop::focusState()->rawWindowFocus(candidate, Desktop::FOCUS_REASON_FFM); } if (oldGrab->m_onEnd) diff --git a/src/managers/SeatManager.hpp b/src/managers/SeatManager.hpp index fe11f930..21736e3a 100644 --- a/src/managers/SeatManager.hpp +++ b/src/managers/SeatManager.hpp @@ -127,9 +127,6 @@ class CSeatManager { void setGrab(SP grab); // nullptr removes SP m_seatGrab; - bool m_isPointerFrameSkipped = false; - bool m_isPointerFrameCommit = false; - private: struct SSeatResourceContainer { SSeatResourceContainer(SP); diff --git a/src/managers/VersionKeeperManager.cpp b/src/managers/VersionKeeperManager.cpp index 6f94fbe5..b1d167fa 100644 --- a/src/managers/VersionKeeperManager.cpp +++ b/src/managers/VersionKeeperManager.cpp @@ -34,8 +34,8 @@ CVersionKeeperManager::CVersionKeeperManager() { return; } - if (!isVersionOlderThanRunning(*LASTVER)) { - Log::logger->log(Log::DEBUG, "CVersionKeeperManager: Read version {} matches or is older than running.", *LASTVER); + if (!isMajorVersionOlderThanRunning(*LASTVER)) { + Log::logger->log(Log::DEBUG, "CVersionKeeperManager: Read version {} matches or is older than running major.", *LASTVER); return; } @@ -59,25 +59,21 @@ CVersionKeeperManager::CVersionKeeperManager() { }); } -bool CVersionKeeperManager::isVersionOlderThanRunning(const std::string& ver) { +bool CVersionKeeperManager::isMajorVersionOlderThanRunning(const std::string& ver) { const CVarList verStrings(ver, 0, '.', true); const int V1 = configStringToInt(verStrings[0]).value_or(0); const int V2 = configStringToInt(verStrings[1]).value_or(0); - const int V3 = configStringToInt(verStrings[2]).value_or(0); static const CVarList runningStrings(HYPRLAND_VERSION, 0, '.', true); static const int R1 = configStringToInt(runningStrings[0]).value_or(0); static const int R2 = configStringToInt(runningStrings[1]).value_or(0); - static const int R3 = configStringToInt(runningStrings[2]).value_or(0); if (R1 > V1) return true; if (R2 > V2) return true; - if (R3 > V3) - return true; return false; } diff --git a/src/managers/VersionKeeperManager.hpp b/src/managers/VersionKeeperManager.hpp index 15821879..11bfb7df 100644 --- a/src/managers/VersionKeeperManager.hpp +++ b/src/managers/VersionKeeperManager.hpp @@ -10,7 +10,7 @@ class CVersionKeeperManager { bool fired(); private: - bool isVersionOlderThanRunning(const std::string& ver); + bool isMajorVersionOlderThanRunning(const std::string& ver); bool m_fired = false; }; diff --git a/src/managers/WelcomeManager.cpp b/src/managers/WelcomeManager.cpp index 7a0b8f7f..6faf58c3 100644 --- a/src/managers/WelcomeManager.cpp +++ b/src/managers/WelcomeManager.cpp @@ -1,4 +1,5 @@ #include "WelcomeManager.hpp" +#include "../Compositor.hpp" #include "../debug/log/Logger.hpp" #include "../config/ConfigValue.hpp" #include "../helpers/fs/FsUtils.hpp" @@ -15,6 +16,11 @@ CWelcomeManager::CWelcomeManager() { return; } + if (g_pCompositor->m_safeMode) { + Log::logger->log(Log::DEBUG, "[welcome] skipping, safe mode"); + return; + } + if (!NFsUtils::executableExistsInPath("hyprland-welcome")) { Log::logger->log(Log::DEBUG, "[welcome] skipping, no welcome app"); return; diff --git a/src/managers/XWaylandManager.cpp b/src/managers/XWaylandManager.cpp index c3c4f901..1fca293a 100644 --- a/src/managers/XWaylandManager.cpp +++ b/src/managers/XWaylandManager.cpp @@ -89,7 +89,7 @@ CBox CHyprXWaylandManager::getGeometryForWindow(PHLWINDOW pWindow) { box = pWindow->m_xdgSurface->m_current.geometry; Vector2D MINSIZE = pWindow->minSize().value_or(Vector2D{MIN_WINDOW_SIZE, MIN_WINDOW_SIZE}); - Vector2D MAXSIZE = pWindow->maxSize().value_or(Math::VECTOR2D_MAX); + Vector2D MAXSIZE = pWindow->maxSize().value_or(Math::VECTOR2D_MAX).clamp(MINSIZE + Vector2D{1, 1}); Vector2D oldSize = box.size(); box.w = std::clamp(box.w, MINSIZE.x, MAXSIZE.x); @@ -129,7 +129,8 @@ bool CHyprXWaylandManager::shouldBeFloated(PHLWINDOW pWindow, bool pending) { const auto SIZEHINTS = pWindow->m_xwaylandSurface->m_sizeHints.get(); if (pWindow->m_xwaylandSurface->m_transient || pWindow->m_xwaylandSurface->m_parent || - (SIZEHINTS && (SIZEHINTS->min_width == SIZEHINTS->max_width) && (SIZEHINTS->min_height == SIZEHINTS->max_height))) + (SIZEHINTS && SIZEHINTS->min_width > 0 && SIZEHINTS->min_height > 0 && SIZEHINTS->max_width > 0 && SIZEHINTS->max_height > 0 && + (SIZEHINTS->min_width == SIZEHINTS->max_width) && (SIZEHINTS->min_height == SIZEHINTS->max_height))) return true; } else { if (!pWindow->m_xdgSurface || !pWindow->m_xdgSurface->m_toplevel) diff --git a/src/managers/animation/AnimationManager.cpp b/src/managers/animation/AnimationManager.cpp index 9a3fc157..c4b921cb 100644 --- a/src/managers/animation/AnimationManager.cpp +++ b/src/managers/animation/AnimationManager.cpp @@ -1,6 +1,5 @@ #include "AnimationManager.hpp" #include "../../Compositor.hpp" -#include "../HookSystemManager.hpp" #include "../../config/ConfigManager.hpp" #include "../../desktop/DesktopTypes.hpp" #include "../../helpers/AnimatedVariable.hpp" @@ -11,6 +10,7 @@ #include "../eventLoop/EventLoopManager.hpp" #include "../../helpers/varlist/VarList.hpp" #include "../../render/Renderer.hpp" +#include "../../event/EventBus.hpp" #include #include @@ -198,7 +198,7 @@ static void handleUpdate(CAnimatedVariable& av, bool warp) { } // manually schedule a frame - if (PMONITOR) + if (PMONITOR && !PMONITOR->inFullscreenMode()) g_pCompositor->scheduleFrameForMonitor(PMONITOR, Aquamarine::IOutput::AQ_SCHEDULE_ANIMATION); } @@ -216,6 +216,9 @@ void CHyprAnimationManager::tick() { if (!PAV) continue; + // lock this value while we are doing handleUpdate to avoid a UAF if an update callback destroys it + const auto LOCK = PAV.lock(); + // for disabled anims just warp bool warp = !*PANIMENABLED || !PAV->enabled(); @@ -249,8 +252,8 @@ void CHyprAnimationManager::frameTick() { if (!shouldTickForNext()) return; - if (!g_pCompositor->m_sessionActive || !g_pHookSystem || g_pCompositor->m_unsafeState || - !std::ranges::any_of(g_pCompositor->m_monitors, [](const auto& mon) { return mon->m_enabled && mon->m_output; })) + if UNLIKELY (!g_pCompositor->m_sessionActive || g_pCompositor->m_unsafeState || + !std::ranges::any_of(g_pCompositor->m_monitors, [](const auto& mon) { return mon->m_enabled && mon->m_output; })) return; if (!m_lastTickValid || m_lastTickTimer.getMillis() >= 1.0f) { @@ -258,7 +261,7 @@ void CHyprAnimationManager::frameTick() { m_lastTickValid = true; tick(); - EMIT_HOOK_EVENT("tick", nullptr); + Event::bus()->m_events.tick.emit(); } if (shouldTickForNext()) @@ -283,6 +286,11 @@ void CHyprAnimationManager::onTicked() { m_tickScheduled = false; } +void CHyprAnimationManager::resetTickState() { + m_lastTickValid = false; + m_tickScheduled = false; +} + std::string CHyprAnimationManager::styleValidInConfigVar(const std::string& config, const std::string& style) { if (config.starts_with("window")) { if (style.starts_with("slide") || style == "gnome" || style == "gnomed") diff --git a/src/managers/animation/AnimationManager.hpp b/src/managers/animation/AnimationManager.hpp index b8acc53e..35bb1e8a 100644 --- a/src/managers/animation/AnimationManager.hpp +++ b/src/managers/animation/AnimationManager.hpp @@ -18,6 +18,9 @@ class CHyprAnimationManager : public Hyprutils::Animation::CAnimationManager { virtual void scheduleTick(); virtual void onTicked(); + // Reset tick state after session changes (suspend/wake, lock/unlock) + void resetTickState(); + using SAnimationPropertyConfig = Hyprutils::Animation::SAnimationPropertyConfig; template void createAnimation(const VarType& v, PHLANIMVAR& pav, SP pConfig, eAVarDamagePolicy policy) { diff --git a/src/managers/animation/DesktopAnimationManager.cpp b/src/managers/animation/DesktopAnimationManager.cpp index 333df7e7..2c450add 100644 --- a/src/managers/animation/DesktopAnimationManager.cpp +++ b/src/managers/animation/DesktopAnimationManager.cpp @@ -1,11 +1,15 @@ #include "DesktopAnimationManager.hpp" +#include + #include "../../desktop/view/LayerSurface.hpp" #include "../../desktop/view/Window.hpp" +#include "../../desktop/view/Group.hpp" #include "../../desktop/Workspace.hpp" #include "../../config/ConfigManager.hpp" #include "../../Compositor.hpp" +#include "desktop/DesktopTypes.hpp" #include "wlr-layer-shell-unstable-v1.hpp" void CDesktopAnimationManager::startAnimation(PHLWINDOW pWindow, eAnimationType type, bool force) { @@ -406,32 +410,26 @@ void CDesktopAnimationManager::animationSlide(PHLWINDOW pWindow, std::string for } const auto MIDPOINT = GOALPOS + GOALSIZE / 2.f; + const auto MONBOX = PMONITOR->logicalBox(); - // check sides it touches - const auto MONITOR_WORKAREA = PMONITOR->logicalBoxMinusReserved(); - const bool DISPLAYLEFT = STICKS(pWindow->m_position.x, MONITOR_WORKAREA.x); - const bool DISPLAYRIGHT = STICKS(pWindow->m_position.x + pWindow->m_size.x, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w); - const bool DISPLAYTOP = STICKS(pWindow->m_position.y, MONITOR_WORKAREA.y); - const bool DISPLAYBOTTOM = STICKS(pWindow->m_position.y + pWindow->m_size.y, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h); + // find the closest edge to midpoint + // CSS style, top right bottom left + std::array distances = { + MIDPOINT.y - MONBOX.y, // + MONBOX.x + MONBOX.w - MIDPOINT.x, // + MONBOX.y + MONBOX.h - MIDPOINT.y, // + MIDPOINT.x - MONBOX.x, // + }; - if (DISPLAYBOTTOM && DISPLAYTOP) { - if (DISPLAYLEFT && DISPLAYRIGHT) { - posOffset = GOALPOS + Vector2D(0.0, GOALSIZE.y); - } else if (DISPLAYLEFT) { - posOffset = GOALPOS - Vector2D(GOALSIZE.x, 0.0); - } else { - posOffset = GOALPOS + Vector2D(GOALSIZE.x, 0.0); - } - } else if (DISPLAYTOP) { - posOffset = GOALPOS - Vector2D(0.0, GOALSIZE.y); - } else if (DISPLAYBOTTOM) { - posOffset = GOALPOS + Vector2D(0.0, GOALSIZE.y); - } else { - if (MIDPOINT.y > PMONITOR->m_position.y + PMONITOR->m_size.y / 2.f) - posOffset = Vector2D(GOALPOS.x, PMONITOR->m_position.y + PMONITOR->m_size.y); - else - posOffset = Vector2D(GOALPOS.x, PMONITOR->m_position.y - GOALSIZE.y); - } + const auto MIN_DIST = std::min({distances[0], distances[1], distances[2], distances[3]}); + if (MIN_DIST == distances[2]) + posOffset = Vector2D(GOALPOS.x, PMONITOR->m_position.y + PMONITOR->m_size.y); + else if (MIN_DIST == distances[3]) + posOffset = GOALPOS - Vector2D(GOALSIZE.x, 0.0); + else if (MIN_DIST == distances[1]) + posOffset = GOALPOS + Vector2D(GOALSIZE.x, 0.0); + else + posOffset = Vector2D(GOALPOS.x, PMONITOR->m_position.y - GOALSIZE.y); if (!close) pWindow->m_realPosition->setValue(posOffset); @@ -472,7 +470,7 @@ void CDesktopAnimationManager::setFullscreenFadeAnimation(PHLWORKSPACE ws, eAnim *w->m_alpha = 1.F; else if (!w->isFullscreen()) { const bool CREATED_OVER_FS = w->m_createdOverFullscreen; - const bool IS_IN_GROUP_OF_FS = FSWINDOW && FSWINDOW->hasInGroup(w); + const bool IS_IN_GROUP_OF_FS = FSWINDOW && FSWINDOW->m_group && FSWINDOW->m_group->has(w); *w->m_alpha = !CREATED_OVER_FS && !IS_IN_GROUP_OF_FS ? 0.f : 1.f; } } @@ -488,6 +486,13 @@ void CDesktopAnimationManager::setFullscreenFadeAnimation(PHLWORKSPACE ws, eAnim } } +void CDesktopAnimationManager::setFullscreenFloatingFade(PHLWINDOW pWindow, float fade) { + if (pWindow->m_fadingOut || !pWindow->m_isFloating) + return; + + *pWindow->m_alpha = fade; +} + void CDesktopAnimationManager::overrideFullscreenFadeAmount(PHLWORKSPACE ws, float fade, PHLWINDOW exclude) { for (auto const& w : g_pCompositor->m_windows) { if (w == exclude) diff --git a/src/managers/animation/DesktopAnimationManager.hpp b/src/managers/animation/DesktopAnimationManager.hpp index f27f09d2..fa86425e 100644 --- a/src/managers/animation/DesktopAnimationManager.hpp +++ b/src/managers/animation/DesktopAnimationManager.hpp @@ -16,6 +16,7 @@ class CDesktopAnimationManager { void startAnimation(PHLWORKSPACE ws, eAnimationType type, bool left = true, bool instant = false); void setFullscreenFadeAnimation(PHLWORKSPACE ws, eAnimationType type); + void setFullscreenFloatingFade(PHLWINDOW pWindow, float fade); void overrideFullscreenFadeAmount(PHLWORKSPACE ws, float fade, PHLWINDOW exclude = nullptr); private: diff --git a/src/managers/eventLoop/EventLoopManager.cpp b/src/managers/eventLoop/EventLoopManager.cpp index 496cbb83..e38474aa 100644 --- a/src/managers/eventLoop/EventLoopManager.cpp +++ b/src/managers/eventLoop/EventLoopManager.cpp @@ -26,10 +26,7 @@ CEventLoopManager::~CEventLoopManager() { wl_event_source_remove(eventSourceData.eventSource); } - for (auto const& w : m_readableWaiters) { - if (w->source != nullptr) - wl_event_source_remove(w->source); - } + m_readableWaiters.clear(); if (m_wayland.eventSource) wl_event_source_remove(m_wayland.eventSource); @@ -40,14 +37,21 @@ CEventLoopManager::~CEventLoopManager() { } static int timerWrite(int fd, uint32_t mask, void* data) { + if (!CFileDescriptor::isReadable(fd)) + Log::logger->log(Log::ERR, "timerWrite: triggered a non readable event on fd : {}", fd); + else { + uint64_t expirations; + read(fd, &expirations, sizeof(expirations)); + } + g_pEventLoopManager->onTimerFire(); - return 1; + return 0; } static int aquamarineFDWrite(int fd, uint32_t mask, void* data) { auto POLLFD = sc(data); POLLFD->onSignal(); - return 1; + return 0; } static int configWatcherWrite(int fd, uint32_t mask, void* data) { @@ -56,13 +60,21 @@ static int configWatcherWrite(int fd, uint32_t mask, void* data) { } static int handleWaiterFD(int fd, uint32_t mask, void* data) { + auto waiter = sc(data); + + if (!waiter) { + Log::logger->log(Log::ERR, "handleWaiterFD: failed casting waiter"); + return 0; + } + if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) { Log::logger->log(Log::ERR, "handleWaiterFD: readable waiter error"); + g_pEventLoopManager->onFdReadableFail(waiter); return 0; } if (mask & WL_EVENT_READABLE) - g_pEventLoopManager->onFdReadable(sc(data)); + g_pEventLoopManager->onFdReadable(waiter); return 0; } @@ -74,6 +86,11 @@ void CEventLoopManager::onFdReadable(SReadableWaiter* waiter) { if (it == m_readableWaiters.end()) return; + if (waiter->source) { // remove even_source if fn() somehow causes a reentry + wl_event_source_remove(waiter->source); + waiter->source = nullptr; + } + UP taken = std::move(*it); m_readableWaiters.erase(it); @@ -81,6 +98,16 @@ void CEventLoopManager::onFdReadable(SReadableWaiter* waiter) { taken->fn(); } +void CEventLoopManager::onFdReadableFail(SReadableWaiter* waiter) { + auto it = std::ranges::find_if(m_readableWaiters, [waiter](const UP& w) { return waiter == w.get() && w->fd == waiter->fd && w->source == waiter->source; }); + + // ??? + if (it == m_readableWaiters.end()) + return; + + m_readableWaiters.erase(it); +} + void CEventLoopManager::enterLoop() { m_wayland.eventSource = wl_event_loop_add_fd(m_wayland.loop, m_timers.timerfd.get(), WL_EVENT_READABLE, timerWrite, nullptr); @@ -182,12 +209,12 @@ void CEventLoopManager::doLater(const std::function& fn) { m_wayland.loop, [](void* data) { auto IDLE = sc(data); - auto cpy = IDLE->fns; + auto fns = std::move(IDLE->fns); IDLE->fns.clear(); IDLE->eventSource = nullptr; - for (auto const& c : cpy) { - if (c) - c(); + for (auto& f : fns) { + if (f) + f(); } }, &m_idle); diff --git a/src/managers/eventLoop/EventLoopManager.hpp b/src/managers/eventLoop/EventLoopManager.hpp index 7a3b4314..7999dc59 100644 --- a/src/managers/eventLoop/EventLoopManager.hpp +++ b/src/managers/eventLoop/EventLoopManager.hpp @@ -65,6 +65,7 @@ class CEventLoopManager { // takes ownership of fd void doOnReadable(Hyprutils::OS::CFileDescriptor fd, std::function&& fn); void onFdReadable(SReadableWaiter* waiter); + void onFdReadableFail(SReadableWaiter* waiter); private: // Manages the event sources after AQ pollFDs change. diff --git a/src/managers/input/InputManager.cpp b/src/managers/input/InputManager.cpp index 73da6df4..9195536f 100644 --- a/src/managers/input/InputManager.cpp +++ b/src/managers/input/InputManager.cpp @@ -35,14 +35,16 @@ #include "../../managers/SeatManager.hpp" #include "../../managers/KeybindManager.hpp" #include "../../render/Renderer.hpp" -#include "../../managers/HookSystemManager.hpp" #include "../../managers/EventManager.hpp" -#include "../../managers/LayoutManager.hpp" #include "../../managers/permissions/DynamicPermissionManager.hpp" #include "../../helpers/time/Time.hpp" #include "../../helpers/MiscFunctions.hpp" +#include "../../layout/LayoutManager.hpp" + +#include "../../event/EventBus.hpp" + #include "trackpad/TrackpadGestures.hpp" #include "../cursor/CursorShapeOverrideController.hpp" @@ -130,16 +132,10 @@ void CInputManager::onMouseMoved(IPointer::SMotionEvent e) { const auto DELTA = *PNOACCEL == 1 ? unaccel : delta; - if (g_pSeatManager->m_isPointerFrameSkipped) - g_pPointerManager->storeMovement(e.timeMs, DELTA, unaccel); - else - g_pPointerManager->setStoredMovement(e.timeMs, DELTA, unaccel); - - PROTO::relativePointer->sendRelativeMotion(sc(e.timeMs) * 1000, DELTA, unaccel); - if (e.mouse) recheckMouseWarpOnMouseInput(); + PROTO::relativePointer->sendRelativeMotion(sc(e.timeMs) * 1000, delta, unaccel); g_pPointerManager->move(DELTA); mouseMoveUnified(e.timeMs, false, e.mouse); @@ -151,6 +147,8 @@ void CInputManager::onMouseMoved(IPointer::SMotionEvent e) { if (e.mouse) m_lastMousePos = getMouseCoordsInternal(); + + g_pSeatManager->sendPointerFrame(); } void CInputManager::onMouseWarp(IPointer::SMotionAbsoluteEvent e) { @@ -195,7 +193,7 @@ void CInputManager::sendMotionEventsToFocused() { m_emptyFocusCursorSet = false; - g_pSeatManager->setPointerFocus(Desktop::focusState()->surface(), m_lastCursorPosFloored - BOX->pos()); + g_pSeatManager->setPointerFocus(Desktop::focusState()->surface(), getMouseCoordsInternal().floor() - BOX->pos()); } void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, std::optional overridePos) { @@ -234,8 +232,12 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st Vector2D surfacePos = Vector2D(-1337, -1337); PHLWINDOW pFoundWindow; PHLLS pFoundLayerSurface; + const auto FOCUS_REASON = refocus ? Desktop::FOCUS_REASON_CLICK : Desktop::FOCUS_REASON_FFM; - EMIT_HOOK_EVENT_CANCELLABLE("mouseMove", MOUSECOORDSFLOORED); + Event::SCallbackInfo info; + Event::bus()->m_events.input.mouse.move.emit(MOUSECOORDSFLOORED, info); + if (info.cancelled) + return; m_lastCursorPosFloored = MOUSECOORDSFLOORED; @@ -369,7 +371,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st } } - g_pLayoutManager->getCurrentLayout()->onMouseMove(getMouseCoordsInternal()); + g_layoutManager->moveMouse(getMouseCoordsInternal()); // forced above all if (!g_pInputManager->m_exclusiveLSes.empty()) { @@ -526,7 +528,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st g_pSeatManager->setPointerFocus(nullptr, {}); if (refocus || !Desktop::focusState()->window()) // if we are forcing a refocus, and we don't find a surface, clear the kb focus too! - Desktop::focusState()->rawWindowFocus(nullptr); + Desktop::focusState()->rawWindowFocus(nullptr, FOCUS_REASON); return; } @@ -554,7 +556,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st m_foundSurfaceToFocus = foundSurface; } - if (m_currentlyDraggedWindow.lock() && pFoundWindow != m_currentlyDraggedWindow) { + if (g_layoutManager->dragController()->target() && pFoundWindow != g_layoutManager->dragController()->target()) { g_pSeatManager->setPointerFocus(foundSurface, surfaceLocal); return; } @@ -586,7 +588,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st ((pFoundWindow->m_isFloating && *PFLOATBEHAVIOR == 2) || (Desktop::focusState()->window()->m_isFloating != pFoundWindow->m_isFloating && *PFLOATBEHAVIOR != 0))) { // enter if change floating style if (FOLLOWMOUSE != 3 && allowKeyboardRefocus) - Desktop::focusState()->rawWindowFocus(pFoundWindow, foundSurface); + Desktop::focusState()->rawWindowFocus(pFoundWindow, FOCUS_REASON, foundSurface); g_pSeatManager->setPointerFocus(foundSurface, surfaceLocal); } else if (FOLLOWMOUSE == 2 || FOLLOWMOUSE == 3) g_pSeatManager->setPointerFocus(foundSurface, surfaceLocal); @@ -614,7 +616,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st const bool hasNoFollowMouse = pFoundWindow && pFoundWindow->m_ruleApplicator->noFollowMouse().valueOrDefault(); if (refocus || !hasNoFollowMouse) - Desktop::focusState()->rawWindowFocus(pFoundWindow, foundSurface); + Desktop::focusState()->rawWindowFocus(pFoundWindow, FOCUS_REASON, foundSurface); } } else Desktop::focusState()->rawSurfaceFocus(foundSurface, pFoundWindow); @@ -623,7 +625,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st } if (g_pSeatManager->m_state.keyboardFocus == nullptr) - Desktop::focusState()->rawWindowFocus(pFoundWindow, foundSurface); + Desktop::focusState()->rawWindowFocus(pFoundWindow, FOCUS_REASON, foundSurface); m_lastFocusOnLS = false; } else { @@ -646,7 +648,10 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st } void CInputManager::onMouseButton(IPointer::SButtonEvent e) { - EMIT_HOOK_EVENT_CANCELLABLE("mouseButton", e); + Event::SCallbackInfo info; + Event::bus()->m_events.input.mouse.button.emit(e, info); + if (info.cancelled) + return; if (e.mouse) recheckMouseWarpOnMouseInput(); @@ -676,6 +681,8 @@ void CInputManager::onMouseButton(IPointer::SButtonEvent e) { m_focusHeldByButtons = false; m_refocusHeldByButtons = false; } + + g_pSeatManager->sendPointerFrame(); } void CInputManager::processMouseRequest(const CSeatManager::SSetCursorEvent& event) { @@ -806,8 +813,10 @@ void CInputManager::processMouseDownNormal(const IPointer::SButtonEvent& e) { auto HLSurf = Desktop::View::CWLSurface::fromResource(g_pSeatManager->m_state.pointerFocus.lock()); - if (HLSurf && HLSurf->view()->type() == Desktop::View::VIEW_TYPE_WINDOW) - g_pCompositor->changeWindowZOrder(dynamicPointerCast(HLSurf->view()), true); + // pointerFocus can target a surface without a Desktop::View (e.g. IME popups), so view() may be null. + const auto PVIEW = HLSurf ? HLSurf->view() : nullptr; + if (PVIEW && PVIEW->type() == Desktop::View::VIEW_TYPE_WINDOW) + g_pCompositor->changeWindowZOrder(dynamicPointerCast(PVIEW), true); break; } @@ -838,6 +847,9 @@ void CInputManager::processMouseDownKill(const IPointer::SButtonEvent& e) { break; } + g_pEventManager->postEvent(SHyprIPCEvent({.event = "kill", .data = std::format("{:x}", rc(PWINDOW.m_data))})); + Event::bus()->m_events.window.kill.emit(PWINDOW); + // kill the mf kill(PWINDOW->getPID(), SIGKILL); break; @@ -864,8 +876,10 @@ void CInputManager::onMouseWheel(IPointer::SAxisEvent e, SP pointer) { if (pointer && pointer->m_scrollFactor.has_value()) factor = *pointer->m_scrollFactor; - const auto EMAP = std::unordered_map{{"event", e}}; - EMIT_HOOK_EVENT_CANCELLABLE("mouseAxis", EMAP); + Event::SCallbackInfo info; + Event::bus()->m_events.input.mouse.axis.emit(e, info); + if (info.cancelled) + return; if (e.mouse) recheckMouseWarpOnMouseInput(); @@ -952,6 +966,23 @@ void CInputManager::onMouseWheel(IPointer::SAxisEvent e, SP pointer) { int32_t deltaDiscrete = std::abs(discrete) != 0 && std::abs(discrete) < 1 ? std::copysign(1, discrete) : std::round(discrete); g_pSeatManager->sendPointerAxis(e.timeMs, e.axis, delta, deltaDiscrete, value120, e.source, WL_POINTER_AXIS_RELATIVE_DIRECTION_IDENTICAL); + + const bool deferPointerFrame = e.source == WL_POINTER_AXIS_SOURCE_FINGER || e.source == WL_POINTER_AXIS_SOURCE_CONTINUOUS; + if (deferPointerFrame) { + m_pointerAxisFramePending = true; + return; + } + + m_pointerAxisFramePending = false; + g_pSeatManager->sendPointerFrame(); +} + +void CInputManager::onPointerFrame() { + if (!m_pointerAxisFramePending) + return; + + m_pointerAxisFramePending = false; + g_pSeatManager->sendPointerFrame(); } Vector2D CInputManager::getMouseCoordsInternal() { @@ -1037,7 +1068,7 @@ void CInputManager::setupKeyboard(SP keeb) { } g_pEventManager->postEvent(SHyprIPCEvent{"activelayout", PKEEB->m_hlName + "," + LAYOUT}); - EMIT_HOOK_EVENT("activeLayout", (std::vector{PKEEB, LAYOUT})); + Event::bus()->m_events.input.keyboard.layout.emit(PKEEB, LAYOUT); }); disableAllKeyboards(false); @@ -1134,7 +1165,7 @@ void CInputManager::applyConfigToKeyboard(SP pKeyboard) { const auto LAYOUTSTR = pKeyboard->getActiveLayout(); g_pEventManager->postEvent(SHyprIPCEvent{"activelayout", pKeyboard->m_hlName + "," + LAYOUTSTR}); - EMIT_HOOK_EVENT("activeLayout", (std::vector{pKeyboard, LAYOUTSTR})); + Event::bus()->m_events.input.keyboard.layout.emit(pKeyboard, LAYOUTSTR); Log::logger->log(Log::DEBUG, "Set the keyboard layout to {} and variant to {} for keyboard \"{}\"", pKeyboard->m_currentRules.layout, pKeyboard->m_currentRules.variant, pKeyboard->m_hlName); @@ -1456,14 +1487,16 @@ void CInputManager::onKeyboardKey(const IKeyboard::SKeyEvent& event, SPm_enabled || !pKeyboard->m_allowed) return; - const bool DISALLOWACTION = pKeyboard->isVirtual() && shouldIgnoreVirtualKeyboard(pKeyboard); + const bool DISALLOWACTION = pKeyboard->isVirtual() && shouldIgnoreVirtualKeyboard(pKeyboard); - const auto IME = m_relay.m_inputMethod.lock(); - const bool HASIME = IME && IME->hasGrab(); - const bool USEIME = HASIME && !DISALLOWACTION; + const auto IME = m_relay.m_inputMethod.lock(); + const bool HASIME = IME && IME->hasGrab(); + const bool USEIME = HASIME && !DISALLOWACTION; - const auto EMAP = std::unordered_map{{"keyboard", pKeyboard}, {"event", event}}; - EMIT_HOOK_EVENT_CANCELLABLE("keyPress", EMAP); + Event::SCallbackInfo info; + Event::bus()->m_events.input.keyboard.key.emit(event, info); + if (info.cancelled) + return; bool passEvent = DISALLOWACTION; @@ -1552,7 +1585,7 @@ void CInputManager::onKeyboardMod(SP pKeyboard) { Log::logger->log(Log::DEBUG, "LAYOUT CHANGED TO {} GROUP {}", LAYOUT, MODS.group); g_pEventManager->postEvent(SHyprIPCEvent{"activelayout", pKeyboard->m_hlName + "," + LAYOUT}); - EMIT_HOOK_EVENT("activeLayout", (std::vector{pKeyboard, LAYOUT})); + Event::bus()->m_events.input.keyboard.layout.emit(pKeyboard, LAYOUT); } } @@ -1612,13 +1645,13 @@ bool CInputManager::refocusLastWindow(PHLMONITOR pMonitor) { if (!foundSurface && Desktop::focusState()->window() && Desktop::focusState()->window()->m_workspace && Desktop::focusState()->window()->m_workspace->isVisibleNotCovered()) { // then the last focused window if we're on the same workspace as it const auto PLASTWINDOW = Desktop::focusState()->window(); - Desktop::focusState()->fullWindowFocus(PLASTWINDOW); + Desktop::focusState()->fullWindowFocus(PLASTWINDOW, Desktop::FOCUS_REASON_FFM); } else { // otherwise fall back to a normal refocus. if (foundSurface && !foundSurface->m_hlSurface->keyboardFocusable()) { const auto PLASTWINDOW = Desktop::focusState()->window(); - Desktop::focusState()->fullWindowFocus(PLASTWINDOW); + Desktop::focusState()->fullWindowFocus(PLASTWINDOW, Desktop::FOCUS_REASON_FFM); } refocus(); @@ -1934,7 +1967,7 @@ void CInputManager::setCursorIconOnBorder(PHLWINDOW w) { if (w->hasPopupAt(mouseCoords)) direction = BORDERICON_NONE; - else if (!boxFullGrabInput.containsPoint(mouseCoords) || (!m_currentlyHeldButtons.empty() && m_currentlyDraggedWindow.expired())) + else if (!boxFullGrabInput.containsPoint(mouseCoords) || (!m_currentlyHeldButtons.empty() && !g_layoutManager->dragController()->target())) direction = BORDERICON_NONE; else { @@ -2020,7 +2053,10 @@ void CInputManager::recheckMouseWarpOnMouseInput() { } void CInputManager::onSwipeBegin(IPointer::SSwipeBeginEvent e) { - EMIT_HOOK_EVENT_CANCELLABLE("swipeBegin", e); + Event::SCallbackInfo info; + Event::bus()->m_events.gesture.swipe.begin.emit(e, info); + if (info.cancelled) + return; g_pTrackpadGestures->gestureBegin(e); @@ -2028,7 +2064,10 @@ void CInputManager::onSwipeBegin(IPointer::SSwipeBeginEvent e) { } void CInputManager::onSwipeUpdate(IPointer::SSwipeUpdateEvent e) { - EMIT_HOOK_EVENT_CANCELLABLE("swipeUpdate", e); + Event::SCallbackInfo info; + Event::bus()->m_events.gesture.swipe.update.emit(e, info); + if (info.cancelled) + return; g_pTrackpadGestures->gestureUpdate(e); @@ -2036,7 +2075,10 @@ void CInputManager::onSwipeUpdate(IPointer::SSwipeUpdateEvent e) { } void CInputManager::onSwipeEnd(IPointer::SSwipeEndEvent e) { - EMIT_HOOK_EVENT_CANCELLABLE("swipeEnd", e); + Event::SCallbackInfo info; + Event::bus()->m_events.gesture.swipe.end.emit(e, info); + if (info.cancelled) + return; g_pTrackpadGestures->gestureEnd(e); @@ -2044,7 +2086,10 @@ void CInputManager::onSwipeEnd(IPointer::SSwipeEndEvent e) { } void CInputManager::onPinchBegin(IPointer::SPinchBeginEvent e) { - EMIT_HOOK_EVENT_CANCELLABLE("pinchBegin", e); + Event::SCallbackInfo info; + Event::bus()->m_events.gesture.pinch.begin.emit(e, info); + if (info.cancelled) + return; g_pTrackpadGestures->gestureBegin(e); @@ -2052,7 +2097,10 @@ void CInputManager::onPinchBegin(IPointer::SPinchBeginEvent e) { } void CInputManager::onPinchUpdate(IPointer::SPinchUpdateEvent e) { - EMIT_HOOK_EVENT_CANCELLABLE("pinchUpdate", e); + Event::SCallbackInfo info; + Event::bus()->m_events.gesture.pinch.update.emit(e, info); + if (info.cancelled) + return; g_pTrackpadGestures->gestureUpdate(e); @@ -2060,7 +2108,10 @@ void CInputManager::onPinchUpdate(IPointer::SPinchUpdateEvent e) { } void CInputManager::onPinchEnd(IPointer::SPinchEndEvent e) { - EMIT_HOOK_EVENT_CANCELLABLE("pinchEnd", e); + Event::SCallbackInfo info; + Event::bus()->m_events.gesture.pinch.end.emit(e, info); + if (info.cancelled) + return; g_pTrackpadGestures->gestureEnd(e); diff --git a/src/managers/input/InputManager.hpp b/src/managers/input/InputManager.hpp index 239f6140..ca235a23 100644 --- a/src/managers/input/InputManager.hpp +++ b/src/managers/input/InputManager.hpp @@ -91,6 +91,7 @@ class CInputManager { void onMouseWarp(IPointer::SMotionAbsoluteEvent); void onMouseButton(IPointer::SButtonEvent); void onMouseWheel(IPointer::SAxisEvent, SP pointer = nullptr); + void onPointerFrame(); void onKeyboardKey(const IKeyboard::SKeyEvent&, SP); void onKeyboardMod(SP); @@ -153,12 +154,6 @@ class CInputManager { STouchData m_touchData; - // for dragging floating windows - PHLWINDOWREF m_currentlyDraggedWindow; - eMouseBindMode m_dragMode = MBIND_INVALID; - bool m_wasDraggingWindow = false; - bool m_dragThresholdReached = false; - // for refocus to be forced PHLWINDOWREF m_forcedFocus; @@ -299,6 +294,7 @@ class CInputManager { uint32_t lastEventTime = 0; uint32_t accumulatedScroll = 0; } m_scrollWheelState; + bool m_pointerAxisFramePending = false; bool shareKeyFromAllKBs(uint32_t key, bool pressed); uint32_t shareModsFromAllKBs(uint32_t depressed); diff --git a/src/managers/input/InputMethodRelay.cpp b/src/managers/input/InputMethodRelay.cpp index 15dd249e..27fd80b6 100644 --- a/src/managers/input/InputMethodRelay.cpp +++ b/src/managers/input/InputMethodRelay.cpp @@ -1,14 +1,13 @@ #include "InputMethodRelay.hpp" #include "../../desktop/state/FocusState.hpp" +#include "../../event/EventBus.hpp" #include "../../protocols/TextInputV3.hpp" #include "../../protocols/TextInputV1.hpp" #include "../../protocols/InputMethodV2.hpp" #include "../../protocols/core/Compositor.hpp" -#include "../../managers/HookSystemManager.hpp" CInputMethodRelay::CInputMethodRelay() { - static auto P = - g_pHookSystem->hookDynamic("keyboardFocus", [&](void* self, SCallbackInfo& info, std::any param) { onKeyboardFocus(std::any_cast>(param)); }); + static auto P = Event::bus()->m_events.input.keyboard.focus.listen([&](SP surf) { onKeyboardFocus(surf); }); m_listeners.newTIV3 = PROTO::textInputV3->m_events.newTextInput.listen([this](const auto& input) { onNewTextInput(input); }); m_listeners.newTIV1 = PROTO::textInputV1->m_events.newTextInput.listen([this](const auto& input) { onNewTextInput(input); }); @@ -75,6 +74,11 @@ CTextInput* CInputMethodRelay::getFocusedTextInput() { if (!Desktop::focusState()->surface()) return nullptr; + for (auto const& ti : m_textInputs) { + if (ti->focusedSurface() == Desktop::focusState()->surface() && ti->isEnabled()) + return ti.get(); + } + for (auto const& ti : m_textInputs) { if (ti->focusedSurface() == Desktop::focusState()->surface()) return ti.get(); diff --git a/src/managers/input/Tablets.cpp b/src/managers/input/Tablets.cpp index 52be6eee..a2fec15c 100644 --- a/src/managers/input/Tablets.cpp +++ b/src/managers/input/Tablets.cpp @@ -2,11 +2,11 @@ #include "../../desktop/view/Window.hpp" #include "../../protocols/Tablet.hpp" #include "../../devices/Tablet.hpp" -#include "../../managers/HookSystemManager.hpp" #include "../../managers/PointerManager.hpp" #include "../../managers/SeatManager.hpp" #include "../../protocols/PointerConstraints.hpp" #include "../../protocols/core/DataDevice.hpp" +#include "../../event/EventBus.hpp" static void unfocusTool(SP tool) { if (!tool->getSurface()) @@ -107,6 +107,11 @@ static Vector2D transformToActiveRegion(const Vector2D pos, const CBox activeAre } void CInputManager::onTabletAxis(CTablet::SAxisEvent e) { + Event::SCallbackInfo info; + Event::bus()->m_events.input.tablet.axis.emit(e, info); + if (info.cancelled) + return; + const auto PTAB = e.tablet; const auto PTOOL = ensureTabletToolPresent(e.tool); @@ -171,7 +176,10 @@ void CInputManager::onTabletAxis(CTablet::SAxisEvent e) { } void CInputManager::onTabletTip(CTablet::STipEvent e) { - EMIT_HOOK_EVENT_CANCELLABLE("tabletTip", e); + Event::SCallbackInfo info; + Event::bus()->m_events.input.tablet.tip.emit(e, info); + if (info.cancelled) + return; const auto PTAB = e.tablet; const auto PTOOL = ensureTabletToolPresent(e.tool); @@ -196,6 +204,11 @@ void CInputManager::onTabletTip(CTablet::STipEvent e) { } void CInputManager::onTabletButton(CTablet::SButtonEvent e) { + Event::SCallbackInfo info; + Event::bus()->m_events.input.tablet.button.emit(e, info); + if (info.cancelled) + return; + const auto PTOOL = ensureTabletToolPresent(e.tool); if (e.down) @@ -210,6 +223,11 @@ void CInputManager::onTabletButton(CTablet::SButtonEvent e) { } void CInputManager::onTabletProximity(CTablet::SProximityEvent e) { + Event::SCallbackInfo info; + Event::bus()->m_events.input.tablet.proximity.emit(e, info); + if (info.cancelled) + return; + const auto PTAB = e.tablet; const auto PTOOL = ensureTabletToolPresent(e.tool); diff --git a/src/managers/input/TextInput.cpp b/src/managers/input/TextInput.cpp index 4475b5ee..be9a5d29 100644 --- a/src/managers/input/TextInput.cpp +++ b/src/managers/input/TextInput.cpp @@ -22,13 +22,7 @@ void CTextInput::initCallbacks() { m_listeners.disable = INPUT->m_events.disable.listen([this] { onDisabled(); }); m_listeners.commit = INPUT->m_events.onCommit.listen([this] { onCommit(); }); m_listeners.reset = INPUT->m_events.reset.listen([this] { onReset(); }); - m_listeners.destroy = INPUT->m_events.destroy.listen([this] { - m_listeners.surfaceUnmap.reset(); - m_listeners.surfaceDestroy.reset(); - g_pInputManager->m_relay.removeTextInput(this); - if (!g_pInputManager->m_relay.getFocusedTextInput()) - g_pInputManager->m_relay.deactivateIME(this); - }); + m_listeners.destroy = INPUT->m_events.destroy.listen([this] { destroy(); }); if (Desktop::focusState()->surface() && Desktop::focusState()->surface()->client() == INPUT->client()) enter(Desktop::focusState()->surface()); @@ -39,16 +33,20 @@ void CTextInput::initCallbacks() { m_listeners.disable = INPUT->m_events.disable.listen([this] { onDisabled(); }); m_listeners.commit = INPUT->m_events.onCommit.listen([this] { onCommit(); }); m_listeners.reset = INPUT->m_events.reset.listen([this] { onReset(); }); - m_listeners.destroy = INPUT->m_events.destroy.listen([this] { - m_listeners.surfaceUnmap.reset(); - m_listeners.surfaceDestroy.reset(); - g_pInputManager->m_relay.removeTextInput(this); - if (!g_pInputManager->m_relay.getFocusedTextInput()) - g_pInputManager->m_relay.deactivateIME(this); - }); + m_listeners.destroy = INPUT->m_events.destroy.listen([this] { destroy(); }); } } +void CTextInput::destroy() { + m_listeners.surfaceUnmap.reset(); + m_listeners.surfaceDestroy.reset(); + + g_pInputManager->m_relay.removeTextInput(this); + + if (!g_pInputManager->m_relay.getFocusedTextInput()) + g_pInputManager->m_relay.deactivateIME(nullptr, false); +} + void CTextInput::onEnabled(SP surfV1) { Log::logger->log(Log::DEBUG, "TI ENABLE"); @@ -305,3 +303,7 @@ bool CTextInput::hasCursorRectangle() { CBox CTextInput::cursorBox() { return CBox{isV3() ? m_v3Input->m_current.box.cursorBox : m_v1Input->m_cursorRectangle}; } + +bool CTextInput::isEnabled() { + return isV3() ? m_v3Input->m_current.enabled.value : true; +} diff --git a/src/managers/input/TextInput.hpp b/src/managers/input/TextInput.hpp index fd24dbfa..798f31e9 100644 --- a/src/managers/input/TextInput.hpp +++ b/src/managers/input/TextInput.hpp @@ -29,6 +29,7 @@ class CTextInput { void onCommit(); void onReset(); + bool isEnabled(); bool hasCursorRectangle(); CBox cursorBox(); @@ -38,6 +39,8 @@ class CTextInput { void setFocusedSurface(SP pSurface); void initCallbacks(); + void destroy(); + WP m_focusedSurface; int m_enterLocks = 0; WP m_v3Input; diff --git a/src/managers/input/Touch.cpp b/src/managers/input/Touch.cpp index 6136cb3f..e45bfd28 100644 --- a/src/managers/input/Touch.cpp +++ b/src/managers/input/Touch.cpp @@ -7,8 +7,8 @@ #include "../../config/ConfigValue.hpp" #include "../../helpers/Monitor.hpp" #include "../../devices/ITouch.hpp" +#include "../../event/EventBus.hpp" #include "../SeatManager.hpp" -#include "../HookSystemManager.hpp" #include "debug/log/Logger.hpp" #include "UnifiedWorkspaceSwipeGesture.hpp" @@ -19,10 +19,14 @@ void CInputManager::onTouchDown(ITouch::SDownEvent e) { static auto PGAPSOUTDATA = CConfigValue("general:gaps_out"); auto* const PGAPSOUT = sc((PGAPSOUTDATA.ptr())->getData()); // TODO: WORKSPACERULE.gapsOut.value_or() - auto gapsOut = *PGAPSOUT; - static auto PBORDERSIZE = CConfigValue("general:border_size"); - static auto PSWIPEINVR = CConfigValue("gestures:workspace_swipe_touch_invert"); - EMIT_HOOK_EVENT_CANCELLABLE("touchDown", e); + auto gapsOut = *PGAPSOUT; + static auto PBORDERSIZE = CConfigValue("general:border_size"); + static auto PSWIPEINVR = CConfigValue("gestures:workspace_swipe_touch_invert"); + + Event::SCallbackInfo info; + Event::bus()->m_events.input.touch.down.emit(e, info); + if (info.cancelled) + return; auto PMONITOR = g_pCompositor->getMonitorFromName(!e.device->m_boundOutput.empty() ? e.device->m_boundOutput : ""); @@ -109,7 +113,11 @@ void CInputManager::onTouchDown(ITouch::SDownEvent e) { void CInputManager::onTouchUp(ITouch::SUpEvent e) { m_lastInputTouch = true; - EMIT_HOOK_EVENT_CANCELLABLE("touchUp", e); + Event::SCallbackInfo info; + Event::bus()->m_events.input.touch.up.emit(e, info); + if (info.cancelled) + return; + if (g_pUnifiedWorkspaceSwipe->isGestureInProgress()) { // If there was a swipe from this finger, end it. if (e.touchID == g_pUnifiedWorkspaceSwipe->m_touchID) @@ -126,7 +134,11 @@ void CInputManager::onTouchMove(ITouch::SMotionEvent e) { m_lastCursorMovement.reset(); - EMIT_HOOK_EVENT_CANCELLABLE("touchMove", e); + Event::SCallbackInfo info; + Event::bus()->m_events.input.touch.motion.emit(e, info); + if (info.cancelled) + return; + if (g_pUnifiedWorkspaceSwipe->isGestureInProgress()) { // Do nothing if this is using a different finger. if (e.touchID != g_pUnifiedWorkspaceSwipe->m_touchID) diff --git a/src/managers/input/trackpad/TrackpadGestures.cpp b/src/managers/input/trackpad/TrackpadGestures.cpp index d41b8ede..e054c2f9 100644 --- a/src/managers/input/trackpad/TrackpadGestures.cpp +++ b/src/managers/input/trackpad/TrackpadGestures.cpp @@ -1,6 +1,8 @@ #include "TrackpadGestures.hpp" #include "../InputManager.hpp" +#include "../../../config/ConfigValue.hpp" +#include "../../../protocols/ShortcutsInhibit.hpp" #include @@ -54,7 +56,7 @@ const char* CTrackpadGestures::stringForDir(eTrackpadGestureDirection dir) { } std::expected CTrackpadGestures::addGesture(UP&& gesture, size_t fingerCount, eTrackpadGestureDirection direction, uint32_t modMask, - float deltaScale) { + float deltaScale, bool disableInhibit) { for (const auto& g : m_gestures) { if (g->fingerCount != fingerCount) continue; @@ -84,14 +86,16 @@ std::expected CTrackpadGestures::addGesture(UP(std::move(gesture), fingerCount, modMask, direction, deltaScale)); + m_gestures.emplace_back(makeShared(std::move(gesture), fingerCount, modMask, direction, deltaScale, disableInhibit)); return {}; } -std::expected CTrackpadGestures::removeGesture(size_t fingerCount, eTrackpadGestureDirection direction, uint32_t modMask, float deltaScale) { - const auto IT = std::ranges::find_if( - m_gestures, [&](const auto& g) { return g->fingerCount == fingerCount && g->direction == direction && g->modMask == modMask && g->deltaScale == deltaScale; }); +std::expected CTrackpadGestures::removeGesture(size_t fingerCount, eTrackpadGestureDirection direction, uint32_t modMask, float deltaScale, + bool disableInhibit) { + const auto IT = std::ranges::find_if(m_gestures, [&](const auto& g) { + return g->fingerCount == fingerCount && g->direction == direction && g->modMask == modMask && g->deltaScale == deltaScale && g->disableInhibit == disableInhibit; + }); if (IT == m_gestures.end()) return std::unexpected("Can't remove a non-existent gesture"); @@ -114,6 +118,8 @@ void CTrackpadGestures::gestureBegin(const IPointer::SSwipeBeginEvent& e) { } void CTrackpadGestures::gestureUpdate(const IPointer::SSwipeUpdateEvent& e) { + static auto PDISABLEINHIBIT = CConfigValue("binds:disable_keybind_grabbing"); + if (m_gestureFindFailed) return; @@ -148,6 +154,9 @@ void CTrackpadGestures::gestureUpdate(const IPointer::SSwipeUpdateEvent& e) { if (g->modMask != MODS) continue; + if (PROTO::shortcutsInhibit->isInhibited() && !*PDISABLEINHIBIT && !g->disableInhibit) + continue; + m_activeGesture = g; g->currentDirection = g->gesture->isDirectionSensitive() ? g->direction : direction; m_activeGesture->gesture->begin({.swipe = &e, .direction = direction, .scale = g->deltaScale}); @@ -184,6 +193,8 @@ void CTrackpadGestures::gestureBegin(const IPointer::SPinchBeginEvent& e) { } void CTrackpadGestures::gestureUpdate(const IPointer::SPinchUpdateEvent& e) { + static auto PDISABLEINHIBIT = CConfigValue("binds:disable_keybind_grabbing"); + if (m_gestureFindFailed) return; @@ -211,6 +222,9 @@ void CTrackpadGestures::gestureUpdate(const IPointer::SPinchUpdateEvent& e) { if (g->modMask != MODS) continue; + if (PROTO::shortcutsInhibit->isInhibited() && !*PDISABLEINHIBIT && !g->disableInhibit) + continue; + m_activeGesture = g; g->currentDirection = g->gesture->isDirectionSensitive() ? g->direction : direction; m_activeGesture->gesture->begin({.pinch = &e, .direction = direction}); diff --git a/src/managers/input/trackpad/TrackpadGestures.hpp b/src/managers/input/trackpad/TrackpadGestures.hpp index 7f96761f..ecf11c40 100644 --- a/src/managers/input/trackpad/TrackpadGestures.hpp +++ b/src/managers/input/trackpad/TrackpadGestures.hpp @@ -11,8 +11,9 @@ class CTrackpadGestures { public: void clearGestures(); - std::expected addGesture(UP&& gesture, size_t fingerCount, eTrackpadGestureDirection direction, uint32_t modMask, float deltaScale); - std::expected removeGesture(size_t fingerCount, eTrackpadGestureDirection direction, uint32_t modMask, float deltaScale); + std::expected addGesture(UP&& gesture, size_t fingerCount, eTrackpadGestureDirection direction, uint32_t modMask, float deltaScale, + bool disableInhibit); + std::expected removeGesture(size_t fingerCount, eTrackpadGestureDirection direction, uint32_t modMask, float deltaScale, bool disableInhibit); void gestureBegin(const IPointer::SSwipeBeginEvent& e); void gestureUpdate(const IPointer::SSwipeUpdateEvent& e); @@ -32,6 +33,7 @@ class CTrackpadGestures { uint32_t modMask = 0; eTrackpadGestureDirection direction = TRACKPAD_GESTURE_DIR_NONE; // configured dir float deltaScale = 1.F; + bool disableInhibit = false; eTrackpadGestureDirection currentDirection = TRACKPAD_GESTURE_DIR_NONE; // actual dir of that select swipe }; diff --git a/src/managers/input/trackpad/gestures/CloseGesture.cpp b/src/managers/input/trackpad/gestures/CloseGesture.cpp index 7beba563..0c37ee36 100644 --- a/src/managers/input/trackpad/gestures/CloseGesture.cpp +++ b/src/managers/input/trackpad/gestures/CloseGesture.cpp @@ -1,13 +1,13 @@ #include "CloseGesture.hpp" #include "../../../../Compositor.hpp" -#include "../../../../managers/LayoutManager.hpp" #include "../../../../managers/animation/DesktopAnimationManager.hpp" #include "../../../../render/Renderer.hpp" #include "../../../../managers/eventLoop/EventLoopManager.hpp" #include "../../../../managers/eventLoop/EventLoopTimer.hpp" #include "../../../../config/ConfigValue.hpp" #include "../../../../desktop/state/FocusState.hpp" +#include "../../../../layout/target/Target.hpp" constexpr const float MAX_DISTANCE = 200.F; @@ -133,7 +133,7 @@ void CCloseTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) if (!window->m_isMapped) return; - g_pLayoutManager->getCurrentLayout()->recalculateWindow(window.lock()); + window->layoutTarget()->recalc(); window->updateDecorationValues(); window->sendWindowSize(true); *window->m_alpha = 1.F; diff --git a/src/managers/input/trackpad/gestures/CursorZoomGesture.cpp b/src/managers/input/trackpad/gestures/CursorZoomGesture.cpp new file mode 100644 index 00000000..97dfe158 --- /dev/null +++ b/src/managers/input/trackpad/gestures/CursorZoomGesture.cpp @@ -0,0 +1,33 @@ +#include "CursorZoomGesture.hpp" + +#include "../../../../Compositor.hpp" +#include "../../../../helpers/Monitor.hpp" + +CCursorZoomTrackpadGesture::CCursorZoomTrackpadGesture(const std::string& first, const std::string& second) { + try { + m_zoomValue = std::stof(first); + } catch (...) { ; } + + if (second == "mult") + m_mode = MODE_MULT; +} + +void CCursorZoomTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) { + ITrackpadGesture::begin(e); + + if (m_mode == MODE_TOGGLE) + m_zoomed = !m_zoomed; + + for (auto const& m : g_pCompositor->m_monitors) { + switch (m_mode) { + case MODE_TOGGLE: + static auto PZOOMFACTOR = CConfigValue("cursor:zoom_factor"); + *m->m_cursorZoom = m_zoomed ? m_zoomValue : *PZOOMFACTOR; + break; + case MODE_MULT: *m->m_cursorZoom = std::clamp(m->m_cursorZoom->goal() * m_zoomValue, 1.0F, 100.0F); break; + } + } +} + +void CCursorZoomTrackpadGesture::update(const ITrackpadGesture::STrackpadGestureUpdate& e) {} +void CCursorZoomTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) {} diff --git a/src/managers/input/trackpad/gestures/CursorZoomGesture.hpp b/src/managers/input/trackpad/gestures/CursorZoomGesture.hpp new file mode 100644 index 00000000..b53c81e9 --- /dev/null +++ b/src/managers/input/trackpad/gestures/CursorZoomGesture.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "ITrackpadGesture.hpp" + +class CCursorZoomTrackpadGesture : public ITrackpadGesture { + public: + CCursorZoomTrackpadGesture(const std::string& zoomLevel, const std::string& mode); + virtual ~CCursorZoomTrackpadGesture() = default; + + virtual void begin(const ITrackpadGesture::STrackpadGestureBegin& e); + virtual void update(const ITrackpadGesture::STrackpadGestureUpdate& e); + virtual void end(const ITrackpadGesture::STrackpadGestureEnd& e); + + private: + float m_zoomValue = 1.0; + inline static bool m_zoomed = false; + + enum eMode : uint8_t { + MODE_TOGGLE = 0, + MODE_MULT, + }; + + eMode m_mode = MODE_TOGGLE; +}; diff --git a/src/managers/input/trackpad/gestures/FloatGesture.cpp b/src/managers/input/trackpad/gestures/FloatGesture.cpp index b2f451f1..0849bfac 100644 --- a/src/managers/input/trackpad/gestures/FloatGesture.cpp +++ b/src/managers/input/trackpad/gestures/FloatGesture.cpp @@ -1,9 +1,10 @@ #include "FloatGesture.hpp" -#include "../../../../managers/LayoutManager.hpp" #include "../../../../render/Renderer.hpp" #include "../../../../desktop/state/FocusState.hpp" #include "../../../../desktop/view/Window.hpp" +#include "../../../../layout/LayoutManager.hpp" +#include "../../../../layout/target/WindowTarget.hpp" constexpr const float MAX_DISTANCE = 250.F; @@ -40,8 +41,7 @@ void CFloatTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& return; } - m_window->m_isFloating = !m_window->m_isFloating; - g_pLayoutManager->getCurrentLayout()->changeWindowFloatingMode(m_window.lock()); + g_layoutManager->changeFloatingMode(m_window->layoutTarget()); m_posFrom = m_window->m_realPosition->begun(); m_sizeFrom = m_window->m_realSize->begun(); @@ -79,8 +79,7 @@ void CFloatTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) if (COMPLETION < 0.2F) { // revert the animation g_pHyprRenderer->damageWindow(m_window.lock()); - m_window->m_isFloating = !m_window->m_isFloating; - g_pLayoutManager->getCurrentLayout()->changeWindowFloatingMode(m_window.lock()); + g_layoutManager->changeFloatingMode(m_window->layoutTarget()); return; } diff --git a/src/managers/input/trackpad/gestures/FullscreenGesture.cpp b/src/managers/input/trackpad/gestures/FullscreenGesture.cpp index 31592f63..a219b685 100644 --- a/src/managers/input/trackpad/gestures/FullscreenGesture.cpp +++ b/src/managers/input/trackpad/gestures/FullscreenGesture.cpp @@ -77,7 +77,6 @@ void CFullscreenTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd if (COMPLETION < 0.2F) { // revert the animation g_pHyprRenderer->damageWindow(m_window.lock()); - m_window->m_isFloating = !m_window->m_isFloating; g_pDesktopAnimationManager->overrideFullscreenFadeAmount(m_window->m_workspace, m_originalMode == FSMODE_NONE ? 1.F : 0.F, m_window.lock()); g_pCompositor->setWindowFullscreenInternal(m_window.lock(), m_window->m_fullscreenState.internal == FSMODE_NONE ? m_originalMode : FSMODE_NONE); return; diff --git a/src/managers/input/trackpad/gestures/MoveGesture.cpp b/src/managers/input/trackpad/gestures/MoveGesture.cpp index 034d88fb..0dcc310f 100644 --- a/src/managers/input/trackpad/gestures/MoveGesture.cpp +++ b/src/managers/input/trackpad/gestures/MoveGesture.cpp @@ -2,8 +2,8 @@ #include "../../../../desktop/state/FocusState.hpp" #include "../../../../desktop/view/Window.hpp" -#include "../../../../managers/LayoutManager.hpp" #include "../../../../render/Renderer.hpp" +#include "../../../../layout/LayoutManager.hpp" void CMoveTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) { ITrackpadGesture::begin(e); @@ -19,7 +19,7 @@ void CMoveTrackpadGesture::update(const ITrackpadGesture::STrackpadGestureUpdate const auto DELTA = e.swipe ? e.swipe->delta : e.pinch->delta; if (m_window->m_isFloating) { - g_pLayoutManager->getCurrentLayout()->moveActiveWindow(DELTA, m_window.lock()); + g_layoutManager->moveTarget(DELTA, m_window->layoutTarget()); m_window->m_realSize->warp(); m_window->m_realPosition->warp(); return; @@ -52,10 +52,10 @@ void CMoveTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) { if (std::abs(m_lastDelta.x) > std::abs(m_lastDelta.y)) { // horizontal - g_pLayoutManager->getCurrentLayout()->moveWindowTo(m_window.lock(), m_lastDelta.x > 0 ? "r" : "l"); + g_layoutManager->moveInDirection(m_window->layoutTarget(), m_lastDelta.x > 0 ? "r" : "l"); } else { // vertical - g_pLayoutManager->getCurrentLayout()->moveWindowTo(m_window.lock(), m_lastDelta.y > 0 ? "b" : "t"); + g_layoutManager->moveInDirection(m_window->layoutTarget(), m_lastDelta.y > 0 ? "b" : "t"); } const auto GOAL = m_window->m_realPosition->goal(); diff --git a/src/managers/input/trackpad/gestures/ResizeGesture.cpp b/src/managers/input/trackpad/gestures/ResizeGesture.cpp index 33f018cd..ffc7704b 100644 --- a/src/managers/input/trackpad/gestures/ResizeGesture.cpp +++ b/src/managers/input/trackpad/gestures/ResizeGesture.cpp @@ -2,8 +2,8 @@ #include "../../../../desktop/state/FocusState.hpp" #include "../../../../desktop/view/Window.hpp" -#include "../../../../managers/LayoutManager.hpp" #include "../../../../render/Renderer.hpp" +#include "../../../../layout/LayoutManager.hpp" void CResizeTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) { ITrackpadGesture::begin(e); @@ -17,8 +17,8 @@ void CResizeTrackpadGesture::update(const ITrackpadGesture::STrackpadGestureUpda g_pHyprRenderer->damageWindow(m_window.lock()); - g_pLayoutManager->getCurrentLayout()->resizeActiveWindow((e.swipe ? e.swipe->delta : e.pinch->delta), - cornerFromBox(m_window->getWindowMainSurfaceBox(), g_pInputManager->getMouseCoordsInternal()), m_window.lock()); + g_layoutManager->resizeTarget((e.swipe ? e.swipe->delta : e.pinch->delta), m_window->layoutTarget(), + Layout::cornerFromBox(m_window->getWindowMainSurfaceBox(), g_pInputManager->getMouseCoordsInternal())); m_window->m_realSize->warp(); m_window->m_realPosition->warp(); diff --git a/src/managers/permissions/DynamicPermissionManager.cpp b/src/managers/permissions/DynamicPermissionManager.cpp index 0e92ed8e..d63a72a0 100644 --- a/src/managers/permissions/DynamicPermissionManager.cpp +++ b/src/managers/permissions/DynamicPermissionManager.cpp @@ -53,6 +53,7 @@ static const char* permissionToString(eDynamicPermissionType type) { case PERMISSION_TYPE_SCREENCOPY: return "PERMISSION_TYPE_SCREENCOPY"; case PERMISSION_TYPE_PLUGIN: return "PERMISSION_TYPE_PLUGIN"; case PERMISSION_TYPE_KEYBOARD: return "PERMISSION_TYPE_KEYBOARD"; + case PERMISSION_TYPE_CURSOR_POS: return "PERMISSION_TYPE_CURSOR_POS"; } return "error"; @@ -251,7 +252,8 @@ void CDynamicPermissionManager::askForPermission(wl_client* client, const std::s std::string description = ""; switch (rule->m_type) { case PERMISSION_TYPE_SCREENCOPY: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, {{"app", appName}}); break; - case PERMISSION_TYPE_PLUGIN: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, {{"app", appName}, {"plugin", binaryPath}}); break; + case PERMISSION_TYPE_CURSOR_POS: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_CURSOR_POS, {{"app", appName}}); break; + case PERMISSION_TYPE_PLUGIN: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_PLUGIN, {{"app", appName}, {"plugin", binaryPath}}); break; case PERMISSION_TYPE_KEYBOARD: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_KEYBOARD, {{"keyboard", binaryPath}}); break; case PERMISSION_TYPE_UNKNOWN: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_UNKNOWN, {{"app", appName}}); break; } diff --git a/src/managers/permissions/DynamicPermissionManager.hpp b/src/managers/permissions/DynamicPermissionManager.hpp index 4de1eb32..423596c3 100644 --- a/src/managers/permissions/DynamicPermissionManager.hpp +++ b/src/managers/permissions/DynamicPermissionManager.hpp @@ -18,6 +18,7 @@ enum eDynamicPermissionType : uint8_t { PERMISSION_TYPE_SCREENCOPY, PERMISSION_TYPE_PLUGIN, PERMISSION_TYPE_KEYBOARD, + PERMISSION_TYPE_CURSOR_POS, }; enum eDynamicPermissionRuleSource : uint8_t { @@ -104,4 +105,4 @@ class CDynamicPermissionManager { std::vector> m_rules; }; -inline UP g_pDynamicPermissionManager; \ No newline at end of file +inline UP g_pDynamicPermissionManager; diff --git a/src/managers/screenshare/CursorshareSession.cpp b/src/managers/screenshare/CursorshareSession.cpp new file mode 100644 index 00000000..703832ab --- /dev/null +++ b/src/managers/screenshare/CursorshareSession.cpp @@ -0,0 +1,240 @@ +#include "ScreenshareManager.hpp" +#include "../PointerManager.hpp" +#include "../../protocols/core/Seat.hpp" +#include "../permissions/DynamicPermissionManager.hpp" +#include "../../render/Renderer.hpp" + +using namespace Screenshare; + +CCursorshareSession::CCursorshareSession(wl_client* client, WP pointer) : m_client(client), m_pointer(pointer) { + m_listeners.pointerDestroyed = m_pointer->m_events.destroyed.listen([this] { stop(); }); + m_listeners.cursorChanged = g_pPointerManager->m_events.cursorChanged.listen([this] { + calculateConstraints(); + m_events.constraintsChanged.emit(); + + if (m_pendingFrame.pending) { + if (copy()) + return; + + LOGM(Log::ERR, "Failed to copy cursor image for cursor share"); + if (m_pendingFrame.callback) + m_pendingFrame.callback(RESULT_NOT_COPIED); + m_pendingFrame.pending = false; + return; + } + }); + + calculateConstraints(); +} + +CCursorshareSession::~CCursorshareSession() { + stop(); +} + +void CCursorshareSession::stop() { + if (m_stopped) + return; + m_stopped = true; + m_events.stopped.emit(); +} + +void CCursorshareSession::calculateConstraints() { + const auto& cursorImage = g_pPointerManager->currentCursorImage(); + m_constraintsChanged = true; + + // cursor is hidden, keep the previous constraints and render 0 alpha + if (!cursorImage.pBuffer) + return; + + // TODO: should cursor share have a format bit flip for RGBA? + if (auto attrs = cursorImage.pBuffer->shm(); attrs.success) { + m_format = attrs.format; + } else { + // we only have shm cursors + return; + } + + m_hotspot = cursorImage.hotspot; + m_bufferSize = cursorImage.size; +} + +// TODO: allow render to buffer without monitor and remove monitor param +eScreenshareError CCursorshareSession::share(PHLMONITOR monitor, SP buffer, FSourceBoxCallback sourceBoxCallback, FScreenshareCallback callback) { + if (m_stopped || m_pointer.expired() || m_bufferSize == Vector2D(0, 0)) + return ERROR_STOPPED; + + if UNLIKELY (!buffer || !buffer->m_resource || !buffer->m_resource->good()) { + LOGM(Log::ERR, "Client requested sharing to an invalid buffer"); + return ERROR_NO_BUFFER; + } + + if UNLIKELY (buffer->size != m_bufferSize) { + LOGM(Log::ERR, "Client requested sharing to an invalid buffer size"); + return ERROR_BUFFER_SIZE; + } + + uint32_t bufFormat; + if (buffer->dmabuf().success) + bufFormat = buffer->dmabuf().format; + else if (buffer->shm().success) + bufFormat = buffer->shm().format; + else { + LOGM(Log::ERR, "Client requested sharing to an invalid buffer"); + return ERROR_NO_BUFFER; + } + + if (bufFormat != m_format) { + LOGM(Log::ERR, "Invalid format {} in {:x}", bufFormat, (uintptr_t)this); + return ERROR_BUFFER_FORMAT; + } + + m_pendingFrame.pending = true; + m_pendingFrame.monitor = monitor; + m_pendingFrame.buffer = buffer; + m_pendingFrame.sourceBoxCallback = sourceBoxCallback; + m_pendingFrame.callback = callback; + + // nothing changed, then delay copy until contraints changed + if (!m_constraintsChanged) + return ERROR_NONE; + + if (!copy()) { + LOGM(Log::ERR, "Failed to copy cursor image for cursor share"); + callback(RESULT_NOT_COPIED); + m_pendingFrame.pending = false; + return ERROR_UNKNOWN; + } + + return ERROR_NONE; +} + +void CCursorshareSession::render() { + const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_client, PERMISSION_TYPE_CURSOR_POS); + + const auto& cursorImage = g_pPointerManager->currentCursorImage(); + + // TODO: implement a monitor independent render mode to buffer that does this in CHyprRenderer::begin() or something like that + g_pHyprOpenGL->m_renderData.transformDamage = false; + g_pHyprOpenGL->setViewport(0, 0, m_bufferSize.x, m_bufferSize.y); + + bool overlaps = g_pPointerManager->getCursorBoxGlobal().overlaps(m_pendingFrame.sourceBoxCallback()); + if (PERM != PERMISSION_RULE_ALLOW_MODE_ALLOW || !overlaps) { + // render black when not allowed + g_pHyprOpenGL->clear(Colors::BLACK); + } else if (!cursorImage.pBuffer || !cursorImage.surface || !cursorImage.bufferTex) { + // render clear when cursor is probably hidden + g_pHyprOpenGL->clear(CHyprColor(0, 0, 0, 0)); + } else { + // render cursor + CBox texbox = {{}, cursorImage.bufferTex->m_size}; + g_pHyprOpenGL->renderTexture(cursorImage.bufferTex, texbox, {}); + } + + g_pHyprOpenGL->m_renderData.blockScreenShader = true; +} + +bool CCursorshareSession::copy() { + if (!m_pendingFrame.callback || !m_pendingFrame.monitor || !m_pendingFrame.callback || !m_pendingFrame.sourceBoxCallback) + return false; + + // FIXME: this doesn't really make sense but just to be safe + m_pendingFrame.callback(RESULT_TIMESTAMP); + + g_pHyprRenderer->makeEGLCurrent(); + + CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX}; + if (auto attrs = m_pendingFrame.buffer->dmabuf(); attrs.success) { + if (attrs.format != m_format) { + LOGM(Log::ERR, "Can't copy: invalid format"); + return false; + } + + if (!g_pHyprRenderer->beginRender(m_pendingFrame.monitor, fakeDamage, RENDER_MODE_TO_BUFFER, m_pendingFrame.buffer, nullptr, true)) { + LOGM(Log::ERR, "Can't copy: failed to begin rendering to dmabuf"); + return false; + } + + render(); + + g_pHyprRenderer->endRender([callback = m_pendingFrame.callback]() { + if (callback) + callback(RESULT_COPIED); + }); + } else if (auto attrs = m_pendingFrame.buffer->shm(); attrs.success) { + auto [bufData, fmt, bufLen] = m_pendingFrame.buffer->beginDataPtr(0); + const auto PFORMAT = NFormatUtils::getPixelFormatFromDRM(m_format); + + if (attrs.format != m_format || !PFORMAT) { + LOGM(Log::ERR, "Can't copy: invalid format"); + return false; + } + + auto outFB = g_pHyprRenderer->createFB(); + outFB->alloc(m_bufferSize.x, m_bufferSize.y, m_format); + + if (!g_pHyprRenderer->beginRender(m_pendingFrame.monitor, fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, outFB, true)) { + LOGM(Log::ERR, "Can't copy: failed to begin rendering to shm"); + return false; + } + + render(); + + g_pHyprRenderer->endRender(); + + g_pHyprOpenGL->m_renderData.pMonitor = m_pendingFrame.monitor; + outFB->bind(); + glBindFramebuffer(GL_READ_FRAMEBUFFER, GLFB(outFB)->getFBID()); + + glPixelStorei(GL_PACK_ALIGNMENT, 1); + + int glFormat = PFORMAT->glFormat; + + if (glFormat == GL_RGBA) + glFormat = GL_BGRA_EXT; + + if (glFormat != GL_BGRA_EXT && glFormat != GL_RGB) { + if (PFORMAT->swizzle.has_value()) { + std::array RGBA = SWIZZLE_RGBA; + std::array BGRA = SWIZZLE_BGRA; + if (PFORMAT->swizzle == RGBA) + glFormat = GL_RGBA; + else if (PFORMAT->swizzle == BGRA) + glFormat = GL_BGRA_EXT; + else { + LOGM(Log::ERR, "Copied frame via shm might be broken or color flipped"); + glFormat = GL_RGBA; + } + } + } + + glReadPixels(0, 0, m_bufferSize.x, m_bufferSize.y, glFormat, PFORMAT->glType, bufData); + + g_pHyprOpenGL->m_renderData.pMonitor.reset(); + + m_pendingFrame.buffer->endDataPtr(); + GLFB(outFB)->unbind(); + glPixelStorei(GL_PACK_ALIGNMENT, 4); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + + m_pendingFrame.callback(RESULT_COPIED); + } else { + LOGM(Log::ERR, "Can't copy: invalid buffer type"); + return false; + } + + m_pendingFrame.pending = false; + m_constraintsChanged = false; + return true; +} + +DRMFormat CCursorshareSession::format() const { + return m_format; +} + +Vector2D CCursorshareSession::bufferSize() const { + return m_bufferSize; +} + +Vector2D CCursorshareSession::hotspot() const { + return m_hotspot; +} diff --git a/src/managers/screenshare/ScreenshareFrame.cpp b/src/managers/screenshare/ScreenshareFrame.cpp new file mode 100644 index 00000000..d747ecee --- /dev/null +++ b/src/managers/screenshare/ScreenshareFrame.cpp @@ -0,0 +1,497 @@ +#include "ScreenshareManager.hpp" +#include "../PointerManager.hpp" +#include "../input/InputManager.hpp" +#include "../permissions/DynamicPermissionManager.hpp" +#include "../../protocols/ColorManagement.hpp" +#include "../../protocols/XDGShell.hpp" +#include "../../Compositor.hpp" +#include "../../render/Renderer.hpp" +#include "../../render/OpenGL.hpp" +#include "../../helpers/Monitor.hpp" +#include "../../desktop/view/Window.hpp" +#include "../../desktop/state/FocusState.hpp" +#include + +using namespace Screenshare; + +CScreenshareFrame::CScreenshareFrame(WP session, bool overlayCursor, bool isFirst) : + m_session(session), m_bufferSize(m_session->bufferSize()), m_overlayCursor(overlayCursor), m_isFirst(isFirst) { + ; +} + +CScreenshareFrame::~CScreenshareFrame() { + if (m_failed || !m_shared) + return; + + if (!m_copied && m_callback) + m_callback(RESULT_NOT_COPIED); +} + +bool CScreenshareFrame::done() const { + if (m_session.expired() || m_session->m_stopped) + return true; + + if (m_session->m_type == SHARE_NONE || m_bufferSize == Vector2D(0, 0)) + return true; + + if (m_failed || m_copied) + return true; + + if (m_session->m_type == SHARE_MONITOR && !m_session->monitor()) + return true; + + if (m_session->m_type == SHARE_REGION && !m_session->monitor()) + return true; + + if (m_session->m_type == SHARE_WINDOW && (!m_session->monitor() || !validMapped(m_session->m_window))) + return true; + + if (!m_shared) + return false; + + if (!m_buffer || !m_buffer->m_resource || !m_buffer->m_resource->good()) + return true; + + if (!m_callback) + return true; + + return false; +} + +eScreenshareError CScreenshareFrame::share(SP buffer, const CRegion& clientDamage, FScreenshareCallback callback) { + if UNLIKELY (done()) + return ERROR_STOPPED; + + if UNLIKELY (!m_session->monitor() || !g_pCompositor->monitorExists(m_session->monitor())) { + LOGM(Log::ERR, "Client requested sharing of a monitor that is gone"); + m_failed = true; + return ERROR_STOPPED; + } + + if UNLIKELY (m_session->m_type == SHARE_WINDOW && !validMapped(m_session->m_window)) { + LOGM(Log::ERR, "Client requested sharing of window that is gone or not shareable!"); + m_failed = true; + return ERROR_STOPPED; + } + + if UNLIKELY (!buffer || !buffer->m_resource || !buffer->m_resource->good()) { + LOGM(Log::ERR, "Client requested sharing to an invalid buffer"); + return ERROR_NO_BUFFER; + } + + if UNLIKELY (buffer->size != m_bufferSize) { + LOGM(Log::ERR, "Client requested sharing to an invalid buffer size"); + return ERROR_BUFFER_SIZE; + } + + uint32_t bufFormat; + if (buffer->dmabuf().success) + bufFormat = buffer->dmabuf().format; + else if (buffer->shm().success) + bufFormat = buffer->shm().format; + else { + LOGM(Log::ERR, "Client requested sharing to an invalid buffer"); + return ERROR_NO_BUFFER; + } + + if (std::ranges::count_if(m_session->allowedFormats(), [&](const DRMFormat& format) { return format == bufFormat; }) == 0) { + LOGM(Log::ERR, "Invalid format {} in {:x}", bufFormat, (uintptr_t)this); + return ERROR_BUFFER_FORMAT; + } + + m_buffer = buffer; + m_callback = callback; + m_shared = true; + + // schedule a frame so that when a screenshare starts it isn't black until the output is updated + if (m_isFirst) { + g_pCompositor->scheduleFrameForMonitor(m_session->monitor(), Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME); + g_pHyprRenderer->damageMonitor(m_session->monitor()); + } + + // TODO: add a damage ring for output damage since last shared frame + CRegion frameDamage = CRegion(0, 0, m_bufferSize.x, m_bufferSize.y); + + // copy everything on the first frame + if (m_isFirst) + m_damage = CRegion(0, 0, m_bufferSize.x, m_bufferSize.y); + else + m_damage = frameDamage.add(clientDamage); + + m_damage.intersect(0, 0, m_bufferSize.x, m_bufferSize.y); + + return ERROR_NONE; +} + +void CScreenshareFrame::copy() { + if (done()) + return; + + // tell client to send presented timestamp + // TODO: is this right? this is right after we commit to aq, not when page flip happens.. + m_callback(RESULT_TIMESTAMP); + + // store a snapshot before the permission popup so we don't break screenshots + const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_session->m_client, PERMISSION_TYPE_SCREENCOPY); + if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING) { + if (!m_session->m_tempFB || !m_session->m_tempFB->isAllocated()) + storeTempFB(); + + // don't copy a frame while allow is pending because screenshot tools will only take the first frame we give, which is empty + return; + } + + if (m_buffer->shm().success) + m_failed = !copyShm(); + else if (m_buffer->dmabuf().success) + m_failed = !copyDmabuf(); + + if (!m_failed) { + // screensharing has started again + m_session->screenshareEvents(true); + m_session->m_shareStopTimer->updateTimeout(std::chrono::milliseconds(500)); // check in half second + } else + m_callback(RESULT_NOT_COPIED); +} + +void CScreenshareFrame::renderMonitor() { + if ((m_session->m_type != SHARE_MONITOR && m_session->m_type != SHARE_REGION) || done()) + return; + + const auto PMONITOR = m_session->monitor(); + + auto TEXTURE = g_pHyprRenderer->createTexture(PMONITOR->m_output->state->state().buffer); + + const bool IS_CM_AWARE = PROTO::colorManagement && PROTO::colorManagement->isClientCMAware(m_session->m_client); + g_pHyprOpenGL->m_renderData.transformDamage = false; + g_pHyprOpenGL->m_renderData.noSimplify = true; + + // render monitor texture + CBox monbox = CBox{{}, PMONITOR->m_pixelSize} + .transform(Math::wlTransformToHyprutils(Math::invertTransform(PMONITOR->m_transform)), PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y) + .translate(-m_session->m_captureBox.pos()); // vvvv kinda ass-backwards but that's how I designed the renderer... sigh. + g_pHyprOpenGL->pushMonitorTransformEnabled(true); + g_pHyprOpenGL->setRenderModifEnabled(false); + g_pHyprOpenGL->renderTexture(TEXTURE, monbox, + { + .cmBackToSRGB = !IS_CM_AWARE, + .cmBackToSRGBSource = !IS_CM_AWARE ? PMONITOR : nullptr, + }); + g_pHyprOpenGL->setRenderModifEnabled(true); + g_pHyprOpenGL->popMonitorTransformEnabled(); + + // render black boxes for noscreenshare + auto hidePopups = [&](Vector2D popupBaseOffset) { + return [&, popupBaseOffset](WP popup, void*) { + if (!popup->wlSurface() || !popup->wlSurface()->resource() || !popup->visible()) + return; + + const auto popRel = popup->coordsRelativeToParent(); + popup->wlSurface()->resource()->breadthfirst( + [&](SP surf, const Vector2D& localOff, void*) { + const auto size = surf->m_current.size; + const auto surfBox = + CBox{popupBaseOffset + popRel + localOff, size}.translate(PMONITOR->m_position).scale(PMONITOR->m_scale).translate(-m_session->m_captureBox.pos()); + + if LIKELY (surfBox.w > 0 && surfBox.h > 0) + g_pHyprOpenGL->renderRect(surfBox, Colors::BLACK, {}); + }, + nullptr); + }; + }; + + for (auto const& l : g_pCompositor->m_layers) { + if (!l->m_ruleApplicator->noScreenShare().valueOrDefault()) + continue; + + if UNLIKELY (!l->visible()) + continue; + + const auto REALPOS = l->m_realPosition->value(); + const auto REALSIZE = l->m_realSize->value(); + + const auto noScreenShareBox = CBox{REALPOS.x, REALPOS.y, std::max(REALSIZE.x, 5.0), std::max(REALSIZE.y, 5.0)} + .translate(-PMONITOR->m_position) + .scale(PMONITOR->m_scale) + .translate(-m_session->m_captureBox.pos()); + + g_pHyprOpenGL->renderRect(noScreenShareBox, Colors::BLACK, {}); + + const auto geom = l->m_geometry; + const Vector2D popupBaseOffset = REALPOS - Vector2D{geom.pos().x, geom.pos().y}; + if (l->m_popupHead) + l->m_popupHead->breadthfirst(hidePopups(popupBaseOffset), nullptr); + } + + for (auto const& w : g_pCompositor->m_windows) { + if (!w->m_ruleApplicator->noScreenShare().valueOrDefault()) + continue; + + if (!g_pHyprRenderer->shouldRenderWindow(w, PMONITOR)) + continue; + + if (w->isHidden()) + continue; + + const auto PWORKSPACE = w->m_workspace; + + if UNLIKELY (!PWORKSPACE && !w->m_fadingOut && w->m_alpha->value() != 0.f) + continue; + + const auto renderOffset = PWORKSPACE && !w->m_pinned ? PWORKSPACE->m_renderOffset->value() : Vector2D{}; + const auto REALPOS = w->m_realPosition->value() + renderOffset; + const auto noScreenShareBox = CBox{REALPOS.x, REALPOS.y, std::max(w->m_realSize->value().x, 5.0), std::max(w->m_realSize->value().y, 5.0)} + .translate(-PMONITOR->m_position) + .scale(PMONITOR->m_scale) + .translate(-m_session->m_captureBox.pos()); + + // seems like rounding doesn't play well with how we manipulate the box position to render regions causing the window to leak through + const auto dontRound = m_session->m_captureBox.pos() != Vector2D() || w->isEffectiveInternalFSMode(FSMODE_FULLSCREEN); + const auto rounding = dontRound ? 0 : w->rounding() * PMONITOR->m_scale; + const auto roundingPower = dontRound ? 2.0f : w->roundingPower(); + + g_pHyprOpenGL->renderRect(noScreenShareBox, Colors::BLACK, {.round = rounding, .roundingPower = roundingPower}); + + if (w->m_isX11 || !w->m_popupHead) + continue; + + const auto geom = w->m_xdgSurface->m_current.geometry; + const Vector2D popupBaseOffset = REALPOS - Vector2D{geom.pos().x, geom.pos().y}; + + w->m_popupHead->breadthfirst(hidePopups(popupBaseOffset), nullptr); + } + + if (m_overlayCursor) { + CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX}; + Vector2D cursorPos = g_pInputManager->getMouseCoordsInternal() - PMONITOR->m_position - m_session->m_captureBox.pos() / PMONITOR->m_scale; + g_pPointerManager->renderSoftwareCursorsFor(PMONITOR, Time::steadyNow(), fakeDamage, cursorPos, true); + } +} + +void CScreenshareFrame::renderWindow() { + if (m_session->m_type != SHARE_WINDOW || done()) + return; + + const auto PWINDOW = m_session->m_window.lock(); + const auto PMONITOR = m_session->monitor(); + + const auto NOW = Time::steadyNow(); + + // TODO: implement a monitor independent render mode to buffer that does this in CHyprRenderer::begin() or something like that + g_pHyprOpenGL->m_renderData.monitorProjection = Mat3x3::identity(); + g_pHyprOpenGL->m_renderData.projection = Mat3x3::outputProjection(m_bufferSize, HYPRUTILS_TRANSFORM_NORMAL); + g_pHyprOpenGL->m_renderData.transformDamage = false; + g_pHyprOpenGL->setViewport(0, 0, m_bufferSize.x, m_bufferSize.y); + + g_pHyprRenderer->m_bBlockSurfaceFeedback = g_pHyprRenderer->shouldRenderWindow(PWINDOW); // block the feedback to avoid spamming the surface if it's visible + g_pHyprRenderer->renderWindow(PWINDOW, PMONITOR, NOW, false, RENDER_PASS_ALL, true, true); + g_pHyprRenderer->m_bBlockSurfaceFeedback = false; + + if (!m_overlayCursor) + return; + + auto pointerSurfaceResource = g_pSeatManager->m_state.pointerFocus.lock(); + + if (!pointerSurfaceResource) + return; + + auto pointerSurface = Desktop::View::CWLSurface::fromResource(pointerSurfaceResource); + if (!pointerSurface) + return; + + auto box = pointerSurface->getSurfaceBoxGlobal(); + if (!box.has_value() || box->intersection(m_session->m_window->getFullWindowBoundingBox()).empty()) + return; + + if (Desktop::focusState()->window() != m_session->m_window) + return; + + CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX}; + g_pPointerManager->renderSoftwareCursorsFor(PMONITOR->m_self.lock(), NOW, fakeDamage, g_pInputManager->getMouseCoordsInternal() - PWINDOW->m_realPosition->value(), true); +} + +void CScreenshareFrame::render() { + const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_session->m_client, PERMISSION_TYPE_SCREENCOPY); + + if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING) { + g_pHyprOpenGL->clear(CHyprColor(0, 0, 0, 0)); + return; + } + + bool windowShareDenied = m_session->m_type == SHARE_WINDOW && m_session->m_window->m_ruleApplicator && m_session->m_window->m_ruleApplicator->noScreenShare().valueOrDefault(); + if (PERM == PERMISSION_RULE_ALLOW_MODE_DENY || windowShareDenied) { + g_pHyprOpenGL->clear(CHyprColor(0, 0, 0, 0)); + CBox texbox = CBox{m_bufferSize / 2.F, g_pHyprOpenGL->m_screencopyDeniedTexture->m_size}.translate(-g_pHyprOpenGL->m_screencopyDeniedTexture->m_size / 2.F); + g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_screencopyDeniedTexture, texbox, {}); + return; + } + + if (m_session->m_tempFB && m_session->m_tempFB->isAllocated()) { + CBox texbox = {{}, m_bufferSize}; + g_pHyprOpenGL->renderTexture(m_session->m_tempFB->getTexture(), texbox, {}); + m_session->m_tempFB->release(); + return; + } + + switch (m_session->m_type) { + case SHARE_REGION: // TODO: could this be better? this is how screencopy works + case SHARE_MONITOR: renderMonitor(); break; + case SHARE_WINDOW: renderWindow(); break; + case SHARE_NONE: + default: return; + } +} + +bool CScreenshareFrame::copyDmabuf() { + if (done()) + return false; + + if (!g_pHyprRenderer->beginRender(m_session->monitor(), m_damage, RENDER_MODE_TO_BUFFER, m_buffer, nullptr, true)) { + LOGM(Log::ERR, "Can't copy: failed to begin rendering to dma frame"); + return false; + } + + render(); + + g_pHyprOpenGL->m_renderData.blockScreenShader = true; + + g_pHyprRenderer->endRender([self = m_self]() { + if (!self || self.expired() || self->m_copied) + return; + + LOGM(Log::TRACE, "Copied frame via dma"); + self->m_callback(RESULT_COPIED); + self->m_copied = true; + }); + + return true; +} + +bool CScreenshareFrame::copyShm() { + if (done()) + return false; + + g_pHyprRenderer->makeEGLCurrent(); + + auto shm = m_buffer->shm(); + auto [pixelData, fmt, bufLen] = m_buffer->beginDataPtr(0); // no need for end, cuz it's shm + + const auto PFORMAT = NFormatUtils::getPixelFormatFromDRM(shm.format); + if (!PFORMAT) { + LOGM(Log::ERR, "Can't copy: failed to find a pixel format"); + return false; + } + + const auto PMONITOR = m_session->monitor(); + + auto outFB = g_pHyprRenderer->createFB(); + outFB->alloc(m_bufferSize.x, m_bufferSize.y, shm.format); + + if (!g_pHyprRenderer->beginRender(PMONITOR, m_damage, RENDER_MODE_FULL_FAKE, nullptr, outFB, true)) { + LOGM(Log::ERR, "Can't copy: failed to begin rendering"); + return false; + } + + render(); + + g_pHyprOpenGL->m_renderData.blockScreenShader = true; + + g_pHyprRenderer->endRender(); + + g_pHyprOpenGL->m_renderData.pMonitor = PMONITOR; + outFB->bind(); + glBindFramebuffer(GL_READ_FRAMEBUFFER, GLFB(outFB)->getFBID()); + + glPixelStorei(GL_PACK_ALIGNMENT, 1); + + uint32_t packStride = NFormatUtils::minStride(PFORMAT, m_bufferSize.x); + int glFormat = PFORMAT->glFormat; + + if (glFormat == GL_RGBA) + glFormat = GL_BGRA_EXT; + + if (glFormat != GL_BGRA_EXT && glFormat != GL_RGB) { + if (PFORMAT->swizzle.has_value()) { + std::array RGBA = SWIZZLE_RGBA; + std::array BGRA = SWIZZLE_BGRA; + if (PFORMAT->swizzle == RGBA) + glFormat = GL_RGBA; + else if (PFORMAT->swizzle == BGRA) + glFormat = GL_BGRA_EXT; + else { + LOGM(Log::ERR, "Copied frame via shm might be broken or color flipped"); + glFormat = GL_RGBA; + } + } + } + + // TODO: use pixel buffer object to not block cpu + if (packStride == sc(shm.stride)) { + m_damage.forEachRect([&](const auto& rect) { + int width = rect.x2 - rect.x1; + int height = rect.y2 - rect.y1; + glReadPixels(rect.x1, rect.y1, width, height, glFormat, PFORMAT->glType, pixelData); + }); + } else { + m_damage.forEachRect([&](const auto& rect) { + size_t width = rect.x2 - rect.x1; + size_t height = rect.y2 - rect.y1; + for (size_t i = rect.y1; i < height; ++i) { + glReadPixels(rect.x1, i, width, 1, glFormat, PFORMAT->glType, pixelData + (rect.x1 * PFORMAT->bytesPerBlock) + (i * shm.stride)); + } + }); + } + + GLFB(outFB)->unbind(); + glPixelStorei(GL_PACK_ALIGNMENT, 4); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + + g_pHyprOpenGL->m_renderData.pMonitor.reset(); + + if (!m_copied) { + LOGM(Log::TRACE, "Copied frame via shm"); + m_callback(RESULT_COPIED); + } + + return true; +} + +void CScreenshareFrame::storeTempFB() { + if (!m_session->m_tempFB) + m_session->m_tempFB = g_pHyprRenderer->createFB(); + m_session->m_tempFB->alloc(m_bufferSize.x, m_bufferSize.y); + + CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX}; + + if (!g_pHyprRenderer->beginRender(m_session->monitor(), fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, m_session->m_tempFB, true)) { + LOGM(Log::ERR, "Can't copy: failed to begin rendering to temp fb"); + return; + } + + switch (m_session->m_type) { + case SHARE_REGION: // TODO: could this be better? this is how screencopy works + case SHARE_MONITOR: renderMonitor(); break; + case SHARE_WINDOW: renderWindow(); break; + case SHARE_NONE: + default: return; + } + + g_pHyprRenderer->endRender(); +} + +Vector2D CScreenshareFrame::bufferSize() const { + return m_bufferSize; +} + +wl_output_transform CScreenshareFrame::transform() const { + switch (m_session->m_type) { + case SHARE_REGION: + case SHARE_MONITOR: return m_session->monitor()->m_transform; + default: + case SHARE_WINDOW: return WL_OUTPUT_TRANSFORM_NORMAL; + } +} + +const CRegion& CScreenshareFrame::damage() const { + return m_damage; +} diff --git a/src/managers/screenshare/ScreenshareManager.cpp b/src/managers/screenshare/ScreenshareManager.cpp new file mode 100644 index 00000000..63f2bbbc --- /dev/null +++ b/src/managers/screenshare/ScreenshareManager.cpp @@ -0,0 +1,165 @@ +#include "ScreenshareManager.hpp" +#include "../../render/Renderer.hpp" +#include "../../Compositor.hpp" +#include "../../desktop/view/Window.hpp" +#include "../../protocols/core/Seat.hpp" + +using namespace Screenshare; + +CScreenshareManager::CScreenshareManager() { + ; +} + +void CScreenshareManager::onOutputCommit(PHLMONITOR monitor) { + std::erase_if(m_sessions, [&](const WP& session) { return session.expired(); }); + + // if no pending frames, and no sessions are sharing, then unblock ds + if (m_pendingFrames.empty()) { + for (const auto& session : m_sessions) { + if (!session->m_stopped && session->m_sharing) + return; + } + + g_pHyprRenderer->m_directScanoutBlocked = false; + return; // nothing to share + } + + std::ranges::for_each(m_pendingFrames, [&](WP& frame) { + if (frame.expired() || !frame->m_shared || frame->done()) + return; + + if (frame->m_session->monitor() != monitor) + return; + + if (frame->m_session->m_type == SHARE_WINDOW) { + CBox geometry = {frame->m_session->m_window->m_realPosition->value(), frame->m_session->m_window->m_realSize->value()}; + if (geometry.intersection({monitor->m_position, monitor->m_size}).empty()) + return; + } + + frame->copy(); + }); + + std::erase_if(m_pendingFrames, [&](const WP& frame) { return frame.expired(); }); +} + +UP CScreenshareManager::newSession(wl_client* client, PHLMONITOR monitor) { + if UNLIKELY (!monitor || !g_pCompositor->monitorExists(monitor)) { + LOGM(Log::ERR, "Client requested sharing of a monitor that is gone"); + return nullptr; + } + + UP session = UP(new CScreenshareSession(monitor, client)); + + session->m_self = session; + m_sessions.emplace_back(session); + + return session; +} + +UP CScreenshareManager::newSession(wl_client* client, PHLMONITOR monitor, CBox captureRegion) { + if UNLIKELY (!monitor || !g_pCompositor->monitorExists(monitor)) { + LOGM(Log::ERR, "Client requested sharing of a monitor that is gone"); + return nullptr; + } + + UP session = UP(new CScreenshareSession(monitor, captureRegion, client)); + + session->m_self = session; + m_sessions.emplace_back(session); + + return session; +} + +UP CScreenshareManager::newSession(wl_client* client, PHLWINDOW window) { + if UNLIKELY (!window || !window->m_isMapped) { + LOGM(Log::ERR, "Client requested sharing of window that is gone or not shareable!"); + return nullptr; + } + + UP session = UP(new CScreenshareSession(window, client)); + + session->m_self = session; + m_sessions.emplace_back(session); + + return session; +} + +UP CScreenshareManager::newCursorSession(wl_client* client, WP pointer) { + UP session = UP(new CCursorshareSession(client, pointer)); + + session->m_self = session; + m_cursorSessions.emplace_back(session); + + return session; +} + +WP CScreenshareManager::getManagedSession(wl_client* client, PHLMONITOR monitor) { + return getManagedSession(SHARE_MONITOR, client, monitor, nullptr, {}); +} + +WP CScreenshareManager::getManagedSession(wl_client* client, PHLMONITOR monitor, CBox captureBox) { + + return getManagedSession(SHARE_REGION, client, monitor, nullptr, captureBox); +} + +WP CScreenshareManager::getManagedSession(wl_client* client, PHLWINDOW window) { + return getManagedSession(SHARE_WINDOW, client, nullptr, window, {}); +} + +WP CScreenshareManager::getManagedSession(eScreenshareType type, wl_client* client, PHLMONITOR monitor, PHLWINDOW window, CBox captureBox) { + if (type == SHARE_NONE) + return {}; + + auto it = std::ranges::find_if(m_managedSessions, [&](const auto& session) { + if (session->m_session->m_client != client || session->m_session->m_type != type) + return false; + + switch (type) { + case SHARE_MONITOR: return session->m_session->m_monitor == monitor; + case SHARE_WINDOW: return session->m_session->m_window == window; + case SHARE_REGION: return session->m_session->m_monitor == monitor && session->m_session->m_captureBox == captureBox; + case SHARE_NONE: + default: return false; + } + + return false; + }); + + if (it == m_managedSessions.end()) { + UP session; + switch (type) { + case SHARE_MONITOR: session = UP(new CScreenshareSession(monitor, client)); break; + case SHARE_WINDOW: session = UP(new CScreenshareSession(window, client)); break; + case SHARE_REGION: session = UP(new CScreenshareSession(monitor, captureBox, client)); break; + case SHARE_NONE: + default: return {}; + } + + session->m_self = session; + m_sessions.emplace_back(session); + + it = m_managedSessions.emplace(m_managedSessions.end(), makeUnique(std::move(session))); + } + + auto& session = *it; + + session->stoppedListener = session->m_session->m_events.stopped.listen([session = WP(session)]() { + if (!session.expired()) + std::erase_if(Screenshare::mgr()->m_managedSessions, [&](const auto& s) { return s && s->m_session.get() == session->m_session.get(); }); + }); + + return session->m_session; +} + +bool CScreenshareManager::isOutputBeingSSd(PHLMONITOR monitor) { + return std::ranges::any_of(m_sessions, [monitor](const auto& s) { + if (!s) + return false; + return (s->m_type == SHARE_MONITOR || s->m_type == SHARE_REGION) && s->m_monitor == monitor; + }); +} + +CScreenshareManager::SManagedSession::SManagedSession(UP&& session) : m_session(std::move(session)) { + ; +} diff --git a/src/managers/screenshare/ScreenshareManager.hpp b/src/managers/screenshare/ScreenshareManager.hpp new file mode 100644 index 00000000..5a4ada5e --- /dev/null +++ b/src/managers/screenshare/ScreenshareManager.hpp @@ -0,0 +1,251 @@ +#pragma once + +#include +#include "../../helpers/memory/Memory.hpp" +#include "../../protocols/types/Buffer.hpp" +#include "../../render/Framebuffer.hpp" +#include "../eventLoop/EventLoopTimer.hpp" +#include "../../render/Renderer.hpp" + +// TODO: do screenshare damage + +class CWLPointerResource; + +namespace Screenshare { + enum eScreenshareType : uint8_t { + SHARE_MONITOR, + SHARE_WINDOW, + SHARE_REGION, + SHARE_NONE + }; + + enum eScreenshareError : uint8_t { + ERROR_NONE, + ERROR_UNKNOWN, + ERROR_STOPPED, + ERROR_NO_BUFFER, + ERROR_BUFFER_SIZE, + ERROR_BUFFER_FORMAT + }; + + enum eScreenshareResult : uint8_t { + RESULT_COPIED, + RESULT_NOT_COPIED, + RESULT_TIMESTAMP, + }; + + using FScreenshareCallback = std::function; + using FSourceBoxCallback = std::function; + + class CScreenshareSession { + public: + CScreenshareSession(const CScreenshareSession&) = delete; + CScreenshareSession(CScreenshareSession&&) = delete; + ~CScreenshareSession(); + + UP nextFrame(bool overlayCursor); + void stop(); + + // constraints + const std::vector& allowedFormats() const; + Vector2D bufferSize() const; + PHLMONITOR monitor() const; // this will return the correct monitor based on type + + struct { + CSignalT<> stopped; + CSignalT<> constraintsChanged; + } m_events; + + private: + CScreenshareSession(PHLMONITOR monitor, wl_client* client); + CScreenshareSession(PHLMONITOR monitor, CBox captureRegion, wl_client* client); + CScreenshareSession(PHLWINDOW window, wl_client* client); + + WP m_self; + bool m_stopped = false; + + eScreenshareType m_type = SHARE_NONE; + PHLMONITORREF m_monitor; + PHLWINDOWREF m_window; + CBox m_captureBox = {}; // given capture area in logical coordinates (see xdg_output) + + wl_client* m_client = nullptr; + std::string m_name = ""; + + std::vector m_formats; + Vector2D m_bufferSize = Vector2D(0, 0); + + SP m_tempFB; + + SP m_shareStopTimer; + bool m_sharing = false; + + struct { + CHyprSignalListener monitorDestroyed; + CHyprSignalListener monitorModeChanged; + CHyprSignalListener windowDestroyed; + CHyprSignalListener windowSizeChanged; + CHyprSignalListener windowMonitorChanged; + } m_listeners; + + void screenshareEvents(bool started); + void calculateConstraints(); + void init(); + + friend class CScreenshareFrame; + friend class CScreenshareManager; + }; + + class CCursorshareSession { + public: + CCursorshareSession(const CCursorshareSession&) = delete; + CCursorshareSession(CCursorshareSession&&) = delete; + ~CCursorshareSession(); + + eScreenshareError share(PHLMONITOR monitor, SP buffer, FSourceBoxCallback sourceBoxCallback, FScreenshareCallback callback); + void stop(); + + // constraints + DRMFormat format() const; + Vector2D bufferSize() const; + Vector2D hotspot() const; + + struct { + CSignalT<> stopped; + CSignalT<> constraintsChanged; + } m_events; + + private: + CCursorshareSession(wl_client* client, WP pointer); + + WP m_self; + bool m_stopped = false; + bool m_constraintsChanged = true; + + wl_client* m_client = nullptr; + WP m_pointer; + + // constraints + DRMFormat m_format = 0 /* DRM_FORMAT_INVALID */; + Vector2D m_hotspot = Vector2D(0, 0); + Vector2D m_bufferSize = Vector2D(0, 0); + + struct { + bool pending = false; + PHLMONITOR monitor; + SP buffer; + FSourceBoxCallback sourceBoxCallback; + FScreenshareCallback callback; + } m_pendingFrame; + + struct { + CHyprSignalListener pointerDestroyed; + CHyprSignalListener cursorChanged; + } m_listeners; + + bool copy(); + void render(); + void calculateConstraints(); + + friend class CScreenshareFrame; + friend class CScreenshareManager; + }; + + class CScreenshareFrame { + public: + CScreenshareFrame(const CScreenshareFrame&) = delete; + CScreenshareFrame(CScreenshareFrame&&) = delete; + CScreenshareFrame(WP session, bool overlayCursor, bool isFirst); + ~CScreenshareFrame(); + + bool done() const; + eScreenshareError share(SP buffer, const CRegion& damage, FScreenshareCallback callback); + + Vector2D bufferSize() const; + wl_output_transform transform() const; // returns the transform applied by compositor on the buffer + const CRegion& damage() const; + + private: + WP m_self; + WP m_session; + FScreenshareCallback m_callback; + SP m_buffer; + Vector2D m_bufferSize = Vector2D(0, 0); + CRegion m_damage; // damage in buffer coords + bool m_shared = false, m_copied = false, m_failed = false; + bool m_overlayCursor = true; + bool m_isFirst = false; + + // + void copy(); + bool copyDmabuf(); + bool copyShm(); + + void render(); + void renderMonitor(); + void renderMonitorRegion(); + void renderWindow(); + + void storeTempFB(); + + friend class CScreenshareManager; + friend class CScreenshareSession; + }; + + class CScreenshareManager { + public: + CScreenshareManager(); + + UP newSession(wl_client* client, PHLMONITOR monitor); + UP newSession(wl_client* client, PHLMONITOR monitor, CBox captureRegion); + UP newSession(wl_client* client, PHLWINDOW window); + + WP getManagedSession(wl_client* client, PHLMONITOR monitor); + WP getManagedSession(wl_client* client, PHLMONITOR monitor, CBox captureBox); + WP getManagedSession(wl_client* client, PHLWINDOW window); + + UP newCursorSession(wl_client* client, WP pointer); + + void onOutputCommit(PHLMONITOR monitor); + bool isOutputBeingSSd(PHLMONITOR monitor); + + private: + std::vector> m_sessions; + std::vector> m_cursorSessions; + std::vector> m_pendingFrames; + + struct SManagedSession { + SManagedSession(UP&& session); + + UP m_session; + CHyprSignalListener stoppedListener; + }; + + std::vector> m_managedSessions; + WP getManagedSession(eScreenshareType type, wl_client* client, PHLMONITOR monitor, PHLWINDOW window, CBox captureBox); + + friend class CScreenshareSession; + }; + + inline UP& mgr() { + static UP manager = nullptr; + if (!manager && g_pHyprRenderer) { + Log::logger->log(Log::DEBUG, "Starting ScreenshareManager"); + manager = makeUnique(); + } + return manager; + } +} + +template <> +struct std::formatter : std::formatter { + auto format(const Screenshare::eScreenshareType& res, std::format_context& ctx) const { + switch (res) { + case Screenshare::SHARE_MONITOR: return formatter::format("monitor", ctx); + case Screenshare::SHARE_WINDOW: return formatter::format("window", ctx); + case Screenshare::SHARE_REGION: return formatter::format("region", ctx); + case Screenshare::SHARE_NONE: return formatter::format("ERR NONE", ctx); + } + return formatter::format("error", ctx); + } +}; diff --git a/src/managers/screenshare/ScreenshareSession.cpp b/src/managers/screenshare/ScreenshareSession.cpp new file mode 100644 index 00000000..2fddc431 --- /dev/null +++ b/src/managers/screenshare/ScreenshareSession.cpp @@ -0,0 +1,166 @@ +#include "ScreenshareManager.hpp" +#include "../../render/OpenGL.hpp" +#include "../../Compositor.hpp" +#include "../../render/Renderer.hpp" +#include "../EventManager.hpp" +#include "../eventLoop/EventLoopManager.hpp" +#include "../../event/EventBus.hpp" + +using namespace Screenshare; + +CScreenshareSession::CScreenshareSession(PHLMONITOR monitor, wl_client* client) : m_type(SHARE_MONITOR), m_monitor(monitor), m_client(client) { + init(); +} + +CScreenshareSession::CScreenshareSession(PHLWINDOW window, wl_client* client) : m_type(SHARE_WINDOW), m_window(window), m_client(client) { + m_listeners.windowDestroyed = m_window->m_events.unmap.listen([this]() { stop(); }); + m_listeners.windowSizeChanged = m_window->m_events.resize.listen([this]() { + calculateConstraints(); + m_events.constraintsChanged.emit(); + }); + m_listeners.windowMonitorChanged = m_window->m_events.monitorChanged.listen([this]() { + m_listeners.monitorDestroyed = monitor()->m_events.disconnect.listen([this]() { stop(); }); + m_listeners.monitorModeChanged = monitor()->m_events.modeChanged.listen([this]() { + calculateConstraints(); + m_events.constraintsChanged.emit(); + }); + + calculateConstraints(); + m_events.constraintsChanged.emit(); + }); + + init(); +} + +CScreenshareSession::CScreenshareSession(PHLMONITOR monitor, CBox captureRegion, wl_client* client) : + m_type(SHARE_REGION), m_monitor(monitor), m_captureBox(captureRegion), m_client(client) { + init(); +} + +CScreenshareSession::~CScreenshareSession() { + stop(); + uintptr_t ptr = m_type == SHARE_WINDOW && !m_window.expired() ? (uintptr_t)m_window.get() : (m_monitor.expired() ? (uintptr_t)nullptr : (uintptr_t)m_monitor.get()); + LOGM(Log::TRACE, "Destroyed screenshare session for ({}): {}, {:x}", m_type, m_name, ptr); +} + +void CScreenshareSession::stop() { + if (m_stopped) + return; + m_stopped = true; + m_events.stopped.emit(); + + screenshareEvents(false); +} + +void CScreenshareSession::init() { + uintptr_t ptr = m_type == SHARE_WINDOW && !m_window.expired() ? (uintptr_t)m_window.get() : (m_monitor.expired() ? (uintptr_t)nullptr : (uintptr_t)m_monitor.get()); + LOGM(Log::TRACE, "Created screenshare session for ({}): {}, {:x}", m_type, m_name, ptr); + + m_shareStopTimer = makeShared( + std::chrono::milliseconds(500), + [this](SP self, void* data) { + // if this fires, then it's been half a second since the last frame, so we aren't sharing + screenshareEvents(false); + }, + nullptr); + + if (g_pEventLoopManager) + g_pEventLoopManager->addTimer(m_shareStopTimer); + + // scale capture box since it's in logical coords + m_captureBox.scale(monitor()->m_scale); + + m_listeners.monitorDestroyed = monitor()->m_events.disconnect.listen([this]() { stop(); }); + m_listeners.monitorModeChanged = monitor()->m_events.modeChanged.listen([this]() { + calculateConstraints(); + m_events.constraintsChanged.emit(); + }); + + calculateConstraints(); +} + +void CScreenshareSession::calculateConstraints() { + const auto PMONITOR = monitor(); + if (!PMONITOR) { + stop(); + return; + } + + // TODO: maybe support more that just monitor format in the future? + m_formats.clear(); + m_formats.push_back(NFormatUtils::alphaFormat(g_pHyprOpenGL->getPreferredReadFormat(PMONITOR))); + m_formats.push_back(g_pHyprOpenGL->getPreferredReadFormat(PMONITOR)); // some clients don't like alpha formats + + // TODO: hack, we can't bit flip so we'll format flip heh, GL_BGRA_EXT won't work here + for (auto& format : m_formats) { + if (format == DRM_FORMAT_XRGB2101010 || format == DRM_FORMAT_ARGB2101010) + format = DRM_FORMAT_XBGR2101010; + } + + switch (m_type) { + case SHARE_MONITOR: + m_bufferSize = PMONITOR->m_pixelSize; + m_name = PMONITOR->m_name; + break; + case SHARE_WINDOW: + m_bufferSize = (m_window->m_realSize->value() * PMONITOR->m_scale).round(); + m_name = m_window->m_title; + break; + case SHARE_REGION: + m_bufferSize = PMONITOR->m_transform % 2 == 0 ? m_captureBox.size() : Vector2D{m_captureBox.h, m_captureBox.w}; + m_name = PMONITOR->m_name; + break; + case SHARE_NONE: + default: + LOGM(Log::ERR, "Invalid share type?? This shouldn't happen"); + stop(); + return; + } + + LOGM(Log::TRACE, "constraints changed for {}", m_name); +} + +void CScreenshareSession::screenshareEvents(bool startSharing) { + if (startSharing && !m_sharing) { + m_sharing = true; + g_pEventManager->postEvent(SHyprIPCEvent{.event = "screencast", .data = std::format("1,{}", m_type)}); + g_pEventManager->postEvent(SHyprIPCEvent{.event = "screencastv2", .data = std::format("1,{},{}", m_type, m_name)}); + LOGM(Log::INFO, "Started screenshare session for ({}): {}", m_type, m_name); + + Event::bus()->m_events.screenshare.state.emit(true, m_type, m_name); + } else if (!startSharing && m_sharing) { + m_sharing = false; + g_pEventManager->postEvent(SHyprIPCEvent{.event = "screencast", .data = std::format("0,{}", m_type)}); + g_pEventManager->postEvent(SHyprIPCEvent{.event = "screencastv2", .data = std::format("0,{},{}", m_type, m_name)}); + LOGM(Log::INFO, "Stopped screenshare session for ({}): {}", m_type, m_name); + + Event::bus()->m_events.screenshare.state.emit(false, m_type, m_name); + } +} + +const std::vector& CScreenshareSession::allowedFormats() const { + return m_formats; +} + +Vector2D CScreenshareSession::bufferSize() const { + return m_bufferSize; +} + +PHLMONITOR CScreenshareSession::monitor() const { + if (m_type == SHARE_WINDOW && m_window.expired()) + return nullptr; + PHLMONITORREF mon = m_type == SHARE_WINDOW ? m_window->m_monitor : m_monitor; + return mon.expired() ? nullptr : mon.lock(); +} + +UP CScreenshareSession::nextFrame(bool overlayCursor) { + UP frame = makeUnique(m_self, overlayCursor, !m_sharing); + frame->m_self = frame; + + Screenshare::mgr()->m_pendingFrames.emplace_back(frame); + + // there is now a pending frame, so block ds + g_pHyprRenderer->m_directScanoutBlocked = true; + + return frame; +} diff --git a/src/plugins/PluginAPI.cpp b/src/plugins/PluginAPI.cpp index 1d6586aa..5f89da53 100644 --- a/src/plugins/PluginAPI.cpp +++ b/src/plugins/PluginAPI.cpp @@ -2,11 +2,11 @@ #include "../Compositor.hpp" #include "../debug/HyprCtl.hpp" #include "../plugins/PluginSystem.hpp" -#include "../managers/HookSystemManager.hpp" -#include "../managers/LayoutManager.hpp" #include "../managers/eventLoop/EventLoopManager.hpp" #include "../config/ConfigManager.hpp" #include "../debug/HyprNotificationOverlay.hpp" +#include "../layout/target/Target.hpp" +#include "../layout/supplementary/WorkspaceAlgoMatcher.hpp" #include #include @@ -37,9 +37,9 @@ APICALL SP HyprlandAPI::registerCallbackDynamic(HANDLE handle, if (!PLUGIN) return nullptr; - auto PFN = g_pHookSystem->hookDynamic(event, fn, handle); - PLUGIN->m_registeredCallbacks.emplace_back(std::make_pair<>(event, WP(PFN))); - return PFN; + //auto PFN = g_pHookSystem->hookDynamic(event, fn, handle); + //PLUGIN->m_registeredCallbacks.emplace_back(std::make_pair<>(event, WP(PFN))); + return nullptr; } APICALL bool HyprlandAPI::unregisterCallback(HANDLE handle, SP fn) { @@ -48,8 +48,8 @@ APICALL bool HyprlandAPI::unregisterCallback(HANDLE handle, SP if (!PLUGIN) return false; - g_pHookSystem->unhook(fn); - std::erase_if(PLUGIN->m_registeredCallbacks, [&](const auto& other) { return other.second.lock() == fn; }); + //g_pHookSystem->unhook(fn); + // std::erase_if(PLUGIN->m_registeredCallbacks, [&](const auto& other) { return other.second.lock() == fn; }); return true; } @@ -62,25 +62,44 @@ APICALL std::string HyprlandAPI::invokeHyprctlCommand(const std::string& call, c } APICALL bool HyprlandAPI::addLayout(HANDLE handle, const std::string& name, IHyprLayout* layout) { - auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle); - - if (!PLUGIN) - return false; - - PLUGIN->m_registeredLayouts.push_back(layout); - - return g_pLayoutManager->addLayout(name, layout); + return false; } APICALL bool HyprlandAPI::removeLayout(HANDLE handle, IHyprLayout* layout) { + return false; +} + +APICALL bool HyprlandAPI::addTiledAlgo(HANDLE handle, const std::string& name, const std::type_info* typeInfo, std::function()>&& factory) { auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle); if (!PLUGIN) return false; - std::erase(PLUGIN->m_registeredLayouts, layout); + PLUGIN->m_registeredAlgos.emplace_back(name); - return g_pLayoutManager->removeLayout(layout); + return Layout::Supplementary::algoMatcher()->registerTiledAlgo(name, typeInfo, std::move(factory)); +} + +APICALL bool HyprlandAPI::addFloatingAlgo(HANDLE handle, const std::string& name, const std::type_info* typeInfo, std::function()>&& factory) { + auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle); + + if (!PLUGIN) + return false; + + PLUGIN->m_registeredAlgos.emplace_back(name); + + return Layout::Supplementary::algoMatcher()->registerFloatingAlgo(name, typeInfo, std::move(factory)); +} + +APICALL bool HyprlandAPI::removeAlgo(HANDLE handle, const std::string& name) { + auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle); + + if (!PLUGIN) + return false; + + std::erase(PLUGIN->m_registeredAlgos, name); + + return Layout::Supplementary::algoMatcher()->unregisterAlgo(name); } APICALL bool HyprlandAPI::reloadConfig() { @@ -130,7 +149,7 @@ APICALL bool HyprlandAPI::addWindowDecoration(HANDLE handle, PHLWINDOW pWindow, pWindow->addWindowDeco(std::move(pDecoration)); - g_pLayoutManager->getCurrentLayout()->recalculateWindow(pWindow); + pWindow->layoutTarget()->recalc(); return true; } @@ -407,7 +426,7 @@ APICALL bool HyprlandAPI::unregisterHyprCtlCommand(HANDLE handle, SPm_registeredHyprctlCommands, cmd); + std::erase_if(PLUGIN->m_registeredHyprctlCommands, [&](const auto& other) { return !other || other == cmd; }); g_pHyprCtl->unregisterCommand(cmd); return true; diff --git a/src/plugins/PluginAPI.hpp b/src/plugins/PluginAPI.hpp index 47a695db..77bb9926 100644 --- a/src/plugins/PluginAPI.hpp +++ b/src/plugins/PluginAPI.hpp @@ -70,6 +70,14 @@ struct SVersionInfo { class IHyprLayout; class IHyprWindowDecoration; struct SConfigValue; +class Hypr_dummyClass {}; + +namespace Layout { + class ITiledAlgorithm; + class IFloatingAlgorithm; +}; + +using HOOK_CALLBACK_FN = Hypr_dummyClass; /* These methods are for the plugin to implement @@ -143,6 +151,8 @@ namespace HyprlandAPI { APICALL Hyprlang::CConfigValue* getConfigValue(HANDLE handle, const std::string& name); /* + Deprecated: doesn't do anything anymore, use Event::bus() + Register a dynamic (function) callback to a selected event. Pointer will be free'd by Hyprland on unregisterCallback(). @@ -150,7 +160,7 @@ namespace HyprlandAPI { WARNING: Losing this pointer will unregister the callback! */ - APICALL [[nodiscard]] SP registerCallbackDynamic(HANDLE handle, const std::string& event, HOOK_CALLBACK_FN fn); + APICALL [[deprecated]] [[nodiscard]] SP registerCallbackDynamic(HANDLE handle, const std::string& event, HOOK_CALLBACK_FN fn); /* Unregisters a callback. If the callback was dynamic, frees the memory. @@ -172,15 +182,26 @@ namespace HyprlandAPI { Adds a layout to Hyprland. returns: true on success. False otherwise. + + deprecated: addTiledAlgo, addFloatingAlgo */ - APICALL bool addLayout(HANDLE handle, const std::string& name, IHyprLayout* layout); + APICALL [[deprecated]] bool addLayout(HANDLE handle, const std::string& name, IHyprLayout* layout); /* Removes an added layout from Hyprland. returns: true on success. False otherwise. + + deprecated: V2 removeAlgo */ - APICALL bool removeLayout(HANDLE handle, IHyprLayout* layout); + APICALL [[deprecated]] bool removeLayout(HANDLE handle, IHyprLayout* layout); + + /* + Algorithm fns. Used for registering and removing. Return success. + */ + APICALL bool addTiledAlgo(HANDLE handle, const std::string& name, const std::type_info* typeInfo, std::function()>&& factory); + APICALL bool addFloatingAlgo(HANDLE handle, const std::string& name, const std::type_info* typeInfo, std::function()>&& factory); + APICALL bool removeAlgo(HANDLE handle, const std::string& name); /* Queues a config reload. Does not take effect immediately. diff --git a/src/plugins/PluginSystem.cpp b/src/plugins/PluginSystem.cpp index 53b05bc8..3bd8f473 100644 --- a/src/plugins/PluginSystem.cpp +++ b/src/plugins/PluginSystem.cpp @@ -4,11 +4,10 @@ #include #include "../config/ConfigManager.hpp" #include "../debug/HyprCtl.hpp" -#include "../managers/LayoutManager.hpp" -#include "../managers/HookSystemManager.hpp" #include "../managers/eventLoop/EventLoopManager.hpp" #include "../managers/permissions/DynamicPermissionManager.hpp" #include "../debug/HyprNotificationOverlay.hpp" +#include "../layout/supplementary/WorkspaceAlgoMatcher.hpp" #include "../i18n/Engine.hpp" CPluginSystem::CPluginSystem() { @@ -151,14 +150,14 @@ void CPluginSystem::unloadPlugin(const CPlugin* plugin, bool eject) { exitFunc(); } - for (auto const& [k, v] : plugin->m_registeredCallbacks) { - if (const auto SHP = v.lock()) - g_pHookSystem->unhook(SHP); - } + // for (auto const& [k, v] : plugin->m_registeredCallbacks) { + // if (const auto SHP = v.lock()) + // g_pHookSystem->unhook(SHP); + // } - const auto ls = plugin->m_registeredLayouts; - for (auto const& l : ls) - g_pLayoutManager->removeLayout(l); + for (const auto& l : plugin->m_registeredAlgos) { + Layout::Supplementary::algoMatcher()->unregisterAlgo(l); + } g_pFunctionHookSystem->removeAllHooksFrom(plugin->m_handle); @@ -171,8 +170,10 @@ void CPluginSystem::unloadPlugin(const CPlugin* plugin, bool eject) { HyprlandAPI::removeDispatcher(plugin->m_handle, d); const auto rhc = plugin->m_registeredHyprctlCommands; - for (auto const& c : rhc) - HyprlandAPI::unregisterHyprCtlCommand(plugin->m_handle, c); + for (auto const& c : rhc) { + if (const auto sp = c.lock()) + HyprlandAPI::unregisterHyprCtlCommand(plugin->m_handle, sp); + } g_pConfigManager->removePluginConfig(plugin->m_handle); diff --git a/src/plugins/PluginSystem.hpp b/src/plugins/PluginSystem.hpp index ed421960..85afaefa 100644 --- a/src/plugins/PluginSystem.hpp +++ b/src/plugins/PluginSystem.hpp @@ -12,22 +12,22 @@ class IHyprWindowDecoration; class CPlugin { public: - std::string m_name = ""; - std::string m_description = ""; - std::string m_author = ""; - std::string m_version = ""; + std::string m_name = ""; + std::string m_description = ""; + std::string m_author = ""; + std::string m_version = ""; - std::string m_path = ""; + std::string m_path = ""; - bool m_loadedWithConfig = false; + bool m_loadedWithConfig = false; - HANDLE m_handle = nullptr; + HANDLE m_handle = nullptr; - std::vector m_registeredLayouts; - std::vector m_registeredDecorations; - std::vector>> m_registeredCallbacks; - std::vector m_registeredDispatchers; - std::vector> m_registeredHyprctlCommands; + std::vector m_registeredDecorations; + //std::vector>> m_registeredCallbacks; + std::vector m_registeredDispatchers; + std::vector> m_registeredHyprctlCommands; + std::vector m_registeredAlgos; }; class CPluginSystem { diff --git a/src/protocols/ColorManagement.cpp b/src/protocols/ColorManagement.cpp index afab5a20..b9c3143b 100644 --- a/src/protocols/ColorManagement.cpp +++ b/src/protocols/ColorManagement.cpp @@ -3,7 +3,7 @@ #include "color-management-v1.hpp" #include "../helpers/Monitor.hpp" #include "core/Output.hpp" -#include "types/ColorManagement.hpp" +#include "../helpers/cm/ColorManagement.hpp" #include using namespace NColorManagement; @@ -388,12 +388,6 @@ CColorManagementFeedbackSurface::CColorManagementFeedbackSurface(SPm_settings = m_surface->getPreferredImageDescription(); m_currentPreferredId = RESOURCE->m_settings->id(); - if (!PROTO::colorManagement->m_debug && RESOURCE->m_settings->value().icc.fd >= 0) { - LOGM(Log::ERR, "FIXME: parse icc profile"); - r->error(WP_COLOR_MANAGER_V1_ERROR_UNSUPPORTED_FEATURE, "ICC profiles are not supported"); - return; - } - RESOURCE->resource()->sendReady(m_currentPreferredId); }); @@ -429,7 +423,7 @@ CColorManagementIccCreator::CColorManagementIccCreator(SPerror(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INCOMPLETE_SET, "Missing required settings"); return; } @@ -443,10 +437,10 @@ CColorManagementIccCreator::CColorManagementIccCreator(SPresource()->sendFailed(WP_IMAGE_DESCRIPTION_V1_CAUSE_UNSUPPORTED, "unsupported"); return; } @@ -459,9 +453,9 @@ CColorManagementIccCreator::CColorManagementIccCreator(SPsetSetIccFile([this](CWpImageDescriptionCreatorIccV1* r, int fd, uint32_t offset, uint32_t length) { - m_settings.icc.fd = fd; - m_settings.icc.offset = offset; - m_settings.icc.length = length; + m_icc.fd = fd; + m_icc.offset = offset; + m_icc.length = length; }); } @@ -505,6 +499,14 @@ CColorManagementParametricCreator::CColorManagementParametricCreator(SPm_self = RESOURCE; RESOURCE->m_settings = CImageDescription::from(m_settings); RESOURCE->resource()->sendReady(RESOURCE->m_settings->id()); @@ -723,26 +725,47 @@ CColorManagementImageDescriptionInfo::CColorManagementImageDescriptionInfo(SP(std::round(value * PRIMARIES_SCALE)); }; - if (m_settings.icc.fd >= 0) - m_resource->sendIccFile(m_settings.icc.fd, m_settings.icc.length); + // FIXME: + // if (m_icc.fd >= 0) + // m_resource->sendIccFile(m_icc.fd, m_icc.length); // send preferred client paramateres m_resource->sendPrimaries(toProto(m_settings.primaries.red.x), toProto(m_settings.primaries.red.y), toProto(m_settings.primaries.green.x), toProto(m_settings.primaries.green.y), toProto(m_settings.primaries.blue.x), toProto(m_settings.primaries.blue.y), toProto(m_settings.primaries.white.x), toProto(m_settings.primaries.white.y)); + if (m_settings.primariesNameSet) m_resource->sendPrimariesNamed(m_settings.primariesNamed); - m_resource->sendTfPower(std::round(m_settings.transferFunctionPower * 10000)); + m_resource->sendTfNamed(m_settings.transferFunction); + + if (m_settings.transferFunctionPower != 1.0f) + m_resource->sendTfPower(std::round(m_settings.transferFunctionPower * 10000)); + m_resource->sendLuminances(std::round(m_settings.luminances.min * 10000), m_settings.luminances.max, m_settings.luminances.reference); - // send expected display paramateres - m_resource->sendTargetPrimaries(toProto(m_settings.masteringPrimaries.red.x), toProto(m_settings.masteringPrimaries.red.y), toProto(m_settings.masteringPrimaries.green.x), - toProto(m_settings.masteringPrimaries.green.y), toProto(m_settings.masteringPrimaries.blue.x), toProto(m_settings.masteringPrimaries.blue.y), - toProto(m_settings.masteringPrimaries.white.x), toProto(m_settings.masteringPrimaries.white.y)); - m_resource->sendTargetLuminance(std::round(m_settings.masteringLuminances.min * 10000), m_settings.masteringLuminances.max); - m_resource->sendTargetMaxCll(m_settings.maxCLL); - m_resource->sendTargetMaxFall(m_settings.maxFALL); + const auto& targetPrimaries = ( // + m_settings.masteringPrimaries.red.x != 0 || m_settings.masteringPrimaries.red.y != 0 || // + m_settings.masteringPrimaries.green.x != 0 || m_settings.masteringPrimaries.green.y != 0 || // + m_settings.masteringPrimaries.blue.x != 0 || m_settings.masteringPrimaries.blue.y != 0) ? + m_settings.masteringPrimaries : + m_settings.primaries; + + m_resource->sendTargetPrimaries( // + toProto(targetPrimaries.red.x), toProto(targetPrimaries.red.y), // + toProto(targetPrimaries.green.x), toProto(targetPrimaries.green.y), // + toProto(targetPrimaries.blue.x), toProto(targetPrimaries.blue.y), // + toProto(targetPrimaries.white.x), toProto(targetPrimaries.white.y)); + + if (m_settings.masteringLuminances.max > 0) + m_resource->sendTargetLuminance(std::round(m_settings.masteringLuminances.min * 10000), m_settings.masteringLuminances.max); + else + m_resource->sendTargetLuminance(std::round(m_settings.luminances.min * 10000), m_settings.luminances.max); + + if (m_settings.maxCLL > 0 || m_settings.maxFALL > 0) { + m_resource->sendTargetMaxCll(m_settings.maxCLL); + m_resource->sendTargetMaxFall(m_settings.maxFALL); + } m_resource->sendDone(); } diff --git a/src/protocols/ColorManagement.hpp b/src/protocols/ColorManagement.hpp index d43d5c12..7cdab37d 100644 --- a/src/protocols/ColorManagement.hpp +++ b/src/protocols/ColorManagement.hpp @@ -7,7 +7,7 @@ #include "../helpers/Monitor.hpp" #include "core/Compositor.hpp" #include "color-management-v1.hpp" -#include "types/ColorManagement.hpp" +#include "../helpers/cm/ColorManagement.hpp" class CColorManager; class CColorManagementOutput; @@ -109,6 +109,14 @@ class CColorManagementIccCreator { WP m_self; NColorManagement::SImageDescription m_settings; + struct SIccFile { + int fd = -1; + uint32_t length = 0; + uint32_t offset = 0; + bool operator==(const SIccFile& i2) const { + return fd == i2.fd; + } + } m_icc; private: SP m_resource; diff --git a/src/protocols/CommitTiming.cpp b/src/protocols/CommitTiming.cpp index 2fcd8e9c..9cc2da83 100644 --- a/src/protocols/CommitTiming.cpp +++ b/src/protocols/CommitTiming.cpp @@ -12,61 +12,48 @@ CCommitTimerResource::CCommitTimerResource(UP&& resource_, SP< m_resource->setOnDestroy([this](CWpCommitTimerV1* r) { PROTO::commitTiming->destroyResource(this); }); m_resource->setSetTimestamp([this](CWpCommitTimerV1* r, uint32_t tvHi, uint32_t tvLo, uint32_t tvNsec) { - return; - if (!m_surface) { r->error(WP_COMMIT_TIMER_V1_ERROR_SURFACE_DESTROYED, "Surface was gone"); return; } - if (m_pendingTimeout.has_value()) { + if (m_surface->m_pending.pendingTimeout.has_value()) { r->error(WP_COMMIT_TIMER_V1_ERROR_TIMESTAMP_EXISTS, "Timestamp is already set"); return; } - timespec ts; - ts.tv_sec = (((uint64_t)tvHi) << 32) | (uint64_t)tvLo; - ts.tv_nsec = tvNsec; + const auto delay = Time::till({.tv_sec = (((uint64_t)tvHi) << 32) | (uint64_t)tvLo, .tv_nsec = tvNsec}); - const auto TIME = Time::fromTimespec(&ts); - const auto TIME_NOW = Time::steadyNow(); - - if (TIME_NOW > TIME) { - // TODO: should we err here? - // for now just do nothing I guess, thats some lag. - m_pendingTimeout = Time::steady_dur::min(); + if (delay.count() <= 0) { + m_surface->m_pending.pendingTimeout.reset(); } else - m_pendingTimeout = TIME - TIME_NOW; + m_surface->m_pending.pendingTimeout = delay; }); m_listeners.surfaceStateCommit = m_surface->m_events.stateCommit2.listen([this](auto state) { - if (!m_pendingTimeout.has_value()) + if (!state || !state->pendingTimeout.has_value() || !m_surface || m_surface->isTearing()) return; m_surface->m_stateQueue.lock(state, LOCK_REASON_TIMER); - if (!m_timerPresent) { - m_timerPresent = true; - timer = makeShared( - m_pendingTimeout, - [this](SP self, void* data) { - if (!m_surface) + if (!state->timer) { + state->timer = makeShared( + state->pendingTimeout, + [surface = m_surface, state](SP self, void* data) { + if (!surface || !state) return; - m_surface->m_stateQueue.unlockFirst(LOCK_REASON_TIMER); + surface->m_stateQueue.unlock(state, LOCK_REASON_TIMER); }, nullptr); + g_pEventLoopManager->addTimer(state->timer); } else - timer->updateTimeout(m_pendingTimeout); + state->timer->updateTimeout(state->pendingTimeout); - m_pendingTimeout.reset(); + state->pendingTimeout.reset(); }); } -CCommitTimerResource::~CCommitTimerResource() { - ; -} - bool CCommitTimerResource::good() { return m_resource->resource(); } diff --git a/src/protocols/CommitTiming.hpp b/src/protocols/CommitTiming.hpp index e79face8..b5a1de93 100644 --- a/src/protocols/CommitTiming.hpp +++ b/src/protocols/CommitTiming.hpp @@ -1,7 +1,6 @@ #pragma once #include -#include #include "WaylandProtocol.hpp" #include "commit-timing-v1.hpp" @@ -14,16 +13,12 @@ class CEventLoopTimer; class CCommitTimerResource { public: CCommitTimerResource(UP&& resource_, SP surface); - ~CCommitTimerResource(); bool good(); private: - UP m_resource; - WP m_surface; - bool m_timerPresent = false; - std::optional m_pendingTimeout; - SP timer; + UP m_resource; + WP m_surface; struct { CHyprSignalListener surfaceStateCommit; diff --git a/src/protocols/ContentType.cpp b/src/protocols/ContentType.cpp index 7c8fdbc3..acae218c 100644 --- a/src/protocols/ContentType.cpp +++ b/src/protocols/ContentType.cpp @@ -6,7 +6,7 @@ CContentTypeManager::CContentTypeManager(SP resource) : if UNLIKELY (!good()) return; - m_resource->setDestroy([](CWpContentTypeManagerV1* r) {}); + m_resource->setDestroy([this](CWpContentTypeManagerV1* r) { PROTO::contentType->destroyResource(this); }); m_resource->setOnDestroy([this](CWpContentTypeManagerV1* r) { PROTO::contentType->destroyResource(this); }); m_resource->setGetSurfaceContentType([](CWpContentTypeManagerV1* r, uint32_t id, wl_resource* surface) { @@ -19,7 +19,7 @@ CContentTypeManager::CContentTypeManager(SP resource) : return; } - if (SURF->m_colorManagement) { + if (SURF->m_contentType) { r->error(WP_CONTENT_TYPE_MANAGER_V1_ERROR_ALREADY_CONSTRUCTED, "CT manager already exists"); return; } diff --git a/src/protocols/ExtWorkspace.cpp b/src/protocols/ExtWorkspace.cpp index 2af72a4d..4fa3152d 100644 --- a/src/protocols/ExtWorkspace.cpp +++ b/src/protocols/ExtWorkspace.cpp @@ -1,9 +1,8 @@ #include "ExtWorkspace.hpp" #include "../Compositor.hpp" -#include "../managers/HookSystemManager.hpp" #include "../managers/eventLoop/EventLoopManager.hpp" +#include "../event/EventBus.hpp" #include -#include #include #include "core/Output.hpp" @@ -27,8 +26,11 @@ CExtWorkspaceGroupResource::CExtWorkspaceGroupResource(WPm_name); - if (auto resource = output->outputResourceFrom(m_resource->client())) - m_resource->sendOutputEnter(resource->getResource()->resource()); + if (auto resources = output->outputResourcesFrom(m_resource->client()); !resources.empty()) { + for (const auto& r : resources) { + m_resource->sendOutputEnter(r->getResource()->resource()); + } + } m_listeners.outputBound = output->m_events.outputBound.listen([this](const SP& output) { if (output->client() == m_resource->client()) @@ -294,17 +296,13 @@ void CExtWorkspaceManagerResource::onWorkspaceCreated(const PHLWORKSPACE& worksp } CExtWorkspaceProtocol::CExtWorkspaceProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { - static auto P1 = g_pHookSystem->hookDynamic("createWorkspace", [this](void* self, SCallbackInfo& info, std::any data) { - auto workspace = std::any_cast(data)->m_self.lock(); - + static auto P1 = Event::bus()->m_events.workspace.created.listen([this](PHLWORKSPACEREF workspace) { for (auto const& m : m_managers) { - m->onWorkspaceCreated(workspace); + m->onWorkspaceCreated(workspace.lock()); } }); - static auto P2 = g_pHookSystem->hookDynamic("monitorAdded", [this](void* self, SCallbackInfo& info, std::any data) { - auto monitor = std::any_cast(data); - + static auto P2 = Event::bus()->m_events.monitor.added.listen([this](PHLMONITOR monitor) { for (auto const& m : m_managers) { m->onMonitorCreated(monitor); } diff --git a/src/protocols/Fifo.cpp b/src/protocols/Fifo.cpp index d9f873c9..355644d9 100644 --- a/src/protocols/Fifo.cpp +++ b/src/protocols/Fifo.cpp @@ -1,8 +1,8 @@ #include "Fifo.hpp" #include "Compositor.hpp" #include "core/Compositor.hpp" -#include "../managers/HookSystemManager.hpp" #include "../helpers/Monitor.hpp" +#include "../event/EventBus.hpp" CFifoResource::CFifoResource(UP&& resource_, SP surface) : m_resource(std::move(resource_)), m_surface(surface) { if UNLIKELY (!m_resource->resource()) @@ -18,7 +18,8 @@ CFifoResource::CFifoResource(UP&& resource_, SP s return; } - m_pending.barrierSet = true; + m_surface->m_pending.barrierSet = true; + m_surface->m_pending.updated.bits.fifo = true; }); m_resource->setWaitBarrier([this](CWpFifoV1* r) { @@ -27,49 +28,37 @@ CFifoResource::CFifoResource(UP&& resource_, SP s return; } - if (!m_pending.barrierSet) - return; + if (!m_surface->m_current.barrierSet) { + // that might mean an empty commit with a barrier_set alone + static const auto PPEND = CConfigValue("debug:fifo_pending_workaround"); + if (!m_surface->m_pending.fifoScheduled) + m_surface->m_pending.fifoScheduled = checkMonitors(*PPEND); - m_pending.surfaceLocked = true; + return; + } + + m_surface->m_pending.surfaceLocked = true; }); m_listeners.surfaceStateCommit = m_surface->m_events.stateCommit.listen([this](auto state) { - if (!m_pending.surfaceLocked) + if (!state || !state->surfaceLocked) return; + static const auto PPEND = CConfigValue("debug:fifo_pending_workaround"); + //#TODO: // this feels wrong, but if we have no pending frames, presented might never come because // we are waiting on the barrier to unlock and no damage is around. - if (m_surface->m_enteredOutputs.empty() && m_surface->m_hlSurface) { - for (auto& m : g_pCompositor->m_monitors) { - if (!m || !m->m_enabled) - continue; + // unlock on timeout instead? + if (!state->fifoScheduled) + state->fifoScheduled = checkMonitors(*PPEND); - auto box = m_surface->m_hlSurface->getSurfaceBoxGlobal(); - if (box && !box->intersection({m->m_position, m->m_size}).empty()) { - if (m->m_tearingState.activelyTearing) - return; // dont fifo lock on tearing. - - g_pCompositor->scheduleFrameForMonitor(m, Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME); - } - } - } else { - for (auto& m : m_surface->m_enteredOutputs) { - if (!m) - continue; - - if (m->m_tearingState.activelyTearing) - return; // dont fifo lock on tearing. - - g_pCompositor->scheduleFrameForMonitor(m.lock(), Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME); - } - } + if (!state->fifoScheduled) + return; // only lock once its mapped. if (m_surface->m_mapped) m_surface->m_stateQueue.lock(state, LOCK_REASON_FIFO); - - m_pending = {}; }); } @@ -82,9 +71,41 @@ bool CFifoResource::good() { } void CFifoResource::presented() { + m_surface->m_current.barrierSet = false; m_surface->m_stateQueue.unlockFirst(LOCK_REASON_FIFO); } +bool CFifoResource::checkMonitors(bool needsSchedule) { + if (m_surface->m_enteredOutputs.empty() && m_surface->m_hlSurface) { + for (auto& m : g_pCompositor->m_monitors) { + if (!m || !m->m_enabled) + continue; + + auto box = m_surface->m_hlSurface->getSurfaceBoxGlobal(); + if (box && !box->intersection({m->m_position, m->m_size}).empty()) { + if (m->m_tearingState.activelyTearing) + return false; // dont fifo lock on tearing. + + if (needsSchedule) + g_pCompositor->scheduleFrameForMonitor(m, Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME); + } + } + } else { + for (auto& m : m_surface->m_enteredOutputs) { + if (!m) + continue; + + if (m->m_tearingState.activelyTearing) + return false; // dont fifo lock on tearing. + + if (needsSchedule) + g_pCompositor->scheduleFrameForMonitor(m.lock(), Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME); + } + } + + return true; +} + CFifoManagerResource::CFifoManagerResource(UP&& resource_) : m_resource(std::move(resource_)) { if UNLIKELY (!m_resource->resource()) return; @@ -132,9 +153,7 @@ bool CFifoManagerResource::good() { } CFifoProtocol::CFifoProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { - static auto P = g_pHookSystem->hookDynamic("monitorAdded", [this](void* self, SCallbackInfo& info, std::any param) { - auto M = std::any_cast(param); - + static auto P = Event::bus()->m_events.monitor.added.listen([this](PHLMONITOR M) { M->m_events.presented.listenStatic([this, m = PHLMONITORREF{M}]() { if (!m || !PROTO::fifo) return; diff --git a/src/protocols/Fifo.hpp b/src/protocols/Fifo.hpp index 8551e78c..5b143f79 100644 --- a/src/protocols/Fifo.hpp +++ b/src/protocols/Fifo.hpp @@ -21,18 +21,12 @@ class CFifoResource { WP m_surface; - struct SState { - bool barrierSet = false; - bool surfaceLocked = false; - }; - - SState m_pending; - struct { CHyprSignalListener surfaceStateCommit; } m_listeners; void presented(); + bool checkMonitors(bool needsSchedule = false); friend class CFifoProtocol; friend class CFifoManagerResource; diff --git a/src/protocols/ForeignToplevel.cpp b/src/protocols/ForeignToplevel.cpp index 5515d2fb..baabda7c 100644 --- a/src/protocols/ForeignToplevel.cpp +++ b/src/protocols/ForeignToplevel.cpp @@ -1,6 +1,6 @@ #include "ForeignToplevel.hpp" #include "../Compositor.hpp" -#include "../managers/HookSystemManager.hpp" +#include "../event/EventBus.hpp" CForeignToplevelHandle::CForeignToplevelHandle(SP resource_, PHLWINDOW pWindow_) : m_resource(resource_), m_window(pWindow_) { if UNLIKELY (!resource_->resource()) @@ -54,26 +54,26 @@ void CForeignToplevelList::onMap(PHLWINDOW pWindow) { std::erase_if(m_handles, [&](const auto& other) { return other.get() == OLDHANDLE.get(); }); } - const auto NEWHANDLE = PROTO::foreignToplevel->m_handles.emplace_back( + auto newHandle = PROTO::foreignToplevel->m_handles.emplace_back( makeShared(makeShared(m_resource->client(), m_resource->version(), 0), pWindow)); - if (!NEWHANDLE->good()) { + if (!newHandle->good()) { LOGM(Log::ERR, "Couldn't create a foreign handle"); m_resource->noMemory(); PROTO::foreignToplevel->m_handles.pop_back(); return; } - const auto IDENTIFIER = std::format("{:08x}->{:016x}", sc(rc(this) & 0xFFFFFFFF), rc(pWindow.get())); + const auto IDENTIFIER = std::format("{:x}", pWindow->m_stableID); LOGM(Log::DEBUG, "Newly mapped window gets an identifier of {}", IDENTIFIER); - m_resource->sendToplevel(NEWHANDLE->m_resource.get()); - NEWHANDLE->m_resource->sendIdentifier(IDENTIFIER.c_str()); - NEWHANDLE->m_resource->sendAppId(pWindow->m_initialClass.c_str()); - NEWHANDLE->m_resource->sendTitle(pWindow->m_initialTitle.c_str()); - NEWHANDLE->m_resource->sendDone(); + m_resource->sendToplevel(newHandle->m_resource.get()); + newHandle->m_resource->sendIdentifier(IDENTIFIER.c_str()); + newHandle->m_resource->sendAppId(pWindow->m_initialClass.c_str()); + newHandle->m_resource->sendTitle(pWindow->m_initialTitle.c_str()); + newHandle->m_resource->sendDone(); - m_handles.push_back(NEWHANDLE); + m_handles.emplace_back(std::move(newHandle)); } SP CForeignToplevelList::handleForWindow(PHLWINDOW pWindow) { @@ -123,9 +123,7 @@ 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) { - auto window = std::any_cast(data); - + static auto P = Event::bus()->m_events.window.open.listen([this](PHLWINDOW window) { if (!windowValidForForeign(window)) return; @@ -134,9 +132,7 @@ CForeignToplevelProtocol::CForeignToplevelProtocol(const wl_interface* iface, co } }); - static auto P1 = g_pHookSystem->hookDynamic("closeWindow", [this](void* self, SCallbackInfo& info, std::any data) { - auto window = std::any_cast(data); - + static auto P1 = Event::bus()->m_events.window.close.listen([this](PHLWINDOW window) { if (!windowValidForForeign(window)) return; @@ -145,9 +141,7 @@ CForeignToplevelProtocol::CForeignToplevelProtocol(const wl_interface* iface, co } }); - static auto P2 = g_pHookSystem->hookDynamic("windowTitle", [this](void* self, SCallbackInfo& info, std::any data) { - auto window = std::any_cast(data); - + static auto P2 = Event::bus()->m_events.window.title.listen([this](PHLWINDOW window) { if (!windowValidForForeign(window)) return; diff --git a/src/protocols/ForeignToplevel.hpp b/src/protocols/ForeignToplevel.hpp index f0188292..0ff74e75 100644 --- a/src/protocols/ForeignToplevel.hpp +++ b/src/protocols/ForeignToplevel.hpp @@ -45,9 +45,9 @@ class CForeignToplevelList { class CForeignToplevelProtocol : public IWaylandProtocol { public: CForeignToplevelProtocol(const wl_interface* iface, const int& ver, const std::string& name); - PHLWINDOW windowFromHandleResource(wl_resource* res); virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); + PHLWINDOW windowFromHandleResource(wl_resource* res); private: void onManagerResourceDestroy(CForeignToplevelList* mgr); diff --git a/src/protocols/ForeignToplevelWlr.cpp b/src/protocols/ForeignToplevelWlr.cpp index 5e18483d..56591261 100644 --- a/src/protocols/ForeignToplevelWlr.cpp +++ b/src/protocols/ForeignToplevelWlr.cpp @@ -5,8 +5,8 @@ #include "../managers/input/InputManager.hpp" #include "../desktop/state/FocusState.hpp" #include "../render/Renderer.hpp" -#include "../managers/HookSystemManager.hpp" #include "../managers/EventManager.hpp" +#include "../event/EventBus.hpp" CForeignToplevelHandleWlr::CForeignToplevelHandleWlr(SP resource_, PHLWINDOW pWindow_) : m_resource(resource_), m_window(pWindow_) { if UNLIKELY (!resource_->resource()) @@ -154,17 +154,23 @@ void CForeignToplevelHandleWlr::sendMonitor(PHLMONITOR pMonitor) { const auto CLIENT = m_resource->client(); if (const auto PLASTMONITOR = g_pCompositor->getMonitorFromID(m_lastMonitorID); PLASTMONITOR && PROTO::outputs.contains(PLASTMONITOR->m_name)) { - const auto OLDRESOURCE = PROTO::outputs.at(PLASTMONITOR->m_name)->outputResourceFrom(CLIENT); + const auto OLDRESOURCES = PROTO::outputs.at(PLASTMONITOR->m_name)->outputResourcesFrom(CLIENT); - if LIKELY (OLDRESOURCE) - m_resource->sendOutputLeave(OLDRESOURCE->getResource()->resource()); + if LIKELY (!OLDRESOURCES.empty()) { + for (const auto& r : OLDRESOURCES) { + m_resource->sendOutputLeave(r->getResource()->resource()); + } + } } if (PROTO::outputs.contains(pMonitor->m_name)) { - const auto NEWRESOURCE = PROTO::outputs.at(pMonitor->m_name)->outputResourceFrom(CLIENT); + const auto NEWRESOURCES = PROTO::outputs.at(pMonitor->m_name)->outputResourcesFrom(CLIENT); - if LIKELY (NEWRESOURCE) - m_resource->sendOutputEnter(NEWRESOURCE->getResource()->resource()); + if LIKELY (!NEWRESOURCES.empty()) { + for (const auto& r : NEWRESOURCES) { + m_resource->sendOutputEnter(r->getResource()->resource()); + } + } } m_lastMonitorID = pMonitor->m_id; @@ -337,70 +343,57 @@ 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); - - if (!windowValidForForeign(PWINDOW)) + static auto P = Event::bus()->m_events.window.open.listen([this](PHLWINDOW window) { + if (!windowValidForForeign(window)) return; for (auto const& m : m_managers) { - m->onMap(PWINDOW); + m->onMap(window); } }); - static auto P1 = g_pHookSystem->hookDynamic("closeWindow", [this](void* self, SCallbackInfo& info, std::any data) { - const auto PWINDOW = std::any_cast(data); - - if (!windowValidForForeign(PWINDOW)) + static auto P1 = Event::bus()->m_events.window.close.listen([this](PHLWINDOW window) { + if (!windowValidForForeign(window)) return; for (auto const& m : m_managers) { - m->onUnmap(PWINDOW); + m->onUnmap(window); } }); - static auto P2 = g_pHookSystem->hookDynamic("windowTitle", [this](void* self, SCallbackInfo& info, std::any data) { - const auto PWINDOW = std::any_cast(data); - - if (!windowValidForForeign(PWINDOW)) + static auto P2 = Event::bus()->m_events.window.title.listen([this](PHLWINDOW window) { + if (!windowValidForForeign(window)) return; for (auto const& m : m_managers) { - m->onTitle(PWINDOW); + m->onTitle(window); } }); - static auto P3 = g_pHookSystem->hookDynamic("activeWindow", [this](void* self, SCallbackInfo& info, std::any data) { - const auto PWINDOW = std::any_cast(data); - - if (PWINDOW && !windowValidForForeign(PWINDOW)) + static auto P3 = Event::bus()->m_events.window.active.listen([this](PHLWINDOW window, Desktop::eFocusReason reason) { + if (window && !windowValidForForeign(window)) return; for (auto const& m : m_managers) { - m->onNewFocus(PWINDOW); + m->onNewFocus(window); } }); - 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)); - const auto PWORKSPACE = std::any_cast(std::any_cast>(data).at(1)); - - if (!PWORKSPACE) + static auto P4 = Event::bus()->m_events.window.moveToWorkspace.listen([this](PHLWINDOW window, PHLWORKSPACE ws) { + if (!ws) return; for (auto const& m : m_managers) { - m->onMoveMonitor(PWINDOW, PWORKSPACE->m_monitor.lock()); + m->onMoveMonitor(window, ws->m_monitor.lock()); } }); - static auto P5 = g_pHookSystem->hookDynamic("fullscreen", [this](void* self, SCallbackInfo& info, std::any data) { - const auto PWINDOW = std::any_cast(data); - - if (!windowValidForForeign(PWINDOW)) + static auto P5 = Event::bus()->m_events.window.fullscreen.listen([this](PHLWINDOW window) { + if (!windowValidForForeign(window)) return; for (auto const& m : m_managers) { - m->onFullscreen(PWINDOW); + m->onFullscreen(window); } }); } diff --git a/src/protocols/GammaControl.cpp b/src/protocols/GammaControl.cpp index 2517c754..c28b881f 100644 --- a/src/protocols/GammaControl.cpp +++ b/src/protocols/GammaControl.cpp @@ -56,6 +56,12 @@ CGammaControl::CGammaControl(SP resource_, wl_resource* out LOGM(Log::DEBUG, "setGamma for {}", m_monitor->m_name); + if UNLIKELY (m_monitor->gammaRampsInUse()) { + LOGM(Log::ERR, "Monitor has gamma ramps in use (ICC?)"); + m_resource->sendFailed(); + return; + } + // TODO: make CFileDescriptor getflags use F_GETFL int fdFlags = fcntl(gammaFd.get(), F_GETFL, 0); if UNLIKELY (fdFlags < 0) { diff --git a/src/protocols/ImageCaptureSource.cpp b/src/protocols/ImageCaptureSource.cpp new file mode 100644 index 00000000..9f54533e --- /dev/null +++ b/src/protocols/ImageCaptureSource.cpp @@ -0,0 +1,135 @@ +#include "ImageCaptureSource.hpp" +#include "core/Output.hpp" +#include "../helpers/Monitor.hpp" +#include "../desktop/view/Window.hpp" +#include "ForeignToplevel.hpp" + +CImageCaptureSource::CImageCaptureSource(SP resource, PHLMONITOR pMonitor) : m_resource(resource), m_monitor(pMonitor) { + if UNLIKELY (!good()) + return; + + m_resource->setData(this); + m_resource->setDestroy([this](CExtImageCaptureSourceV1* pMgr) { PROTO::imageCaptureSource->destroyResource(this); }); + m_resource->setOnDestroy([this](CExtImageCaptureSourceV1* pMgr) { PROTO::imageCaptureSource->destroyResource(this); }); +} + +CImageCaptureSource::CImageCaptureSource(SP resource, PHLWINDOW pWindow) : m_resource(resource), m_window(pWindow) { + if UNLIKELY (!good()) + return; + + m_resource->setData(this); + m_resource->setDestroy([this](CExtImageCaptureSourceV1* pMgr) { PROTO::imageCaptureSource->destroyResource(this); }); + m_resource->setOnDestroy([this](CExtImageCaptureSourceV1* pMgr) { PROTO::imageCaptureSource->destroyResource(this); }); +} + +bool CImageCaptureSource::good() { + return m_resource && m_resource->resource(); +} + +std::string CImageCaptureSource::getName() { + if (!m_monitor.expired()) + return m_monitor->m_name; + if (!m_window.expired()) + return m_window->m_title; + + return "error"; +} + +std::string CImageCaptureSource::getTypeName() { + if (!m_monitor.expired()) + return "monitor"; + if (!m_window.expired()) + return "window"; + + return "error"; +} + +CBox CImageCaptureSource::logicalBox() { + if (!m_monitor.expired()) + return m_monitor->logicalBox(); + if (!m_window.expired()) + return m_window->getFullWindowBoundingBox(); + return CBox(); +} + +COutputImageCaptureSourceProtocol::COutputImageCaptureSourceProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { + ; +} + +void COutputImageCaptureSourceProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + const auto RESOURCE = PROTO::imageCaptureSource->m_outputManagers.emplace_back(makeShared(client, ver, id)); + + if UNLIKELY (!RESOURCE->resource()) { + wl_client_post_no_memory(client); + PROTO::imageCaptureSource->m_outputManagers.pop_back(); + return; + } + + RESOURCE->setDestroy([](CExtOutputImageCaptureSourceManagerV1* pMgr) { PROTO::imageCaptureSource->destroyResource(pMgr); }); + RESOURCE->setOnDestroy([](CExtOutputImageCaptureSourceManagerV1* pMgr) { PROTO::imageCaptureSource->destroyResource(pMgr); }); + RESOURCE->setCreateSource([](CExtOutputImageCaptureSourceManagerV1* pMgr, uint32_t id, wl_resource* output) { + PHLMONITOR pMonitor = CWLOutputResource::fromResource(output)->m_monitor.lock(); + if (!pMonitor) { + LOGM(Log::ERR, "Client tried to create source from invalid output resource"); + pMgr->error(-1, "invalid output resource"); + return; + } + + auto PSOURCE = + PROTO::imageCaptureSource->m_sources.emplace_back(makeShared(makeShared(pMgr->client(), pMgr->version(), id), pMonitor)); + PSOURCE->m_self = PSOURCE; + + LOGM(Log::INFO, "New capture source for monitor: {}", pMonitor->m_name); + }); +} + +CToplevelImageCaptureSourceProtocol::CToplevelImageCaptureSourceProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { + ; +} + +void CToplevelImageCaptureSourceProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + const auto RESOURCE = PROTO::imageCaptureSource->m_toplevelManagers.emplace_back(makeShared(client, ver, id)); + + if UNLIKELY (!RESOURCE->resource()) { + RESOURCE->noMemory(); + PROTO::imageCaptureSource->m_toplevelManagers.pop_back(); + return; + } + + RESOURCE->setDestroy([](CExtForeignToplevelImageCaptureSourceManagerV1* pMgr) { PROTO::imageCaptureSource->destroyResource(pMgr); }); + RESOURCE->setOnDestroy([](CExtForeignToplevelImageCaptureSourceManagerV1* pMgr) { PROTO::imageCaptureSource->destroyResource(pMgr); }); + RESOURCE->setCreateSource([](CExtForeignToplevelImageCaptureSourceManagerV1* pMgr, uint32_t id, wl_resource* handle) { + PHLWINDOW pWindow = PROTO::foreignToplevel->windowFromHandleResource(handle); + if (!pWindow) { + LOGM(Log::ERR, "Client tried to create source from invalid foreign toplevel handle resource"); + pMgr->error(-1, "invalid foreign toplevel resource"); + return; + } + + auto PSOURCE = + PROTO::imageCaptureSource->m_sources.emplace_back(makeShared(makeShared(pMgr->client(), pMgr->version(), id), pWindow)); + PSOURCE->m_self = PSOURCE; + + LOGM(Log::INFO, "New capture source for foreign toplevel: {}", pWindow->m_title); + }); +} + +CImageCaptureSourceProtocol::CImageCaptureSourceProtocol() { + m_output = makeUnique(&ext_output_image_capture_source_manager_v1_interface, 1, "OutputImageCaptureSource"); + m_toplevel = makeUnique(&ext_foreign_toplevel_image_capture_source_manager_v1_interface, 1, "ForeignToplevelImageCaptureSource"); +} + +SP CImageCaptureSourceProtocol::sourceFromResource(wl_resource* res) { + auto data = sc(sc(wl_resource_get_user_data(res))->data()); + return data && data->m_self ? data->m_self.lock() : nullptr; +} + +void CImageCaptureSourceProtocol::destroyResource(CExtOutputImageCaptureSourceManagerV1* resource) { + std::erase_if(m_outputManagers, [&](const auto& other) { return other.get() == resource; }); +} +void CImageCaptureSourceProtocol::destroyResource(CExtForeignToplevelImageCaptureSourceManagerV1* resource) { + std::erase_if(m_toplevelManagers, [&](const auto& other) { return other.get() == resource; }); +} +void CImageCaptureSourceProtocol::destroyResource(CImageCaptureSource* resource) { + std::erase_if(m_sources, [&](const auto& other) { return other.get() == resource; }); +} diff --git a/src/protocols/ImageCaptureSource.hpp b/src/protocols/ImageCaptureSource.hpp new file mode 100644 index 00000000..47580dd2 --- /dev/null +++ b/src/protocols/ImageCaptureSource.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include + +#include "../defines.hpp" +#include "../helpers/signal/Signal.hpp" +#include "WaylandProtocol.hpp" +#include "ext-image-capture-source-v1.hpp" + +class CImageCopyCaptureSession; + +class CImageCaptureSource { + public: + CImageCaptureSource(SP resource, PHLMONITOR pMonitor); + CImageCaptureSource(SP resource, PHLWINDOW pWindow); + + bool good(); + std::string getName(); + std::string getTypeName(); + CBox logicalBox(); + + WP m_self; + + private: + SP m_resource; + + PHLMONITORREF m_monitor; + PHLWINDOWREF m_window; + + friend class CImageCopyCaptureSession; + friend class CImageCopyCaptureCursorSession; +}; + +class COutputImageCaptureSourceProtocol : public IWaylandProtocol { + public: + COutputImageCaptureSourceProtocol(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); +}; + +class CToplevelImageCaptureSourceProtocol : public IWaylandProtocol { + public: + CToplevelImageCaptureSourceProtocol(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); +}; + +class CImageCaptureSourceProtocol { + public: + CImageCaptureSourceProtocol(); + + SP sourceFromResource(wl_resource* resource); + + void destroyResource(CExtOutputImageCaptureSourceManagerV1* resource); + void destroyResource(CExtForeignToplevelImageCaptureSourceManagerV1* resource); + void destroyResource(CImageCaptureSource* resource); + + private: + UP m_output; + UP m_toplevel; + + std::vector> m_outputManagers; + std::vector> m_toplevelManagers; + + std::vector> m_sources; + + friend class COutputImageCaptureSourceProtocol; + friend class CToplevelImageCaptureSourceProtocol; +}; + +namespace PROTO { + inline UP imageCaptureSource; +}; diff --git a/src/protocols/ImageCopyCapture.cpp b/src/protocols/ImageCopyCapture.cpp new file mode 100644 index 00000000..eca3c939 --- /dev/null +++ b/src/protocols/ImageCopyCapture.cpp @@ -0,0 +1,516 @@ +#include "ImageCopyCapture.hpp" +#include "../managers/screenshare/ScreenshareManager.hpp" +#include "../managers/permissions/DynamicPermissionManager.hpp" +#include "../managers/PointerManager.hpp" +#include "./core/Seat.hpp" +#include "LinuxDMABUF.hpp" +#include "../desktop/view/Window.hpp" +#include "../render/OpenGL.hpp" +#include "../desktop/state/FocusState.hpp" +#include + +using namespace Screenshare; + +CImageCopyCaptureSession::CImageCopyCaptureSession(SP resource, SP source, extImageCopyCaptureManagerV1Options options) : + m_resource(resource), m_source(source), m_paintCursor(options & EXT_IMAGE_COPY_CAPTURE_MANAGER_V1_OPTIONS_PAINT_CURSORS) { + if UNLIKELY (!good()) + return; + + m_resource->setDestroy([this](CExtImageCopyCaptureSessionV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); }); + m_resource->setOnDestroy([this](CExtImageCopyCaptureSessionV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); }); + + m_resource->setCreateFrame([this](CExtImageCopyCaptureSessionV1* pMgr, uint32_t id) { + if (!m_frame.expired()) { + LOGM(Log::ERR, "Duplicate frame in session for source: \"{}\"", m_source->getName()); + m_resource->error(EXT_IMAGE_COPY_CAPTURE_SESSION_V1_ERROR_DUPLICATE_FRAME, "duplicate frame"); + return; + } + + auto PFRAME = PROTO::imageCopyCapture->m_frames.emplace_back( + makeShared(makeShared(pMgr->client(), pMgr->version(), id), m_self)); + + m_frame = PFRAME; + }); + + if (m_source->m_monitor) + m_session = Screenshare::mgr()->newSession(m_resource->client(), m_source->m_monitor.lock()); + else + m_session = Screenshare::mgr()->newSession(m_resource->client(), m_source->m_window.lock()); + + if UNLIKELY (!m_session) { + m_resource->sendStopped(); + m_resource->error(-1, "unable to share screen"); + return; + } + + sendConstraints(); + + m_listeners.constraintsChanged = m_session->m_events.constraintsChanged.listen([this]() { sendConstraints(); }); + m_listeners.stopped = m_session->m_events.stopped.listen([this]() { PROTO::imageCopyCapture->destroyResource(this); }); +} + +CImageCopyCaptureSession::~CImageCopyCaptureSession() { + if (m_session) + m_session->stop(); + if (m_resource->resource()) + m_resource->sendStopped(); +} + +bool CImageCopyCaptureSession::good() { + return m_resource && m_resource->resource(); +} + +void CImageCopyCaptureSession::sendConstraints() { + auto formats = m_session->allowedFormats(); + + if UNLIKELY (formats.empty()) { + m_session->stop(); + m_resource->error(-1, "no formats available"); + return; + } + + for (DRMFormat format : formats) { + m_resource->sendShmFormat(NFormatUtils::drmToShm(format)); + + auto modifiers = g_pHyprOpenGL->getDRMFormatModifiers(format); + + wl_array modsArr; + wl_array_init(&modsArr); + if (!modifiers.empty()) { + wl_array_add(&modsArr, modifiers.size() * sizeof(uint64_t)); + memcpy(modsArr.data, modifiers.data(), modifiers.size() * sizeof(uint64_t)); + } + m_resource->sendDmabufFormat(format, &modsArr); + wl_array_release(&modsArr); + } + + dev_t device = PROTO::linuxDma->getMainDevice(); + struct wl_array deviceArr = { + .size = sizeof(device), + .data = sc(&device), + }; + m_resource->sendDmabufDevice(&deviceArr); + + m_bufferSize = m_session->bufferSize(); + m_resource->sendBufferSize(m_bufferSize.x, m_bufferSize.y); + + m_resource->sendDone(); +} + +CImageCopyCaptureCursorSession::CImageCopyCaptureCursorSession(SP resource, SP source, SP pointer) : + m_resource(resource), m_source(source), m_pointer(pointer) { + if UNLIKELY (!good()) + return; + + if (!m_source || (!m_source->m_monitor && !m_source->m_window)) + return; + + const auto PMONITOR = m_source->m_monitor.expired() ? m_source->m_window->m_monitor.lock() : m_source->m_monitor.lock(); + + // TODO: add listeners for source being destroyed + + sendCursorEvents(); + m_listeners.commit = PMONITOR->m_events.commit.listen([this, PMONITOR]() { sendCursorEvents(); }); + + m_resource->setDestroy([this](CExtImageCopyCaptureCursorSessionV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); }); + m_resource->setOnDestroy([this](CExtImageCopyCaptureCursorSessionV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); }); + + m_resource->setGetCaptureSession([this](CExtImageCopyCaptureCursorSessionV1* pMgr, uint32_t id) { + if (m_session || m_sessionResource) { + LOGM(Log::ERR, "Duplicate cursor copy capture session for source: \"{}\"", m_source->getName()); + m_resource->error(EXT_IMAGE_COPY_CAPTURE_CURSOR_SESSION_V1_ERROR_DUPLICATE_SESSION, "duplicate session"); + return; + } + + m_sessionResource = makeShared(pMgr->client(), pMgr->version(), id); + + m_sessionResource->setDestroy([this](CExtImageCopyCaptureSessionV1* pMgr) { destroyCaptureSession(); }); + m_sessionResource->setOnDestroy([this](CExtImageCopyCaptureSessionV1* pMgr) { destroyCaptureSession(); }); + + m_sessionResource->setCreateFrame([this](CExtImageCopyCaptureSessionV1* pMgr, uint32_t id) { + if UNLIKELY (!m_session || !m_sessionResource) + return; + + if (m_frameResource) { + LOGM(Log::ERR, "Duplicate frame in session for source: \"{}\"", m_source->getName()); + m_resource->error(EXT_IMAGE_COPY_CAPTURE_SESSION_V1_ERROR_DUPLICATE_FRAME, "duplicate frame"); + return; + } + + createFrame(makeShared(pMgr->client(), pMgr->version(), id)); + }); + + m_session = Screenshare::mgr()->newCursorSession(pMgr->client(), m_pointer); + if UNLIKELY (!m_session) { + m_sessionResource->sendStopped(); + m_sessionResource->error(-1, "unable to share cursor"); + return; + } + + sendConstraints(); + + m_listeners.constraintsChanged = m_session->m_events.constraintsChanged.listen([this]() { sendConstraints(); }); + m_listeners.stopped = m_session->m_events.stopped.listen([this]() { destroyCaptureSession(); }); + }); +} + +CImageCopyCaptureCursorSession::~CImageCopyCaptureCursorSession() { + destroyCaptureSession(); +} + +bool CImageCopyCaptureCursorSession::good() { + return m_resource && m_resource->resource(); +} + +void CImageCopyCaptureCursorSession::destroyCaptureSession() { + m_listeners.constraintsChanged.reset(); + m_listeners.stopped.reset(); + + if (m_frameResource && m_frameResource->resource()) + m_frameResource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_STOPPED); + m_frameResource.reset(); + + m_sessionResource.reset(); + m_session.reset(); +} + +void CImageCopyCaptureCursorSession::createFrame(SP resource) { + m_frameResource = resource; + m_captured = false; + m_buffer.reset(); + + m_frameResource->setDestroy([this](CExtImageCopyCaptureFrameV1* pMgr) { m_frameResource.reset(); }); + m_frameResource->setOnDestroy([this](CExtImageCopyCaptureFrameV1* pMgr) { m_frameResource.reset(); }); + + m_frameResource->setAttachBuffer([this](CExtImageCopyCaptureFrameV1* pMgr, wl_resource* buf) { + if UNLIKELY (!m_frameResource || !m_frameResource->resource()) + return; + + if (m_captured) { + LOGM(Log::ERR, "Frame already captured in attach_buffer, {:x}", (uintptr_t)this); + m_frameResource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, "already captured"); + m_frameResource.reset(); + return; + } + + auto PBUFFERRES = CWLBufferResource::fromResource(buf); + if (!PBUFFERRES || !PBUFFERRES->m_buffer) { + LOGM(Log::ERR, "Invalid buffer in attach_buffer {:x}", (uintptr_t)this); + m_frameResource->error(-1, "invalid buffer"); + m_frameResource.reset(); + return; + } + + m_buffer = PBUFFERRES->m_buffer.lock(); + }); + + m_frameResource->setDamageBuffer([this](CExtImageCopyCaptureFrameV1* pMgr, int32_t x, int32_t y, int32_t w, int32_t h) { + if UNLIKELY (!m_frameResource || !m_frameResource->resource()) + return; + + if (m_captured) { + LOGM(Log::ERR, "Frame already captured in damage_buffer, {:x}", (uintptr_t)this); + m_frameResource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, "already captured"); + m_frameResource.reset(); + return; + } + + if (x < 0 || y < 0 || w <= 0 || h <= 0) { + m_frameResource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_INVALID_BUFFER_DAMAGE, "invalid buffer damage"); + m_frameResource.reset(); + return; + } + + // we don't really need to keep track of damage for cursor frames because we will just copy the whole thing + }); + + m_frameResource->setCapture([this](CExtImageCopyCaptureFrameV1* pMgr) { + if UNLIKELY (!m_frameResource || !m_frameResource->resource()) + return; + + if (m_captured) { + LOGM(Log::ERR, "Frame already captured in capture, {:x}", (uintptr_t)this); + m_frameResource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, "already captured"); + m_frameResource.reset(); + return; + } + + const auto PMONITOR = m_source->m_monitor.expired() ? m_source->m_window->m_monitor.lock() : m_source->m_monitor.lock(); + + auto sourceBoxCallback = [this]() { return m_source ? m_source->logicalBox() : CBox(); }; + auto error = m_session->share(PMONITOR, m_buffer, sourceBoxCallback, [this](eScreenshareResult result) { + switch (result) { + case RESULT_COPIED: m_frameResource->sendReady(); break; + case RESULT_NOT_COPIED: m_frameResource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); break; + case RESULT_TIMESTAMP: + auto [sec, nsec] = Time::secNsec(Time::steadyNow()); + uint32_t tvSecHi = (sizeof(sec) > 4) ? sec >> 32 : 0; + uint32_t tvSecLo = sec & 0xFFFFFFFF; + m_frameResource->sendPresentationTime(tvSecHi, tvSecLo, nsec); + break; + } + }); + + if (!m_frameResource) + return; + + switch (error) { + case ERROR_NONE: m_captured = true; break; + case ERROR_NO_BUFFER: + m_frameResource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_NO_BUFFER, "no buffer attached"); + m_frameResource.reset(); + break; + case ERROR_BUFFER_SIZE: + case ERROR_BUFFER_FORMAT: m_frameResource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_BUFFER_CONSTRAINTS); break; + case ERROR_STOPPED: m_frameResource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_STOPPED); break; + case ERROR_UNKNOWN: m_frameResource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); break; + } + }); + + // we should always copy over the entire cursor image, it doesn't cost much + m_frameResource->sendDamage(0, 0, m_bufferSize.x, m_bufferSize.y); + + // the cursor is never transformed... probably? + m_frameResource->sendTransform(WL_OUTPUT_TRANSFORM_NORMAL); +} + +void CImageCopyCaptureCursorSession::sendConstraints() { + if UNLIKELY (!m_session || !m_sessionResource) + return; + + auto format = m_session->format(); + if UNLIKELY (format == DRM_FORMAT_INVALID) { + m_session->stop(); + m_sessionResource->error(-1, "no formats available"); + return; + } + + m_sessionResource->sendShmFormat(NFormatUtils::drmToShm(format)); + + auto modifiers = g_pHyprOpenGL->getDRMFormatModifiers(format); + + wl_array modsArr; + wl_array_init(&modsArr); + if (!modifiers.empty()) { + wl_array_add(&modsArr, modifiers.size() * sizeof(uint64_t)); + memcpy(modsArr.data, modifiers.data(), modifiers.size() * sizeof(uint64_t)); + } + m_sessionResource->sendDmabufFormat(format, &modsArr); + wl_array_release(&modsArr); + + dev_t device = PROTO::linuxDma->getMainDevice(); + struct wl_array deviceArr = { + .size = sizeof(device), + .data = sc(&device), + }; + m_sessionResource->sendDmabufDevice(&deviceArr); + + m_bufferSize = m_session->bufferSize(); + m_sessionResource->sendBufferSize(m_bufferSize.x, m_bufferSize.y); + + m_sessionResource->sendDone(); +} + +void CImageCopyCaptureCursorSession::sendCursorEvents() { + const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_resource->client(), PERMISSION_TYPE_CURSOR_POS); + if (PERM != PERMISSION_RULE_ALLOW_MODE_ALLOW) { + if (PERM == PERMISSION_RULE_ALLOW_MODE_DENY) { + m_resource->error(-1, "client not allowed to capture cursor"); + PROTO::imageCopyCapture->destroyResource(this); + } + return; + } + + const auto PMONITOR = m_source->m_monitor.expired() ? m_source->m_window->m_monitor.lock() : m_source->m_monitor.lock(); + CBox sourceBox = m_source->logicalBox(); + bool overlaps = g_pPointerManager->getCursorBoxGlobal().overlaps(sourceBox); + + if (m_entered && !overlaps) { + m_entered = false; + m_resource->sendLeave(); + return; + } else if (!m_entered && overlaps) { + m_entered = true; + m_resource->sendEnter(); + } + + if (!overlaps) + return; + + Vector2D pos = g_pPointerManager->position() - sourceBox.pos(); + if (pos != m_pos) { + m_pos = pos; + m_resource->sendPosition(m_pos.x, m_pos.y); + } + + Vector2D hotspot = g_pPointerManager->hotspot(); + if (hotspot != m_hotspot) { + m_hotspot = hotspot; + m_resource->sendHotspot(m_hotspot.x, m_hotspot.y); + } +} + +CImageCopyCaptureFrame::CImageCopyCaptureFrame(SP resource, WP session) : m_resource(resource), m_session(session) { + if UNLIKELY (!good()) + return; + + if (m_session->m_bufferSize != m_session->m_session->bufferSize()) { + m_session->sendConstraints(); + m_resource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); + return; + } + + m_frame = m_session->m_session->nextFrame(m_session->m_paintCursor); + + m_resource->setDestroy([this](CExtImageCopyCaptureFrameV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); }); + m_resource->setOnDestroy([this](CExtImageCopyCaptureFrameV1* pMgr) { PROTO::imageCopyCapture->destroyResource(this); }); + + m_resource->setAttachBuffer([this](CExtImageCopyCaptureFrameV1* pMgr, wl_resource* buf) { + if (m_captured) { + LOGM(Log::ERR, "Frame already captured in attach_buffer, {:x}", (uintptr_t)this); + m_resource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, "already captured"); + return; + } + + auto PBUFFERRES = CWLBufferResource::fromResource(buf); + if (!PBUFFERRES || !PBUFFERRES->m_buffer) { + LOGM(Log::ERR, "Invalid buffer in attach_buffer {:x}", (uintptr_t)this); + m_resource->error(-1, "invalid buffer"); + return; + } + + m_buffer = PBUFFERRES->m_buffer.lock(); + }); + + m_resource->setDamageBuffer([this](CExtImageCopyCaptureFrameV1* pMgr, int32_t x, int32_t y, int32_t w, int32_t h) { + if (m_captured) { + LOGM(Log::ERR, "Frame already captured in damage_buffer, {:x}", (uintptr_t)this); + m_resource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, "already captured"); + return; + } + + if (x < 0 || y < 0 || w <= 0 || h <= 0) { + m_resource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_INVALID_BUFFER_DAMAGE, "invalid buffer damage"); + return; + } + + m_clientDamage.add(x, y, w, h); + }); + + m_resource->setCapture([this](CExtImageCopyCaptureFrameV1* pMgr) { + if (m_captured) { + LOGM(Log::ERR, "Frame already captured in capture, {:x}", (uintptr_t)this); + m_resource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_ALREADY_CAPTURED, "already captured"); + return; + } + + auto error = m_frame->share(m_buffer, m_clientDamage, [this](eScreenshareResult result) { + switch (result) { + case RESULT_COPIED: m_resource->sendReady(); break; + case RESULT_NOT_COPIED: m_resource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); break; + case RESULT_TIMESTAMP: + auto [sec, nsec] = Time::secNsec(Time::steadyNow()); + uint32_t tvSecHi = (sizeof(sec) > 4) ? sec >> 32 : 0; + uint32_t tvSecLo = sec & 0xFFFFFFFF; + m_resource->sendPresentationTime(tvSecHi, tvSecLo, nsec); + break; + } + }); + + switch (error) { + case ERROR_NONE: m_captured = true; break; + case ERROR_NO_BUFFER: m_resource->error(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_ERROR_NO_BUFFER, "no buffer attached"); break; + case ERROR_BUFFER_SIZE: + case ERROR_BUFFER_FORMAT: m_resource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_BUFFER_CONSTRAINTS); break; + case ERROR_STOPPED: m_resource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_STOPPED); break; + case ERROR_UNKNOWN: m_resource->sendFailed(EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_UNKNOWN); break; + } + }); + + m_clientDamage.clear(); + + // TODO: see ScreenshareFrame::share() for "add a damage ring for output damage since last shared frame" + m_resource->sendDamage(0, 0, m_session->m_bufferSize.x, m_session->m_bufferSize.y); + + m_resource->sendTransform(m_frame->transform()); +} + +CImageCopyCaptureFrame::~CImageCopyCaptureFrame() { + if (m_session) + m_session->m_frame.reset(); +} + +bool CImageCopyCaptureFrame::good() { + return m_resource && m_resource->resource(); +} + +CImageCopyCaptureProtocol::CImageCopyCaptureProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { + ; +} + +void CImageCopyCaptureProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { + const auto RESOURCE = m_managers.emplace_back(makeShared(client, ver, id)); + + if UNLIKELY (!RESOURCE->resource()) { + wl_client_post_no_memory(client); + m_managers.pop_back(); + return; + } + + RESOURCE->setDestroy([this](CExtImageCopyCaptureManagerV1* pMgr) { destroyResource(pMgr); }); + RESOURCE->setOnDestroy([this](CExtImageCopyCaptureManagerV1* pMgr) { destroyResource(pMgr); }); + + RESOURCE->setCreateSession([this](CExtImageCopyCaptureManagerV1* pMgr, uint32_t id, wl_resource* source_, extImageCopyCaptureManagerV1Options options) { + auto source = PROTO::imageCaptureSource->sourceFromResource(source_); + if (!source) { + LOGM(Log::ERR, "Client tried to create image copy capture session from invalid source"); + pMgr->error(-1, "invalid image capture source"); + return; + } + + if (options > 1) { + LOGM(Log::ERR, "Client tried to create image copy capture session with invalid options"); + pMgr->error(EXT_IMAGE_COPY_CAPTURE_MANAGER_V1_ERROR_INVALID_OPTION, "Options can't be above 1"); + return; + } + + auto& PSESSION = + m_sessions.emplace_back(makeShared(makeShared(pMgr->client(), pMgr->version(), id), source, options)); + PSESSION->m_self = PSESSION; + LOGM(Log::INFO, "New image copy capture session for source ({}): \"{}\"", source->getTypeName(), source->getName()); + }); + + RESOURCE->setCreatePointerCursorSession([this](CExtImageCopyCaptureManagerV1* pMgr, uint32_t id, wl_resource* source_, wl_resource* pointer_) { + SP source = PROTO::imageCaptureSource->sourceFromResource(source_); + if (!source) { + LOGM(Log::ERR, "Client tried to create image copy capture session from invalid source"); + destroyResource(pMgr); + return; + } + + const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(pMgr->client(), PERMISSION_TYPE_CURSOR_POS); + if (PERM == PERMISSION_RULE_ALLOW_MODE_DENY) + return; + + m_cursorSessions.emplace_back(makeShared(makeShared(pMgr->client(), pMgr->version(), id), source, + CWLPointerResource::fromResource(pointer_))); + + LOGM(Log::INFO, "New image copy capture cursor session for source ({}): \"{}\"", source->getTypeName(), source->getName()); + }); +} + +void CImageCopyCaptureProtocol::destroyResource(CExtImageCopyCaptureManagerV1* resource) { + std::erase_if(m_managers, [&](const auto& other) { return other.get() == resource; }); +} + +void CImageCopyCaptureProtocol::destroyResource(CImageCopyCaptureSession* resource) { + std::erase_if(m_sessions, [&](const auto& other) { return other.get() == resource; }); +} + +void CImageCopyCaptureProtocol::destroyResource(CImageCopyCaptureCursorSession* resource) { + std::erase_if(m_cursorSessions, [&](const auto& other) { return other.get() == resource; }); +} + +void CImageCopyCaptureProtocol::destroyResource(CImageCopyCaptureFrame* resource) { + std::erase_if(m_frames, [&](const auto& other) { return other.get() == resource; }); +} diff --git a/src/protocols/ImageCopyCapture.hpp b/src/protocols/ImageCopyCapture.hpp new file mode 100644 index 00000000..b8cfa1e8 --- /dev/null +++ b/src/protocols/ImageCopyCapture.hpp @@ -0,0 +1,133 @@ +#pragma once + +#include + +#include "../defines.hpp" +#include "../helpers/signal/Signal.hpp" +#include "../helpers/Format.hpp" +#include "WaylandProtocol.hpp" +#include "ImageCaptureSource.hpp" +#include "ext-image-copy-capture-v1.hpp" + +class IHLBuffer; +class CWLPointerResource; +namespace Screenshare { + class CCursorshareSession; + class CScreenshareSession; + class CScreenshareFrame; +}; + +class CImageCopyCaptureFrame { + public: + CImageCopyCaptureFrame(SP resource, WP session); + ~CImageCopyCaptureFrame(); + + bool good(); + + private: + SP m_resource; + WP m_session; + UP m_frame; + + bool m_captured = false; + SP m_buffer; + CRegion m_clientDamage; + + friend class CImageCopyCaptureSession; +}; + +class CImageCopyCaptureSession { + public: + CImageCopyCaptureSession(SP resource, SP source, extImageCopyCaptureManagerV1Options options); + ~CImageCopyCaptureSession(); + + bool good(); + + private: + SP m_resource; + + SP m_source; + UP m_session; + WP m_frame; + + Vector2D m_bufferSize = Vector2D(0, 0); + bool m_paintCursor = true; + + struct { + CHyprSignalListener constraintsChanged; + CHyprSignalListener stopped; + } m_listeners; + + WP m_self; + + // + void sendConstraints(); + + friend class CImageCopyCaptureProtocol; + friend class CImageCopyCaptureFrame; +}; + +class CImageCopyCaptureCursorSession { + public: + CImageCopyCaptureCursorSession(SP resource, SP source, SP pointer); + ~CImageCopyCaptureCursorSession(); + + bool good(); + + private: + SP m_resource; + SP m_source; + SP m_pointer; + + // cursor session stuff + bool m_entered = false; + Vector2D m_pos = Vector2D(0, 0); + Vector2D m_hotspot = Vector2D(0, 0); + + // capture session stuff + SP m_sessionResource; + UP m_session; + Vector2D m_bufferSize = Vector2D(0, 0); + + // frame stuff + SP m_frameResource; + bool m_captured = false; + SP m_buffer; + + struct { + CHyprSignalListener constraintsChanged; + CHyprSignalListener stopped; + CHyprSignalListener commit; + } m_listeners; + + void sendCursorEvents(); + + void createFrame(SP resource); + void destroyCaptureSession(); + void sendConstraints(); +}; + +class CImageCopyCaptureProtocol : public IWaylandProtocol { + public: + CImageCopyCaptureProtocol(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 destroyResource(CExtImageCopyCaptureManagerV1* resource); + void destroyResource(CImageCopyCaptureSession* resource); + void destroyResource(CImageCopyCaptureCursorSession* resource); + void destroyResource(CImageCopyCaptureFrame* resource); + + private: + std::vector> m_managers; + std::vector> m_sessions; + std::vector> m_cursorSessions; + + std::vector> m_frames; + + friend class CImageCopyCaptureSession; +}; + +namespace PROTO { + inline UP imageCopyCapture; +}; diff --git a/src/protocols/LinuxDMABUF.cpp b/src/protocols/LinuxDMABUF.cpp index 4f59e4b3..f16c8c56 100644 --- a/src/protocols/LinuxDMABUF.cpp +++ b/src/protocols/LinuxDMABUF.cpp @@ -10,9 +10,9 @@ #include "core/Compositor.hpp" #include "types/DMABuffer.hpp" #include "types/WLBuffer.hpp" -#include "../managers/HookSystemManager.hpp" #include "../render/OpenGL.hpp" #include "../Compositor.hpp" +#include "../event/EventBus.hpp" using namespace Hyprutils::OS; @@ -26,6 +26,8 @@ static std::optional devIDFromFD(int fd) { CDMABUFFormatTable::CDMABUFFormatTable(SDMABUFTranche _rendererTranche, std::vector> tranches_) : m_rendererTranche(_rendererTranche), m_monitorTranches(tranches_) { + static const auto PSKIP_NON_KMS = CConfigValue("quirks:skip_non_kms_dmabuf_formats"); + std::vector formatsVec; std::set> formats; @@ -35,6 +37,17 @@ CDMABUFFormatTable::CDMABUFFormatTable(SDMABUFTranche _rendererTranche, std::vec m_rendererTranche.indices.clear(); for (auto const& fmt : m_rendererTranche.formats) { for (auto const& mod : fmt.modifiers) { + LOGM(Log::TRACE, "Render format 0x{:x} ({}) with mod 0x{:x} ({})", fmt.drmFormat, NFormatUtils::drmFormatName(fmt.drmFormat), mod, NFormatUtils::drmModifierName(mod)); + if (*PSKIP_NON_KMS && !m_monitorTranches.empty()) { + if (std::ranges::none_of(m_monitorTranches, [fmt, mod](const std::pair& pair) { + return std::ranges::any_of(pair.second.formats, [fmt, mod](const SDRMFormat& format) { + return format.drmFormat == fmt.drmFormat && std::ranges::any_of(format.modifiers, [mod](uint64_t modifier) { return mod == modifier; }); + }); + })) { + LOGM(Log::TRACE, " skipped"); + continue; + } + } auto format = std::make_pair<>(fmt.drmFormat, mod); auto [_, inserted] = formats.insert(format); if (inserted) { @@ -56,6 +69,9 @@ CDMABUFFormatTable::CDMABUFFormatTable(SDMABUFTranche _rendererTranche, std::vec tranche.indices.clear(); for (auto const& fmt : tranche.formats) { for (auto const& mod : fmt.modifiers) { + LOGM(Log::TRACE, "[DMA] Monitor format 0x{:x} ({}) with mod 0x{:x} ({})", fmt.drmFormat, NFormatUtils::drmFormatName(fmt.drmFormat), mod, + NFormatUtils::drmModifierName(mod)); + // FIXME: recheck this. DRM_FORMAT_MOD_INVALID is allowed by the proto "For legacy support". DRM_FORMAT_MOD_LINEAR should be the most compatible mod // apparently these can implode on planes, so don't use them if (mod == DRM_FORMAT_MOD_INVALID || mod == DRM_FORMAT_MOD_LINEAR) continue; @@ -147,10 +163,17 @@ CLinuxDMABUFParamsResource::CLinuxDMABUFParamsResource(UP(modHi) << 32) | modLo; + + if (m_resource->version() >= 5 && m_attrs->modifier && m_attrs->modifier != modifier) { + r->error(ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_FORMAT, "planes have different modifiers"); + return; + } + m_attrs->fds[plane] = fd; m_attrs->strides[plane] = stride; m_attrs->offsets[plane] = offset; - m_attrs->modifier = (sc(modHi) << 32) | modLo; + m_attrs->modifier = modifier; }); m_resource->setCreate([this](CZwpLinuxBufferParamsV1* r, int32_t w, int32_t h, uint32_t fmt, zwpLinuxBufferParamsV1Flags flags) { @@ -165,6 +188,13 @@ CLinuxDMABUFParamsResource::CLinuxDMABUFParamsResource(UPversion() >= 4 && std::ranges::none_of(PROTO::linuxDma->m_formatTable->m_rendererTranche.formats, [this, fmt](const auto format) { + return format.drmFormat == fmt && std::ranges::any_of(format.modifiers, [this](const auto mod) { return !mod || mod == m_attrs->modifier; }); + })) { + r->error(ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_FORMAT, "format + modifier pair is not supported"); + return; + } + m_attrs->size = {w, h}; m_attrs->format = fmt; m_attrs->planes = 4 - std::ranges::count(m_attrs->fds, -1); @@ -382,8 +412,7 @@ CLinuxDMABUFResource::CLinuxDMABUFResource(UP&& resource_) : } }); - if (m_resource->version() < 4) - sendMods(); + sendMods(); } bool CLinuxDMABUFResource::good() { @@ -392,22 +421,20 @@ bool CLinuxDMABUFResource::good() { void CLinuxDMABUFResource::sendMods() { for (auto const& fmt : PROTO::linuxDma->m_formatTable->m_rendererTranche.formats) { - for (auto const& mod : fmt.modifiers) { - if (m_resource->version() < 3) { - if (mod == DRM_FORMAT_MOD_INVALID || mod == DRM_FORMAT_MOD_LINEAR) - m_resource->sendFormat(fmt.drmFormat); - continue; + m_resource->sendFormat(fmt.drmFormat); + + if (m_resource->version() == 3) { + for (auto const& mod : fmt.modifiers) { + // TODO: https://gitlab.freedesktop.org/xorg/xserver/-/issues/1166 + + m_resource->sendModifier(fmt.drmFormat, mod >> 32, mod & 0xFFFFFFFF); } - - // TODO: https://gitlab.freedesktop.org/xorg/xserver/-/issues/1166 - - m_resource->sendModifier(fmt.drmFormat, mod >> 32, mod & 0xFFFFFFFF); } } } CLinuxDMABufV1Protocol::CLinuxDMABufV1Protocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { - static auto P = g_pHookSystem->hookDynamic("ready", [this](void* self, SCallbackInfo& info, std::any d) { + static auto P = Event::bus()->m_events.ready.listen([this] { int rendererFD = g_pCompositor->m_drmRenderNode.fd >= 0 ? g_pCompositor->m_drmRenderNode.fd : g_pCompositor->m_drm.fd; auto dev = devIDFromFD(rendererFD); @@ -440,22 +467,29 @@ CLinuxDMABufV1Protocol::CLinuxDMABufV1Protocol(const wl_interface* iface, const tches.emplace_back(std::make_pair<>(mon, tranche)); } - static auto monitorAdded = g_pHookSystem->hookDynamic("monitorAdded", [this](void* self, SCallbackInfo& info, std::any param) { - auto pMonitor = std::any_cast(param); - auto tranche = SDMABUFTranche{ - .device = m_mainDevice, - .flags = ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_SCANOUT, - .formats = pMonitor->m_output->getRenderFormats(), + static auto monitorAdded = Event::bus()->m_events.monitor.added.listen([this](PHLMONITOR mon) { + auto tranche = SDMABUFTranche{ + .device = m_mainDevice, + .flags = ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_SCANOUT, + .formats = mon->m_output->getRenderFormats(), }; - m_formatTable->m_monitorTranches.emplace_back(std::make_pair<>(pMonitor, tranche)); + m_formatTable->m_monitorTranches.emplace_back(std::make_pair<>(mon, tranche)); resetFormatTable(); }); - static auto monitorRemoved = g_pHookSystem->hookDynamic("monitorRemoved", [this](void* self, SCallbackInfo& info, std::any param) { - auto pMonitor = std::any_cast(param); - std::erase_if(m_formatTable->m_monitorTranches, [pMonitor](std::pair pair) { return pair.first == pMonitor; }); + static auto monitorRemoved = Event::bus()->m_events.monitor.removed.listen([this](PHLMONITOR mon) { + std::erase_if(m_formatTable->m_monitorTranches, [mon](std::pair pair) { return pair.first == mon; }); resetFormatTable(); }); + + static auto configReloaded = Event::bus()->m_events.config.reloaded.listen([this] { + static const auto PSKIP_NON_KMS = CConfigValue("quirks:skip_non_kms_dmabuf_formats"); + static auto prev = *PSKIP_NON_KMS; + if (prev != *PSKIP_NON_KMS) { + prev = *PSKIP_NON_KMS; + resetFormatTable(); + } + }); } m_formatTable = makeUnique(eglTranche, tches); @@ -607,3 +641,7 @@ void CLinuxDMABufV1Protocol::updateScanoutTranche(SP surface feedbackResource->m_lastFeedbackWasScanout = true; } + +dev_t CLinuxDMABufV1Protocol::getMainDevice() { + return m_mainDevice; +} diff --git a/src/protocols/LinuxDMABUF.hpp b/src/protocols/LinuxDMABUF.hpp index 296ef04d..b1d59155 100644 --- a/src/protocols/LinuxDMABUF.hpp +++ b/src/protocols/LinuxDMABUF.hpp @@ -113,6 +113,7 @@ class CLinuxDMABufV1Protocol : public IWaylandProtocol { virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); void updateScanoutTranche(SP surface, PHLMONITOR pMonitor); + dev_t getMainDevice(); private: void destroyResource(CLinuxDMABUFResource* resource); diff --git a/src/protocols/OutputManagement.cpp b/src/protocols/OutputManagement.cpp index 57d39371..f85578e2 100644 --- a/src/protocols/OutputManagement.cpp +++ b/src/protocols/OutputManagement.cpp @@ -2,8 +2,8 @@ #include #include "../Compositor.hpp" #include "../managers/input/InputManager.hpp" -#include "../managers/HookSystemManager.hpp" #include "../config/ConfigManager.hpp" +#include "../event/EventBus.hpp" using namespace Aquamarine; @@ -578,7 +578,7 @@ bool COutputConfigurationHead::good() { } COutputManagementProtocol::COutputManagementProtocol(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) { + static auto P = Event::bus()->m_events.monitor.layoutChanged.listen([this] { updateAllOutputs(); sendPendingSuccessEvents(); }); diff --git a/src/protocols/PresentationTime.cpp b/src/protocols/PresentationTime.cpp index 82c4b1eb..456ad724 100644 --- a/src/protocols/PresentationTime.cpp +++ b/src/protocols/PresentationTime.cpp @@ -1,7 +1,7 @@ #include "PresentationTime.hpp" #include #include "../helpers/Monitor.hpp" -#include "../managers/HookSystemManager.hpp" +#include "../event/EventBus.hpp" #include "core/Compositor.hpp" #include "core/Output.hpp" #include @@ -40,46 +40,45 @@ bool CPresentationFeedback::good() { return m_resource->resource(); } -void CPresentationFeedback::sendQueued(WP data, const Time::steady_tp& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags) { +void CPresentationFeedback::sendQueued(WP data, const timespec& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags) { auto client = m_resource->client(); if LIKELY (PROTO::outputs.contains(data->m_monitor->m_name) && data->m_wasPresented) { - if LIKELY (auto outputResource = PROTO::outputs.at(data->m_monitor->m_name)->outputResourceFrom(client); outputResource) - m_resource->sendSyncOutput(outputResource->getResource()->resource()); + if LIKELY (auto outputResources = PROTO::outputs.at(data->m_monitor->m_name)->outputResourcesFrom(client); !outputResources.empty()) { + for (const auto& r : outputResources) { + m_resource->sendSyncOutput(r->getResource()->resource()); + } + } } - uint32_t flags = 0; - if (!data->m_monitor->m_tearingState.activelyTearing) - flags |= WP_PRESENTATION_FEEDBACK_KIND_VSYNC; - if (data->m_zeroCopy) - flags |= WP_PRESENTATION_FEEDBACK_KIND_ZERO_COPY; - if (reportedFlags & Aquamarine::IOutput::AQ_OUTPUT_PRESENT_HW_CLOCK) - flags |= WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK; - if (reportedFlags & Aquamarine::IOutput::AQ_OUTPUT_PRESENT_HW_COMPLETION) - flags |= WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION; + if (data->m_wasPresented) { + uint32_t flags = 0; + if (!data->m_monitor->m_tearingState.activelyTearing) + flags |= WP_PRESENTATION_FEEDBACK_KIND_VSYNC; + if (data->m_zeroCopy) + flags |= WP_PRESENTATION_FEEDBACK_KIND_ZERO_COPY; + if (reportedFlags & Aquamarine::IOutput::AQ_OUTPUT_PRESENT_HW_CLOCK) + flags |= WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK; + if (reportedFlags & Aquamarine::IOutput::AQ_OUTPUT_PRESENT_HW_COMPLETION) + flags |= WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION; - const auto TIMESPEC = Time::toTimespec(when); + time_t tv_sec = 0; + if (sizeof(time_t) > 4) + tv_sec = when.tv_sec >> 32; - time_t tv_sec = 0; - if (sizeof(time_t) > 4) - tv_sec = TIMESPEC.tv_sec >> 32; + uint32_t refreshNs = m_resource->version() == 1 && data->m_monitor->m_vrrActive && data->m_monitor->m_output->vrrCapable ? 0 : untilRefreshNs; - uint32_t refreshNs = m_resource->version() == 1 && data->m_monitor->m_vrrActive && data->m_monitor->m_output->vrrCapable ? 0 : untilRefreshNs; - - if (data->m_wasPresented) - m_resource->sendPresented(sc(tv_sec), sc(TIMESPEC.tv_sec & 0xFFFFFFFF), sc(TIMESPEC.tv_nsec), refreshNs, sc(seq >> 32), + m_resource->sendPresented(sc(tv_sec), sc(when.tv_sec & 0xFFFFFFFF), sc(when.tv_nsec), refreshNs, sc(seq >> 32), sc(seq & 0xFFFFFFFF), sc(flags)); - else + } else m_resource->sendDiscarded(); m_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 = PHLMONITORREF{std::any_cast(param)}; - std::erase_if(m_queue, [PMONITOR](const auto& other) { return !other->m_surface || other->m_monitor == PMONITOR; }); - }); + static auto P = Event::bus()->m_events.monitor.removed.listen( + [this](PHLMONITOR mon) { std::erase_if(m_queue, [mon](const auto& other) { return !other->m_surface || other->m_monitor == mon; }); }); } void CPresentationProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { @@ -88,6 +87,7 @@ void CPresentationProtocol::bindManager(wl_client* client, void* data, uint32_t RESOURCE->setDestroy([this](CWpPresentation* pMgr) { this->onManagerResourceDestroy(pMgr->resource()); }); RESOURCE->setFeedback([this](CWpPresentation* pMgr, wl_resource* surf, uint32_t id) { this->onGetFeedback(pMgr, surf, id); }); + RESOURCE->sendClockId(CLOCK_MONOTONIC); } void CPresentationProtocol::onManagerResourceDestroy(wl_resource* res) { @@ -110,7 +110,7 @@ void CPresentationProtocol::onGetFeedback(CWpPresentation* pMgr, wl_resource* su } } -void CPresentationProtocol::onPresented(PHLMONITOR pMonitor, const Time::steady_tp& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags) { +void CPresentationProtocol::onPresented(PHLMONITOR pMonitor, const timespec& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags) { for (auto const& feedback : m_feedbacks) { if (!feedback->m_surface) continue; diff --git a/src/protocols/PresentationTime.hpp b/src/protocols/PresentationTime.hpp index c348c175..caf63ace 100644 --- a/src/protocols/PresentationTime.hpp +++ b/src/protocols/PresentationTime.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include "WaylandProtocol.hpp" @@ -37,7 +38,7 @@ class CPresentationFeedback { bool good(); - void sendQueued(WP data, const Time::steady_tp& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags); + void sendQueued(WP data, const timespec& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags); private: UP m_resource; @@ -53,7 +54,7 @@ class CPresentationProtocol : public IWaylandProtocol { virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); - void onPresented(PHLMONITOR pMonitor, const Time::steady_tp& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags); + void onPresented(PHLMONITOR pMonitor, const timespec& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags); void queueData(UP&& data); private: diff --git a/src/protocols/Screencopy.cpp b/src/protocols/Screencopy.cpp index c02b759c..825939ef 100644 --- a/src/protocols/Screencopy.cpp +++ b/src/protocols/Screencopy.cpp @@ -1,431 +1,12 @@ #include "Screencopy.hpp" -#include "../Compositor.hpp" -#include "../managers/eventLoop/EventLoopManager.hpp" -#include "../managers/PointerManager.hpp" -#include "../managers/input/InputManager.hpp" -#include "../managers/EventManager.hpp" -#include "../managers/permissions/DynamicPermissionManager.hpp" -#include "../render/Renderer.hpp" -#include "../render/OpenGL.hpp" -#include "../helpers/Monitor.hpp" +#include "../managers/screenshare/ScreenshareManager.hpp" #include "core/Output.hpp" -#include "types/WLBuffer.hpp" +#include "../render/Renderer.hpp" #include "types/Buffer.hpp" -#include "ColorManagement.hpp" #include "../helpers/Format.hpp" #include "../helpers/time/Time.hpp" -#include "XDGShell.hpp" -#include -#include - -CScreencopyFrame::CScreencopyFrame(SP resource_, int32_t overlay_cursor, wl_resource* output, CBox box_) : m_resource(resource_) { - if UNLIKELY (!good()) - return; - - m_overlayCursor = !!overlay_cursor; - m_monitor = CWLOutputResource::fromResource(output)->m_monitor; - - if (!m_monitor) { - LOGM(Log::ERR, "Client requested sharing of a monitor that doesn't exist"); - m_resource->sendFailed(); - return; - } - - m_resource->setOnDestroy([this](CZwlrScreencopyFrameV1* pMgr) { PROTO::screencopy->destroyResource(this); }); - m_resource->setDestroy([this](CZwlrScreencopyFrameV1* pFrame) { PROTO::screencopy->destroyResource(this); }); - m_resource->setCopy([this](CZwlrScreencopyFrameV1* pFrame, wl_resource* res) { this->copy(pFrame, res); }); - m_resource->setCopyWithDamage([this](CZwlrScreencopyFrameV1* pFrame, wl_resource* res) { - m_withDamage = true; - this->copy(pFrame, res); - }); - - g_pHyprRenderer->makeEGLCurrent(); - - m_shmFormat = g_pHyprOpenGL->getPreferredReadFormat(m_monitor.lock()); - if (m_shmFormat == DRM_FORMAT_INVALID) { - LOGM(Log::ERR, "No format supported by renderer in capture output"); - m_resource->sendFailed(); - return; - } - - // TODO: hack, we can't bit flip so we'll format flip heh, GL_BGRA_EXT won't work here - if (m_shmFormat == DRM_FORMAT_XRGB2101010 || m_shmFormat == DRM_FORMAT_ARGB2101010) - m_shmFormat = DRM_FORMAT_XBGR2101010; - - const auto PSHMINFO = NFormatUtils::getPixelFormatFromDRM(m_shmFormat); - if (!PSHMINFO) { - LOGM(Log::ERR, "No pixel format supported by renderer in capture output"); - m_resource->sendFailed(); - return; - } - - m_dmabufFormat = g_pHyprOpenGL->getPreferredReadFormat(m_monitor.lock()); - - if (box_.width == 0 && box_.height == 0) - m_box = {0, 0, sc(m_monitor->m_size.x), sc(m_monitor->m_size.y)}; - else - m_box = box_; - - const auto POS = m_box.pos() * m_monitor->m_scale; - m_box.transform(Math::wlTransformToHyprutils(m_monitor->m_transform), m_monitor->m_pixelSize.x, m_monitor->m_pixelSize.y).scale(m_monitor->m_scale).round(); - m_box.x = POS.x; - m_box.y = POS.y; - - m_shmStride = NFormatUtils::minStride(PSHMINFO, m_box.w); - - m_resource->sendBuffer(NFormatUtils::drmToShm(m_shmFormat), m_box.width, m_box.height, m_shmStride); - - if (m_resource->version() >= 3) { - if LIKELY (m_dmabufFormat != DRM_FORMAT_INVALID) - m_resource->sendLinuxDmabuf(m_dmabufFormat, m_box.width, m_box.height); - - m_resource->sendBufferDone(); - } -} - -void CScreencopyFrame::copy(CZwlrScreencopyFrameV1* pFrame, wl_resource* buffer_) { - if UNLIKELY (!good()) { - LOGM(Log::ERR, "No frame in copyFrame??"); - return; - } - - if UNLIKELY (!g_pCompositor->monitorExists(m_monitor.lock())) { - LOGM(Log::ERR, "Client requested sharing of a monitor that is gone"); - m_resource->sendFailed(); - return; - } - - const auto PBUFFER = CWLBufferResource::fromResource(buffer_); - if UNLIKELY (!PBUFFER) { - LOGM(Log::ERR, "Invalid buffer in {:x}", (uintptr_t)this); - m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer"); - PROTO::screencopy->destroyResource(this); - return; - } - - if UNLIKELY (PBUFFER->m_buffer->size != m_box.size()) { - LOGM(Log::ERR, "Invalid dimensions in {:x}", (uintptr_t)this); - m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer dimensions"); - PROTO::screencopy->destroyResource(this); - return; - } - - if UNLIKELY (m_buffer) { - LOGM(Log::ERR, "Buffer used in {:x}", (uintptr_t)this); - m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_ALREADY_USED, "frame already used"); - PROTO::screencopy->destroyResource(this); - return; - } - - if (auto attrs = PBUFFER->m_buffer->dmabuf(); attrs.success) { - m_bufferDMA = true; - - if (attrs.format != m_dmabufFormat) { - LOGM(Log::ERR, "Invalid buffer dma format in {:x}", (uintptr_t)pFrame); - m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer format"); - PROTO::screencopy->destroyResource(this); - return; - } - } else if (auto attrs = PBUFFER->m_buffer->shm(); attrs.success) { - if (attrs.format != m_shmFormat) { - LOGM(Log::ERR, "Invalid buffer shm format in {:x}", (uintptr_t)pFrame); - m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer format"); - PROTO::screencopy->destroyResource(this); - return; - } else if (attrs.stride != m_shmStride) { - LOGM(Log::ERR, "Invalid buffer shm stride in {:x}", (uintptr_t)pFrame); - m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer stride"); - PROTO::screencopy->destroyResource(this); - return; - } - } else { - LOGM(Log::ERR, "Invalid buffer type in {:x}", (uintptr_t)pFrame); - m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer type"); - PROTO::screencopy->destroyResource(this); - return; - } - - m_buffer = CHLBufferReference(PBUFFER->m_buffer.lock()); - - PROTO::screencopy->m_framesAwaitingWrite.emplace_back(m_self); - - g_pHyprRenderer->m_directScanoutBlocked = true; - - if (!m_withDamage) - g_pHyprRenderer->damageMonitor(m_monitor.lock()); -} - -void CScreencopyFrame::share() { - if (!m_buffer || !m_monitor) - return; - - const auto NOW = Time::steadyNow(); - - auto callback = [this, NOW, weak = m_self](bool success) { - if (weak.expired()) - return; - - if (!success) { - LOGM(Log::ERR, "{} copy failed in {:x}", m_bufferDMA ? "Dmabuf" : "Shm", (uintptr_t)this); - m_resource->sendFailed(); - return; - } - - m_resource->sendFlags(sc(0)); - if (m_withDamage) { - // TODO: add a damage ring for this. - m_resource->sendDamage(0, 0, m_buffer->size.x, m_buffer->size.y); - } - - const auto [sec, nsec] = Time::secNsec(NOW); - - uint32_t tvSecHi = (sizeof(sec) > 4) ? sec >> 32 : 0; - uint32_t tvSecLo = sec & 0xFFFFFFFF; - m_resource->sendReady(tvSecHi, tvSecLo, nsec); - }; - - if (m_bufferDMA) - copyDmabuf(callback); - else - callback(copyShm()); -} - -void CScreencopyFrame::renderMon() { - auto TEXTURE = makeShared(m_monitor->m_output->state->state().buffer); - - CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX}; - - const bool IS_CM_AWARE = PROTO::colorManagement && PROTO::colorManagement->isClientCMAware(m_client->client()); - - CBox monbox = CBox{0, 0, m_monitor->m_pixelSize.x, m_monitor->m_pixelSize.y} - .translate({-m_box.x, -m_box.y}) // vvvv kinda ass-backwards but that's how I designed the renderer... sigh. - .transform(Math::wlTransformToHyprutils(Math::invertTransform(m_monitor->m_transform)), m_monitor->m_pixelSize.x, m_monitor->m_pixelSize.y); - g_pHyprOpenGL->pushMonitorTransformEnabled(true); - g_pHyprOpenGL->setRenderModifEnabled(false); - g_pHyprOpenGL->renderTexture(TEXTURE, monbox, - { - .cmBackToSRGB = !IS_CM_AWARE, - .cmBackToSRGBSource = !IS_CM_AWARE ? m_monitor.lock() : nullptr, - }); - g_pHyprOpenGL->setRenderModifEnabled(true); - g_pHyprOpenGL->popMonitorTransformEnabled(); - - auto hidePopups = [&](Vector2D popupBaseOffset) { - return [&, popupBaseOffset](WP popup, void*) { - if (!popup->wlSurface() || !popup->wlSurface()->resource() || !popup->visible()) - return; - - const auto popRel = popup->coordsRelativeToParent(); - popup->wlSurface()->resource()->breadthfirst( - [&](SP surf, const Vector2D& localOff, void*) { - const auto size = surf->m_current.size; - const auto surfBox = CBox{popupBaseOffset + popRel + localOff, size}.translate(m_monitor->m_position).scale(m_monitor->m_scale).translate(-m_box.pos()); - - if LIKELY (surfBox.w > 0 && surfBox.h > 0) - g_pHyprOpenGL->renderRect(surfBox, Colors::BLACK, {}); - }, - nullptr); - }; - }; - - for (auto const& l : g_pCompositor->m_layers) { - if (!l->m_ruleApplicator->noScreenShare().valueOrDefault()) - continue; - - if UNLIKELY (!l->visible()) - continue; - - const auto REALPOS = l->m_realPosition->value(); - const auto REALSIZE = l->m_realSize->value(); - - const auto noScreenShareBox = - CBox{REALPOS.x, REALPOS.y, std::max(REALSIZE.x, 5.0), std::max(REALSIZE.y, 5.0)}.translate(-m_monitor->m_position).scale(m_monitor->m_scale).translate(-m_box.pos()); - - g_pHyprOpenGL->renderRect(noScreenShareBox, Colors::BLACK, {}); - - const auto geom = l->m_geometry; - const Vector2D popupBaseOffset = REALPOS - Vector2D{geom.pos().x, geom.pos().y}; - if (l->m_popupHead) - l->m_popupHead->breadthfirst(hidePopups(popupBaseOffset), nullptr); - } - - for (auto const& w : g_pCompositor->m_windows) { - if (!w->m_ruleApplicator->noScreenShare().valueOrDefault()) - continue; - - if (!g_pHyprRenderer->shouldRenderWindow(w, m_monitor.lock())) - continue; - - if (w->isHidden()) - continue; - - const auto PWORKSPACE = w->m_workspace; - - if UNLIKELY (!PWORKSPACE && !w->m_fadingOut && w->m_alpha->value() != 0.f) - continue; - - const auto renderOffset = PWORKSPACE && !w->m_pinned ? PWORKSPACE->m_renderOffset->value() : Vector2D{}; - const auto REALPOS = w->m_realPosition->value() + renderOffset; - const auto noScreenShareBox = CBox{REALPOS.x, REALPOS.y, std::max(w->m_realSize->value().x, 5.0), std::max(w->m_realSize->value().y, 5.0)} - .translate(-m_monitor->m_position) - .scale(m_monitor->m_scale) - .translate(-m_box.pos()); - - const auto dontRound = w->isEffectiveInternalFSMode(FSMODE_FULLSCREEN); - const auto rounding = dontRound ? 0 : w->rounding() * m_monitor->m_scale; - const auto roundingPower = dontRound ? 2.0f : w->roundingPower(); - - g_pHyprOpenGL->renderRect(noScreenShareBox, Colors::BLACK, {.round = rounding, .roundingPower = roundingPower}); - - if (w->m_isX11 || !w->m_popupHead) - continue; - - const auto geom = w->m_xdgSurface->m_current.geometry; - const Vector2D popupBaseOffset = REALPOS - Vector2D{geom.pos().x, geom.pos().y}; - - w->m_popupHead->breadthfirst(hidePopups(popupBaseOffset), nullptr); - } - - if (m_overlayCursor) - g_pPointerManager->renderSoftwareCursorsFor(m_monitor.lock(), Time::steadyNow(), fakeDamage, - g_pInputManager->getMouseCoordsInternal() - m_monitor->m_position - m_box.pos() / m_monitor->m_scale, true); -} - -void CScreencopyFrame::storeTempFB() { - g_pHyprRenderer->makeEGLCurrent(); - - m_tempFb.alloc(m_box.w, m_box.h); - - CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX}; - - if (!g_pHyprRenderer->beginRender(m_monitor.lock(), fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, &m_tempFb, true)) { - LOGM(Log::ERR, "Can't copy: failed to begin rendering to temp fb"); - return; - } - - renderMon(); - - g_pHyprRenderer->endRender(); -} - -void CScreencopyFrame::copyDmabuf(std::function callback) { - const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_resource->client(), PERMISSION_TYPE_SCREENCOPY); - - CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX}; - - if (!g_pHyprRenderer->beginRender(m_monitor.lock(), fakeDamage, RENDER_MODE_TO_BUFFER, m_buffer.m_buffer, nullptr, true)) { - LOGM(Log::ERR, "Can't copy: failed to begin rendering to dma frame"); - callback(false); - return; - } - - if (PERM == PERMISSION_RULE_ALLOW_MODE_ALLOW) { - if (m_tempFb.isAllocated()) { - CBox texbox = {{}, m_box.size()}; - g_pHyprOpenGL->renderTexture(m_tempFb.getTexture(), texbox, {}); - m_tempFb.release(); - } else - renderMon(); - } else if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING) - g_pHyprOpenGL->clear(Colors::BLACK); - else { - g_pHyprOpenGL->clear(Colors::BLACK); - CBox texbox = CBox{m_monitor->m_transformedSize / 2.F, g_pHyprOpenGL->m_screencopyDeniedTexture->m_size}.translate(-g_pHyprOpenGL->m_screencopyDeniedTexture->m_size / 2.F); - g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_screencopyDeniedTexture, texbox, {}); - } - - g_pHyprOpenGL->m_renderData.blockScreenShader = true; - - g_pHyprRenderer->endRender([callback]() { - LOGM(Log::TRACE, "Copied frame via dma"); - callback(true); - }); -} - -bool CScreencopyFrame::copyShm() { - const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_resource->client(), PERMISSION_TYPE_SCREENCOPY); - - auto shm = m_buffer->shm(); - auto [pixelData, fmt, bufLen] = m_buffer->beginDataPtr(0); // no need for end, cuz it's shm - - CRegion fakeDamage = {0, 0, INT16_MAX, INT16_MAX}; - - g_pHyprRenderer->makeEGLCurrent(); - - CFramebuffer fb; - fb.alloc(m_box.w, m_box.h, m_monitor->m_output->state->state().drmFormat); - - if (!g_pHyprRenderer->beginRender(m_monitor.lock(), fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, &fb, true)) { - LOGM(Log::ERR, "Can't copy: failed to begin rendering"); - return false; - } - - if (PERM == PERMISSION_RULE_ALLOW_MODE_ALLOW) { - if (m_tempFb.isAllocated()) { - CBox texbox = {{}, m_box.size()}; - g_pHyprOpenGL->renderTexture(m_tempFb.getTexture(), texbox, {}); - m_tempFb.release(); - } else - renderMon(); - } else if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING) - g_pHyprOpenGL->clear(Colors::BLACK); - else { - g_pHyprOpenGL->clear(Colors::BLACK); - CBox texbox = CBox{m_monitor->m_transformedSize / 2.F, g_pHyprOpenGL->m_screencopyDeniedTexture->m_size}.translate(-g_pHyprOpenGL->m_screencopyDeniedTexture->m_size / 2.F); - g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_screencopyDeniedTexture, texbox, {}); - } - - glBindFramebuffer(GL_READ_FRAMEBUFFER, fb.getFBID()); - - const auto PFORMAT = NFormatUtils::getPixelFormatFromDRM(shm.format); - if (!PFORMAT) { - LOGM(Log::ERR, "Can't copy: failed to find a pixel format"); - g_pHyprRenderer->endRender(); - return false; - } - - auto glFormat = PFORMAT->flipRB ? GL_BGRA_EXT : GL_RGBA; - - g_pHyprOpenGL->m_renderData.blockScreenShader = true; - g_pHyprRenderer->endRender(); - - g_pHyprRenderer->makeEGLCurrent(); - g_pHyprOpenGL->m_renderData.pMonitor = m_monitor; - fb.bind(); - - glPixelStorei(GL_PACK_ALIGNMENT, 1); - - const auto drmFmt = NFormatUtils::getPixelFormatFromDRM(shm.format); - uint32_t packStride = NFormatUtils::minStride(drmFmt, m_box.w); - - // This could be optimized by using a pixel buffer object to make this async, - // but really clients should just use a dma buffer anyways. - if (packStride == sc(shm.stride)) { - glReadPixels(0, 0, m_box.w, m_box.h, glFormat, PFORMAT->glType, pixelData); - } else { - for (size_t i = 0; i < m_box.h; ++i) { - uint32_t y = i; - glReadPixels(0, y, m_box.w, 1, glFormat, PFORMAT->glType, pixelData + i * shm.stride); - } - } - - g_pHyprOpenGL->m_renderData.pMonitor.reset(); - - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - - LOGM(Log::TRACE, "Copied frame via shm"); - - return true; -} - -bool CScreencopyFrame::good() { - return m_resource->resource(); -} - -CScreencopyClient::~CScreencopyClient() { - g_pHookSystem->unhook(m_tickCallback); -} +using namespace Screenshare; CScreencopyClient::CScreencopyClient(SP resource_) : m_resource(resource_) { if UNLIKELY (!good()) @@ -434,18 +15,27 @@ CScreencopyClient::CScreencopyClient(SP resource_) : m m_resource->setDestroy([this](CZwlrScreencopyManagerV1* pMgr) { PROTO::screencopy->destroyResource(this); }); m_resource->setOnDestroy([this](CZwlrScreencopyManagerV1* pMgr) { PROTO::screencopy->destroyResource(this); }); m_resource->setCaptureOutput( - [this](CZwlrScreencopyManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, wl_resource* output) { this->captureOutput(frame, overlayCursor, output, {}); }); + [this](CZwlrScreencopyManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, wl_resource* output) { captureOutput(frame, overlayCursor, output, {}); }); m_resource->setCaptureOutputRegion([this](CZwlrScreencopyManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, wl_resource* output, int32_t x, int32_t y, int32_t w, - int32_t h) { this->captureOutput(frame, overlayCursor, output, {x, y, w, h}); }); + int32_t h) { captureOutput(frame, overlayCursor, output, {x, y, w, h}); }); - m_lastMeasure.reset(); - m_lastFrame.reset(); - m_tickCallback = g_pHookSystem->hookDynamic("tick", [&](void* self, SCallbackInfo& info, std::any data) { onTick(); }); + m_savedClient = m_resource->client(); } void CScreencopyClient::captureOutput(uint32_t frame, int32_t overlayCursor_, wl_resource* output, CBox box) { + const auto PMONITORRES = CWLOutputResource::fromResource(output); + if (!PMONITORRES || !PMONITORRES->m_monitor) { + LOGM(Log::ERR, "Tried to capture invalid output/monitor in {:x}", (uintptr_t)this); + m_resource->error(-1, "invalid output"); + return; + } + + const auto PMONITOR = PMONITORRES->m_monitor.lock(); + auto session = box.w == 0 && box.h == 0 ? Screenshare::mgr()->getManagedSession(m_resource->client(), PMONITOR) : + Screenshare::mgr()->getManagedSession(m_resource->client(), PMONITOR, box); + const auto FRAME = PROTO::screencopy->m_frames.emplace_back( - makeShared(makeShared(m_resource->client(), m_resource->version(), frame), overlayCursor_, output, box)); + makeShared(makeShared(m_resource->client(), m_resource->version(), frame), session, !!overlayCursor_)); if (!FRAME->good()) { LOGM(Log::ERR, "Couldn't alloc frame for sharing! (no memory)"); @@ -454,38 +44,122 @@ void CScreencopyClient::captureOutput(uint32_t frame, int32_t overlayCursor_, wl return; } - FRAME->m_self = FRAME; FRAME->m_client = m_self; -} - -void CScreencopyClient::onTick() { - if (m_lastMeasure.getMillis() < 500) - return; - - m_framesInLastHalfSecond = m_frameCounter; - m_frameCounter = 0; - m_lastMeasure.reset(); - - const auto LASTFRAMEDELTA = m_lastFrame.getMillis() / 1000.0; - const bool FRAMEAWAITING = std::ranges::any_of(PROTO::screencopy->m_frames, [&](const auto& frame) { return frame->m_client.get() == this; }); - - if (m_framesInLastHalfSecond > 3 && !m_sentScreencast) { - EMIT_HOOK_EVENT("screencast", (std::vector{1, sc(m_framesInLastHalfSecond), sc(m_clientOwner)})); - g_pEventManager->postEvent(SHyprIPCEvent{"screencast", "1," + std::to_string(m_clientOwner)}); - m_sentScreencast = true; - } else if (m_framesInLastHalfSecond < 4 && m_sentScreencast && LASTFRAMEDELTA > 1.0 && !FRAMEAWAITING) { - EMIT_HOOK_EVENT("screencast", (std::vector{0, sc(m_framesInLastHalfSecond), sc(m_clientOwner)})); - g_pEventManager->postEvent(SHyprIPCEvent{"screencast", "0," + std::to_string(m_clientOwner)}); - m_sentScreencast = false; - } + FRAME->m_self = FRAME; } bool CScreencopyClient::good() { - return m_resource->resource(); + return m_resource && m_resource->resource(); } -wl_client* CScreencopyClient::client() { - return m_resource ? m_resource->client() : nullptr; +CScreencopyFrame::CScreencopyFrame(SP resource_, WP session, bool overlayCursor) : + m_resource(resource_), m_session(session), m_overlayCursor(overlayCursor) { + if UNLIKELY (!good()) + return; + + m_resource->setOnDestroy([this](CZwlrScreencopyFrameV1* pMgr) { PROTO::screencopy->destroyResource(this); }); + m_resource->setDestroy([this](CZwlrScreencopyFrameV1* pFrame) { PROTO::screencopy->destroyResource(this); }); + m_resource->setCopy([this](CZwlrScreencopyFrameV1* pFrame, wl_resource* res) { shareFrame(pFrame, res, false); }); + m_resource->setCopyWithDamage([this](CZwlrScreencopyFrameV1* pFrame, wl_resource* res) { shareFrame(pFrame, res, true); }); + + m_frame = m_session->nextFrame(overlayCursor); + + auto formats = m_session->allowedFormats(); + if (formats.empty()) { + LOGM(Log::ERR, "No format supported by renderer in screencopy protocol"); + m_resource->sendFailed(); + return; + } + + DRMFormat format = formats.at(0); + auto bufSize = m_frame->bufferSize(); + + const auto PSHMINFO = NFormatUtils::getPixelFormatFromDRM(format); + + if (!PSHMINFO) { + LOGM(Log::ERR, "No pixel format for drm format"); + m_resource->sendFailed(); + return; + } + + const auto stride = NFormatUtils::minStride(PSHMINFO, bufSize.x); + m_resource->sendBuffer(NFormatUtils::drmToShm(format), bufSize.x, bufSize.y, stride); + + if (m_resource->version() >= 3) { + if LIKELY (format != DRM_FORMAT_INVALID) + m_resource->sendLinuxDmabuf(format, bufSize.x, bufSize.y); + + m_resource->sendBufferDone(); + } +} + +void CScreencopyFrame::shareFrame(CZwlrScreencopyFrameV1* pFrame, wl_resource* buffer, bool withDamage) { + if UNLIKELY (!good()) { + LOGM(Log::ERR, "No frame in shareFrame??"); + return; + } + + if UNLIKELY (m_session.expired() || !m_session->monitor()) { + LOGM(Log::ERR, "Session stopped for frame {:x}", (uintptr_t)this); + m_resource->sendFailed(); + return; + } + + if UNLIKELY (m_buffer) { + LOGM(Log::ERR, "Buffer used in {:x}", (uintptr_t)this); + m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_ALREADY_USED, "frame already used"); + m_resource->sendFailed(); + return; + } + + const auto PBUFFERRES = CWLBufferResource::fromResource(buffer); + if UNLIKELY (!PBUFFERRES || !PBUFFERRES->m_buffer) { + LOGM(Log::ERR, "Invalid buffer in {:x}", (uintptr_t)this); + m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer"); + m_resource->sendFailed(); + return; + } + + const auto& PBUFFER = PBUFFERRES->m_buffer.lock(); + + if (!withDamage) + g_pHyprRenderer->damageMonitor(m_session->monitor()); + + auto error = m_frame->share(PBUFFER, {}, [this, withDamage, self = m_self](eScreenshareResult result) { + if (self.expired() || !good()) + return; + switch (result) { + case RESULT_COPIED: { + m_resource->sendFlags(sc(0)); + if (withDamage) + m_frame->damage().forEachRect([&](const auto& rect) { m_resource->sendDamage(rect.x1, rect.y1, rect.x2 - rect.x1, rect.y2 - rect.y1); }); + + const auto [sec, nsec] = Time::secNsec(m_timestamp); + uint32_t tvSecHi = (sizeof(sec) > 4) ? sec >> 32 : 0; + uint32_t tvSecLo = sec & 0xFFFFFFFF; + m_resource->sendReady(tvSecHi, tvSecLo, nsec); + break; + } + case RESULT_NOT_COPIED: + LOGM(Log::ERR, "Frame share failed in {:x}", (uintptr_t)this); + m_resource->sendFailed(); + break; + case RESULT_TIMESTAMP: m_timestamp = Time::steadyNow(); break; + } + }); + + switch (error) { + case ERROR_NONE: m_buffer = CHLBufferReference(PBUFFER); break; + case ERROR_NO_BUFFER: m_resource->error(ZWLR_SCREENCOPY_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer"); break; + case ERROR_BUFFER_SIZE: + case ERROR_BUFFER_FORMAT: m_resource->sendFailed(); break; + case ERROR_UNKNOWN: + case ERROR_STOPPED: m_resource->sendFailed(); break; + } +} + +bool CScreencopyFrame::good() { + return m_resource && m_resource->resource(); } CScreencopyProtocol::CScreencopyProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { @@ -508,64 +182,10 @@ void CScreencopyProtocol::bindManager(wl_client* client, void* data, uint32_t ve } void CScreencopyProtocol::destroyResource(CScreencopyClient* client) { - std::erase_if(m_clients, [&](const auto& other) { return other.get() == client; }); std::erase_if(m_frames, [&](const auto& other) { return other->m_client.get() == client; }); - std::erase_if(m_framesAwaitingWrite, [&](const auto& other) { return !other || other->m_client.get() == client; }); + std::erase_if(m_clients, [&](const auto& other) { return other.get() == client; }); } void CScreencopyProtocol::destroyResource(CScreencopyFrame* frame) { std::erase_if(m_frames, [&](const auto& other) { return other.get() == frame; }); - std::erase_if(m_framesAwaitingWrite, [&](const auto& other) { return !other || other.get() == frame; }); -} - -void CScreencopyProtocol::onOutputCommit(PHLMONITOR pMonitor) { - if (m_framesAwaitingWrite.empty()) { - for (auto client : m_clients) { - if (client->m_framesInLastHalfSecond > 0) - return; - } - g_pHyprRenderer->m_directScanoutBlocked = false; - return; // nothing to share - } - - std::vector> framesToRemove; - // reserve number of elements to avoid reallocations - framesToRemove.reserve(m_framesAwaitingWrite.size()); - - // share frame if correct output - for (auto const& f : m_framesAwaitingWrite) { - if (!f) - continue; - - // check permissions - const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(f->m_resource->client(), PERMISSION_TYPE_SCREENCOPY); - - if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING) { - if (!f->m_tempFb.isAllocated()) - f->storeTempFB(); // make a snapshot before the popup - - continue; // pending an answer, don't do anything yet. - } - - // otherwise share. If it's denied, it will be black. - - if (!f->m_monitor || !f->m_buffer) { - framesToRemove.emplace_back(f); - continue; - } - - if (f->m_monitor != pMonitor) - continue; - - f->share(); - - f->m_client->m_lastFrame.reset(); - ++f->m_client->m_frameCounter; - - framesToRemove.emplace_back(f); - } - - for (auto const& f : framesToRemove) { - std::erase(m_framesAwaitingWrite, f); - } } diff --git a/src/protocols/Screencopy.hpp b/src/protocols/Screencopy.hpp index 54b0d28c..b73c090d 100644 --- a/src/protocols/Screencopy.hpp +++ b/src/protocols/Screencopy.hpp @@ -1,50 +1,32 @@ #pragma once -#include "../defines.hpp" -#include "./types/Buffer.hpp" -#include "wlr-screencopy-unstable-v1.hpp" #include "WaylandProtocol.hpp" +#include "wlr-screencopy-unstable-v1.hpp" -#include -#include -#include "../managers/HookSystemManager.hpp" -#include "../helpers/time/Timer.hpp" #include "../helpers/time/Time.hpp" -#include "../render/Framebuffer.hpp" -#include "../managers/eventLoop/EventLoopTimer.hpp" +#include "./types/Buffer.hpp" #include +#include + class CMonitor; class IHLBuffer; - -enum eClientOwners { - CLIENT_SCREENCOPY = 0, - CLIENT_TOPLEVEL_EXPORT +namespace Screenshare { + class CScreenshareSession; + class CScreenshareFrame; }; class CScreencopyClient { public: CScreencopyClient(SP resource_); - ~CScreencopyClient(); - bool good(); - wl_client* client(); - - WP m_self; - eClientOwners m_clientOwner = CLIENT_SCREENCOPY; - - CTimer m_lastFrame; - int m_frameCounter = 0; + bool good(); private: SP m_resource; + WP m_self; - int m_framesInLastHalfSecond = 0; - CTimer m_lastMeasure; - bool m_sentScreencast = false; - - SP m_tickCallback; - void onTick(); + wl_client* m_savedClient = nullptr; void captureOutput(uint32_t frame, int32_t overlayCursor, wl_resource* output, CBox box); @@ -53,38 +35,27 @@ class CScreencopyClient { class CScreencopyFrame { public: - CScreencopyFrame(SP resource, int32_t overlay_cursor, wl_resource* output, CBox box); + CScreencopyFrame(SP resource, WP session, bool overlayCursor); - bool good(); - - WP m_self; - WP m_client; + bool good(); private: - SP m_resource; + SP m_resource; + WP m_self; + WP m_client; - PHLMONITORREF m_monitor; - bool m_overlayCursor = false; - bool m_withDamage = false; + WP m_session; + UP m_frame; - CHLBufferReference m_buffer; - bool m_bufferDMA = false; - uint32_t m_shmFormat = 0; - uint32_t m_dmabufFormat = 0; - int m_shmStride = 0; - CBox m_box = {}; + CHLBufferReference m_buffer; + Time::steady_tp m_timestamp; + bool m_overlayCursor = true; - // if we have a pending perm, hold the buffer. - CFramebuffer m_tempFb; - - void copy(CZwlrScreencopyFrameV1* pFrame, wl_resource* buffer); - void copyDmabuf(std::function callback); - bool copyShm(); - void renderMon(); - void storeTempFB(); - void share(); + // + void shareFrame(CZwlrScreencopyFrameV1* pFrame, wl_resource* buffer, bool withDamage); friend class CScreencopyProtocol; + friend class CScreencopyClient; }; class CScreencopyProtocol : public IWaylandProtocol { @@ -95,21 +66,10 @@ class CScreencopyProtocol : public IWaylandProtocol { void destroyResource(CScreencopyClient* resource); void destroyResource(CScreencopyFrame* resource); - void onOutputCommit(PHLMONITOR pMonitor); - private: std::vector> m_frames; - std::vector> m_framesAwaitingWrite; std::vector> m_clients; - void shareAllFrames(PHLMONITOR pMonitor); - void shareFrame(CScreencopyFrame* frame); - void sendFrameDamage(CScreencopyFrame* frame); - bool copyFrameDmabuf(CScreencopyFrame* frame); - bool copyFrameShm(CScreencopyFrame* frame, const Time::steady_tp& now); - - uint32_t drmFormatForMonitor(PHLMONITOR pMonitor); - friend class CScreencopyFrame; friend class CScreencopyClient; }; diff --git a/src/protocols/SinglePixel.cpp b/src/protocols/SinglePixel.cpp index 51c3551c..c32379a3 100644 --- a/src/protocols/SinglePixel.cpp +++ b/src/protocols/SinglePixel.cpp @@ -12,11 +12,11 @@ CSinglePixelBuffer::CSinglePixelBuffer(uint32_t id, wl_client* client, CHyprColo m_opaque = col_.a >= 1.F; - m_texture = makeShared(DRM_FORMAT_ARGB8888, rc(&m_color), 4, Vector2D{1, 1}); + m_texture = g_pHyprRenderer->createTexture(DRM_FORMAT_ARGB8888, rc(&m_color), 4, Vector2D{1, 1}); m_resource = CWLBufferResource::create(makeShared(client, 1, id)); - m_success = m_texture->m_texID; + m_success = m_texture->ok(); size = {1, 1}; diff --git a/src/protocols/TearingControl.cpp b/src/protocols/TearingControl.cpp index 685c84a7..3fd346a7 100644 --- a/src/protocols/TearingControl.cpp +++ b/src/protocols/TearingControl.cpp @@ -1,13 +1,12 @@ #include "TearingControl.hpp" #include "../managers/ProtocolManager.hpp" #include "../desktop/view/Window.hpp" +#include "../event/EventBus.hpp" #include "../Compositor.hpp" #include "core/Compositor.hpp" -#include "../managers/HookSystemManager.hpp" CTearingControlProtocol::CTearingControlProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { - static auto P = - g_pHookSystem->hookDynamic("destroyWindow", [this](void* self, SCallbackInfo& info, std::any param) { this->onWindowDestroy(std::any_cast(param)); }); + static auto P = Event::bus()->m_events.window.destroy.listen([this](PHLWINDOW window) { onWindowDestroy(window); }); } void CTearingControlProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { diff --git a/src/protocols/ToplevelExport.cpp b/src/protocols/ToplevelExport.cpp index 9a97f934..d7ba7519 100644 --- a/src/protocols/ToplevelExport.cpp +++ b/src/protocols/ToplevelExport.cpp @@ -1,19 +1,14 @@ #include "ToplevelExport.hpp" #include "../Compositor.hpp" #include "ForeignToplevelWlr.hpp" -#include "../managers/PointerManager.hpp" -#include "../managers/SeatManager.hpp" -#include "types/WLBuffer.hpp" -#include "types/Buffer.hpp" +#include "../managers/screenshare/ScreenshareManager.hpp" #include "../helpers/Format.hpp" -#include "../managers/EventManager.hpp" -#include "../managers/input/InputManager.hpp" -#include "../managers/permissions/DynamicPermissionManager.hpp" #include "../render/Renderer.hpp" -#include #include +using namespace Screenshare; + CToplevelExportClient::CToplevelExportClient(SP resource_) : m_resource(resource_) { if UNLIKELY (!good()) return; @@ -21,21 +16,21 @@ CToplevelExportClient::CToplevelExportClient(SPsetOnDestroy([this](CHyprlandToplevelExportManagerV1* pMgr) { PROTO::toplevelExport->destroyResource(this); }); m_resource->setDestroy([this](CHyprlandToplevelExportManagerV1* pMgr) { PROTO::toplevelExport->destroyResource(this); }); m_resource->setCaptureToplevel([this](CHyprlandToplevelExportManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, uint32_t handle) { - this->captureToplevel(pMgr, frame, overlayCursor, g_pCompositor->getWindowFromHandle(handle)); + captureToplevel(frame, overlayCursor, g_pCompositor->getWindowFromHandle(handle)); }); m_resource->setCaptureToplevelWithWlrToplevelHandle([this](CHyprlandToplevelExportManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, wl_resource* handle) { - this->captureToplevel(pMgr, frame, overlayCursor, PROTO::foreignToplevelWlr->windowFromHandleResource(handle)); + captureToplevel(frame, overlayCursor, PROTO::foreignToplevelWlr->windowFromHandleResource(handle)); }); - m_lastMeasure.reset(); - m_lastFrame.reset(); - m_tickCallback = g_pHookSystem->hookDynamic("tick", [&](void* self, SCallbackInfo& info, std::any data) { onTick(); }); + m_savedClient = m_resource->client(); } -void CToplevelExportClient::captureToplevel(CHyprlandToplevelExportManagerV1* pMgr, uint32_t frame, int32_t overlayCursor_, PHLWINDOW handle) { +void CToplevelExportClient::captureToplevel(uint32_t frame, int32_t overlayCursor_, PHLWINDOW handle) { + auto session = Screenshare::mgr()->getManagedSession(m_resource->client(), handle); + // create a frame const auto FRAME = PROTO::toplevelExport->m_frames.emplace_back( - makeShared(makeShared(m_resource->client(), m_resource->version(), frame), overlayCursor_, handle)); + makeShared(makeShared(m_resource->client(), m_resource->version(), frame), session, !!overlayCursor_)); if UNLIKELY (!FRAME->good()) { LOGM(Log::ERR, "Couldn't alloc frame for sharing! (no memory)"); @@ -44,349 +39,116 @@ void CToplevelExportClient::captureToplevel(CHyprlandToplevelExportManagerV1* pM return; } - FRAME->m_self = FRAME; FRAME->m_client = m_self; -} - -void CToplevelExportClient::onTick() { - if (m_lastMeasure.getMillis() < 500) - return; - - m_framesInLastHalfSecond = m_frameCounter; - m_frameCounter = 0; - m_lastMeasure.reset(); - - const auto LASTFRAMEDELTA = m_lastFrame.getMillis() / 1000.0; - const bool FRAMEAWAITING = std::ranges::any_of(PROTO::toplevelExport->m_frames, [&](const auto& frame) { return frame->m_client.get() == this; }); - - if (m_framesInLastHalfSecond > 3 && !m_sentScreencast) { - EMIT_HOOK_EVENT("screencast", (std::vector{1, sc(m_framesInLastHalfSecond), sc(m_clientOwner)})); - g_pEventManager->postEvent(SHyprIPCEvent{"screencast", "1," + std::to_string(m_clientOwner)}); - m_sentScreencast = true; - } else if (m_framesInLastHalfSecond < 4 && m_sentScreencast && LASTFRAMEDELTA > 1.0 && !FRAMEAWAITING) { - EMIT_HOOK_EVENT("screencast", (std::vector{0, sc(m_framesInLastHalfSecond), sc(m_clientOwner)})); - g_pEventManager->postEvent(SHyprIPCEvent{"screencast", "0," + std::to_string(m_clientOwner)}); - m_sentScreencast = false; - } + FRAME->m_self = FRAME; } bool CToplevelExportClient::good() { - return m_resource->resource(); + return m_resource && m_resource->resource(); } -CToplevelExportFrame::CToplevelExportFrame(SP resource_, int32_t overlayCursor_, PHLWINDOW pWindow_) : m_resource(resource_), m_window(pWindow_) { +CToplevelExportFrame::CToplevelExportFrame(SP resource_, WP session, bool overlayCursor) : + m_resource(resource_), m_session(session) { if UNLIKELY (!good()) return; - m_cursorOverlayRequested = !!overlayCursor_; - - if UNLIKELY (!m_window) { - LOGM(Log::ERR, "Client requested sharing of window handle {:x} which does not exist!", m_window); - m_resource->sendFailed(); - return; - } - - if UNLIKELY (!m_window->m_isMapped) { - LOGM(Log::ERR, "Client requested sharing of window handle {:x} which is not shareable!", m_window); - m_resource->sendFailed(); - return; - } - m_resource->setOnDestroy([this](CHyprlandToplevelExportFrameV1* pFrame) { PROTO::toplevelExport->destroyResource(this); }); m_resource->setDestroy([this](CHyprlandToplevelExportFrameV1* pFrame) { PROTO::toplevelExport->destroyResource(this); }); - m_resource->setCopy([this](CHyprlandToplevelExportFrameV1* pFrame, wl_resource* res, int32_t ignoreDamage) { this->copy(pFrame, res, ignoreDamage); }); + m_resource->setCopy([this](CHyprlandToplevelExportFrameV1* pFrame, wl_resource* res, int32_t ignoreDamage) { shareFrame(res, !!ignoreDamage); }); - const auto PMONITOR = m_window->m_monitor.lock(); + m_frame = m_session->nextFrame(overlayCursor); - g_pHyprRenderer->makeEGLCurrent(); - - m_shmFormat = g_pHyprOpenGL->getPreferredReadFormat(PMONITOR); - if UNLIKELY (m_shmFormat == DRM_FORMAT_INVALID) { - LOGM(Log::ERR, "No format supported by renderer in capture toplevel"); + auto formats = m_session->allowedFormats(); + if (formats.empty()) { + LOGM(Log::ERR, "No format supported by renderer in toplevel export protocol"); m_resource->sendFailed(); return; } - const auto PSHMINFO = NFormatUtils::getPixelFormatFromDRM(m_shmFormat); - if UNLIKELY (!PSHMINFO) { - LOGM(Log::ERR, "No pixel format supported by renderer in capture toplevel"); - m_resource->sendFailed(); - return; - } + DRMFormat format = formats.at(0); + auto bufSize = m_frame->bufferSize(); - m_dmabufFormat = g_pHyprOpenGL->getPreferredReadFormat(PMONITOR); + const auto PSHMINFO = NFormatUtils::getPixelFormatFromDRM(format); + const auto stride = NFormatUtils::minStride(PSHMINFO, bufSize.x); + m_resource->sendBuffer(NFormatUtils::drmToShm(format), bufSize.x, bufSize.y, stride); - m_box = {0, 0, sc(m_window->m_realSize->value().x * PMONITOR->m_scale), sc(m_window->m_realSize->value().y * PMONITOR->m_scale)}; - - m_box.transform(Math::wlTransformToHyprutils(PMONITOR->m_transform), PMONITOR->m_transformedSize.x, PMONITOR->m_transformedSize.y).round(); - - m_shmStride = NFormatUtils::minStride(PSHMINFO, m_box.w); - - m_resource->sendBuffer(NFormatUtils::drmToShm(m_shmFormat), m_box.width, m_box.height, m_shmStride); - - if LIKELY (m_dmabufFormat != DRM_FORMAT_INVALID) - m_resource->sendLinuxDmabuf(m_dmabufFormat, m_box.width, m_box.height); + if LIKELY (format != DRM_FORMAT_INVALID) + m_resource->sendLinuxDmabuf(format, bufSize.x, bufSize.y); m_resource->sendBufferDone(); } -void CToplevelExportFrame::copy(CHyprlandToplevelExportFrameV1* pFrame, wl_resource* buffer_, int32_t ignoreDamage) { +bool CToplevelExportFrame::good() { + return m_resource && m_resource->resource(); +} + +void CToplevelExportFrame::shareFrame(wl_resource* buffer, bool ignoreDamage) { if UNLIKELY (!good()) { - LOGM(Log::ERR, "No frame in copyFrame??"); + LOGM(Log::ERR, "No frame in shareFrame??"); return; } - if UNLIKELY (!validMapped(m_window)) { - LOGM(Log::ERR, "Client requested sharing of window handle {:x} which is gone!", m_window); + if UNLIKELY (m_session.expired() || !m_session->monitor()) { + LOGM(Log::ERR, "Session stopped for frame {:x}", (uintptr_t)this); m_resource->sendFailed(); return; } - if UNLIKELY (!m_window->m_isMapped) { - LOGM(Log::ERR, "Client requested sharing of window handle {:x} which is not shareable (2)!", m_window); - m_resource->sendFailed(); - return; - } - - const auto PBUFFER = CWLBufferResource::fromResource(buffer_); - if UNLIKELY (!PBUFFER) { - m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer"); - PROTO::toplevelExport->destroyResource(this); - return; - } - - if UNLIKELY (PBUFFER->m_buffer->size != m_box.size()) { - m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer dimensions"); - PROTO::toplevelExport->destroyResource(this); - return; - } - if UNLIKELY (m_buffer) { + LOGM(Log::ERR, "Buffer used in {:x}", (uintptr_t)this); m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_ALREADY_USED, "frame already used"); - PROTO::toplevelExport->destroyResource(this); + m_resource->sendFailed(); return; } - if (auto attrs = PBUFFER->m_buffer->dmabuf(); attrs.success) { - m_bufferDMA = true; - - if (attrs.format != m_dmabufFormat) { - m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer format"); - PROTO::toplevelExport->destroyResource(this); - return; - } - } else if (auto attrs = PBUFFER->m_buffer->shm(); attrs.success) { - if (attrs.format != m_shmFormat) { - m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer format"); - PROTO::toplevelExport->destroyResource(this); - return; - } else if (attrs.stride != m_shmStride) { - m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer stride"); - PROTO::toplevelExport->destroyResource(this); - return; - } - } else { - m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer type"); - PROTO::toplevelExport->destroyResource(this); + const auto PBUFFERRES = CWLBufferResource::fromResource(buffer); + if UNLIKELY (!PBUFFERRES || !PBUFFERRES->m_buffer) { + LOGM(Log::ERR, "Invalid buffer in {:x}", (uintptr_t)this); + m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer"); + m_resource->sendFailed(); return; } - m_buffer = CHLBufferReference(PBUFFER->m_buffer.lock()); + const auto& PBUFFER = PBUFFERRES->m_buffer.lock(); - m_ignoreDamage = ignoreDamage; + if (ignoreDamage) + g_pHyprRenderer->damageMonitor(m_session->monitor()); - if (ignoreDamage && validMapped(m_window)) - share(); - else - PROTO::toplevelExport->m_framesAwaitingWrite.emplace_back(m_self); -} - -void CToplevelExportFrame::share() { - if (!m_buffer || !validMapped(m_window)) - return; - - if (m_bufferDMA) { - if (!copyDmabuf(Time::steadyNow())) { - m_resource->sendFailed(); - return; - } - } else { - if (!copyShm(Time::steadyNow())) { - m_resource->sendFailed(); + auto error = m_frame->share(PBUFFER, {}, [this, ignoreDamage, self = m_self](eScreenshareResult result) { + if (self.expired() || !good()) return; + switch (result) { + case RESULT_COPIED: { + m_resource->sendFlags(sc(0)); + if (!ignoreDamage) + m_frame->damage().forEachRect([&](const auto& rect) { m_resource->sendDamage(rect.x1, rect.y1, rect.x2 - rect.x1, rect.y2 - rect.y1); }); + + const auto [sec, nsec] = Time::secNsec(m_timestamp); + uint32_t tvSecHi = (sizeof(sec) > 4) ? sec >> 32 : 0; + uint32_t tvSecLo = sec & 0xFFFFFFFF; + m_resource->sendReady(tvSecHi, tvSecLo, nsec); + break; + } + case RESULT_NOT_COPIED: + LOGM(Log::ERR, "Frame share failed in {:x}", (uintptr_t)this); + m_resource->sendFailed(); + break; + case RESULT_TIMESTAMP: m_timestamp = Time::steadyNow(); break; } + }); + + switch (error) { + case ERROR_NONE: m_buffer = CHLBufferReference(PBUFFER); break; + case ERROR_NO_BUFFER: m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer"); break; + case ERROR_BUFFER_SIZE: + case ERROR_BUFFER_FORMAT: m_resource->sendFailed(); break; + case ERROR_UNKNOWN: + case ERROR_STOPPED: m_resource->sendFailed(); break; } - - m_resource->sendFlags(sc(0)); - - if (!m_ignoreDamage) - m_resource->sendDamage(0, 0, m_box.width, m_box.height); - - const auto [sec, nsec] = Time::secNsec(Time::steadyNow()); - - uint32_t tvSecHi = (sizeof(sec) > 4) ? sec >> 32 : 0; - uint32_t tvSecLo = sec & 0xFFFFFFFF; - m_resource->sendReady(tvSecHi, tvSecLo, nsec); -} - -bool CToplevelExportFrame::copyShm(const Time::steady_tp& now) { - const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_resource->client(), PERMISSION_TYPE_SCREENCOPY); - auto shm = m_buffer->shm(); - auto [pixelData, fmt, bufLen] = m_buffer->beginDataPtr(0); // no need for end, cuz it's shm - - // render the client - const auto PMONITOR = m_window->m_monitor.lock(); - CRegion fakeDamage{0, 0, PMONITOR->m_pixelSize.x * 10, PMONITOR->m_pixelSize.y * 10}; - - g_pHyprRenderer->makeEGLCurrent(); - - CFramebuffer outFB; - outFB.alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, PMONITOR->m_output->state->state().drmFormat); - - auto overlayCursor = shouldOverlayCursor(); - - if (overlayCursor) { - g_pPointerManager->lockSoftwareForMonitor(PMONITOR->m_self.lock()); - g_pPointerManager->damageCursor(PMONITOR->m_self.lock()); - } - - if (!g_pHyprRenderer->beginRender(PMONITOR, fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, &outFB)) - return false; - - g_pHyprOpenGL->clear(CHyprColor(0, 0, 0, 1.0)); - - // render client at 0,0 - if (PERM == PERMISSION_RULE_ALLOW_MODE_ALLOW) { - if (!m_window->m_ruleApplicator->noScreenShare().valueOrDefault()) { - g_pHyprRenderer->m_bBlockSurfaceFeedback = g_pHyprRenderer->shouldRenderWindow(m_window); // block the feedback to avoid spamming the surface if it's visible - g_pHyprRenderer->renderWindow(m_window, PMONITOR, now, false, RENDER_PASS_ALL, true, true); - g_pHyprRenderer->m_bBlockSurfaceFeedback = false; - } - if (overlayCursor) - g_pPointerManager->renderSoftwareCursorsFor(PMONITOR->m_self.lock(), now, fakeDamage, g_pInputManager->getMouseCoordsInternal() - m_window->m_realPosition->value()); - } else if (PERM == PERMISSION_RULE_ALLOW_MODE_DENY) { - CBox texbox = CBox{PMONITOR->m_transformedSize / 2.F, g_pHyprOpenGL->m_screencopyDeniedTexture->m_size}.translate(-g_pHyprOpenGL->m_screencopyDeniedTexture->m_size / 2.F); - g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_screencopyDeniedTexture, texbox, {}); - } - - const auto PFORMAT = NFormatUtils::getPixelFormatFromDRM(shm.format); - if (!PFORMAT) { - g_pHyprRenderer->endRender(); - return false; - } - - g_pHyprOpenGL->m_renderData.blockScreenShader = true; - g_pHyprRenderer->endRender(); - - g_pHyprRenderer->makeEGLCurrent(); - g_pHyprOpenGL->m_renderData.pMonitor = PMONITOR; - outFB.bind(); - - glBindFramebuffer(GL_READ_FRAMEBUFFER, outFB.getFBID()); - glPixelStorei(GL_PACK_ALIGNMENT, 1); - - auto glFormat = PFORMAT->flipRB ? GL_BGRA_EXT : GL_RGBA; - - auto origin = Vector2D(0, 0); - switch (PMONITOR->m_transform) { - case WL_OUTPUT_TRANSFORM_FLIPPED_180: - case WL_OUTPUT_TRANSFORM_90: { - origin.y = PMONITOR->m_pixelSize.y - m_box.height; - break; - } - case WL_OUTPUT_TRANSFORM_FLIPPED_270: - case WL_OUTPUT_TRANSFORM_180: { - origin.x = PMONITOR->m_pixelSize.x - m_box.width; - origin.y = PMONITOR->m_pixelSize.y - m_box.height; - break; - } - case WL_OUTPUT_TRANSFORM_FLIPPED: - case WL_OUTPUT_TRANSFORM_270: { - origin.x = PMONITOR->m_pixelSize.x - m_box.width; - break; - } - default: break; - } - - glReadPixels(origin.x, origin.y, m_box.width, m_box.height, glFormat, PFORMAT->glType, pixelData); - - if (overlayCursor) { - g_pPointerManager->unlockSoftwareForMonitor(PMONITOR->m_self.lock()); - g_pPointerManager->damageCursor(PMONITOR->m_self.lock()); - } - - outFB.unbind(); - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - - return true; -} - -bool CToplevelExportFrame::copyDmabuf(const Time::steady_tp& now) { - const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(m_resource->client(), PERMISSION_TYPE_SCREENCOPY); - const auto PMONITOR = m_window->m_monitor.lock(); - - CRegion fakeDamage{0, 0, INT16_MAX, INT16_MAX}; - - auto overlayCursor = shouldOverlayCursor(); - - if (overlayCursor) { - g_pPointerManager->lockSoftwareForMonitor(PMONITOR->m_self.lock()); - g_pPointerManager->damageCursor(PMONITOR->m_self.lock()); - } - - if (!g_pHyprRenderer->beginRender(PMONITOR, fakeDamage, RENDER_MODE_TO_BUFFER, m_buffer.m_buffer)) - return false; - - g_pHyprOpenGL->clear(CHyprColor(0, 0, 0, 1.0)); - if (PERM == PERMISSION_RULE_ALLOW_MODE_ALLOW) { - if (!m_window->m_ruleApplicator->noScreenShare().valueOrDefault()) { - g_pHyprRenderer->m_bBlockSurfaceFeedback = g_pHyprRenderer->shouldRenderWindow(m_window); // block the feedback to avoid spamming the surface if it's visible - g_pHyprRenderer->renderWindow(m_window, PMONITOR, now, false, RENDER_PASS_ALL, true, true); - g_pHyprRenderer->m_bBlockSurfaceFeedback = false; - } - - if (overlayCursor) - g_pPointerManager->renderSoftwareCursorsFor(PMONITOR->m_self.lock(), now, fakeDamage, g_pInputManager->getMouseCoordsInternal() - m_window->m_realPosition->value()); - } else if (PERM == PERMISSION_RULE_ALLOW_MODE_DENY) { - CBox texbox = CBox{PMONITOR->m_transformedSize / 2.F, g_pHyprOpenGL->m_screencopyDeniedTexture->m_size}.translate(-g_pHyprOpenGL->m_screencopyDeniedTexture->m_size / 2.F); - g_pHyprOpenGL->renderTexture(g_pHyprOpenGL->m_screencopyDeniedTexture, texbox, {}); - } - - g_pHyprOpenGL->m_renderData.blockScreenShader = true; - g_pHyprRenderer->endRender(); - - if (overlayCursor) { - g_pPointerManager->unlockSoftwareForMonitor(PMONITOR->m_self.lock()); - g_pPointerManager->damageCursor(PMONITOR->m_self.lock()); - } - - return true; -} - -bool CToplevelExportFrame::shouldOverlayCursor() const { - if (!m_cursorOverlayRequested) - return false; - - auto pointerSurfaceResource = g_pSeatManager->m_state.pointerFocus.lock(); - - if (!pointerSurfaceResource) - return false; - - auto pointerSurface = Desktop::View::CWLSurface::fromResource(pointerSurfaceResource); - - return pointerSurface && Desktop::View::CWindow::fromView(pointerSurface->view()) == m_window; -} - -bool CToplevelExportFrame::good() { - return m_resource->resource(); } CToplevelExportProtocol::CToplevelExportProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { - static auto P1 = g_pHookSystem->hookDynamic("closeWindow", [this](void* self, SCallbackInfo& info, std::any data) { - auto window = std::any_cast(data); - - onWindowUnmap(window); - }); + ; } void CToplevelExportProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { @@ -405,69 +167,10 @@ void CToplevelExportProtocol::bindManager(wl_client* client, void* data, uint32_ } void CToplevelExportProtocol::destroyResource(CToplevelExportClient* client) { - std::erase_if(m_clients, [&](const auto& other) { return other.get() == client; }); std::erase_if(m_frames, [&](const auto& other) { return other->m_client.get() == client; }); - std::erase_if(m_framesAwaitingWrite, [&](const auto& other) { return !other || other->m_client.get() == client; }); + std::erase_if(m_clients, [&](const auto& other) { return other.get() == client; }); } void CToplevelExportProtocol::destroyResource(CToplevelExportFrame* frame) { std::erase_if(m_frames, [&](const auto& other) { return other.get() == frame; }); - std::erase_if(m_framesAwaitingWrite, [&](const auto& other) { return !other || other.get() == frame; }); -} - -void CToplevelExportProtocol::onOutputCommit(PHLMONITOR pMonitor) { - if (m_framesAwaitingWrite.empty()) - return; // nothing to share - - std::vector> framesToRemove; - // reserve number of elements to avoid reallocations - framesToRemove.reserve(m_framesAwaitingWrite.size()); - - // share frame if correct output - for (auto const& f : m_framesAwaitingWrite) { - if (!f) - continue; - - // check permissions - const auto PERM = g_pDynamicPermissionManager->clientPermissionMode(f->m_resource->client(), PERMISSION_TYPE_SCREENCOPY); - - if (PERM == PERMISSION_RULE_ALLOW_MODE_PENDING) - continue; // pending an answer, don't do anything yet. - - if (!validMapped(f->m_window)) { - framesToRemove.emplace_back(f); - continue; - } - - if (!f->m_window) - continue; - - const auto PWINDOW = f->m_window; - - if (pMonitor != PWINDOW->m_monitor.lock()) - continue; - - CBox geometry = {PWINDOW->m_realPosition->value().x, PWINDOW->m_realPosition->value().y, PWINDOW->m_realSize->value().x, PWINDOW->m_realSize->value().y}; - - if (geometry.intersection({pMonitor->m_position, pMonitor->m_size}).empty()) - continue; - - f->share(); - - f->m_client->m_lastFrame.reset(); - ++f->m_client->m_frameCounter; - - framesToRemove.push_back(f); - } - - for (auto const& f : framesToRemove) { - std::erase(m_framesAwaitingWrite, f); - } -} - -void CToplevelExportProtocol::onWindowUnmap(PHLWINDOW pWindow) { - for (auto const& f : m_frames) { - if (f->m_window == pWindow) - f->m_window.reset(); - } } diff --git a/src/protocols/ToplevelExport.hpp b/src/protocols/ToplevelExport.hpp index 44704d84..5d30f09b 100644 --- a/src/protocols/ToplevelExport.hpp +++ b/src/protocols/ToplevelExport.hpp @@ -1,72 +1,59 @@ #pragma once -#include "../defines.hpp" -#include "hyprland-toplevel-export-v1.hpp" #include "WaylandProtocol.hpp" -#include "Screencopy.hpp" +#include "hyprland-toplevel-export-v1.hpp" + #include "../helpers/time/Time.hpp" +#include "./types/Buffer.hpp" +#include #include class CMonitor; +namespace Screenshare { + class CScreenshareSession; + class CScreenshareFrame; +}; class CToplevelExportClient { public: CToplevelExportClient(SP resource_); - bool good(); - - WP m_self; - eClientOwners m_clientOwner = CLIENT_TOPLEVEL_EXPORT; - - CTimer m_lastFrame; - int m_frameCounter = 0; + bool good(); private: SP m_resource; + WP m_self; - int m_framesInLastHalfSecond = 0; - CTimer m_lastMeasure; - bool m_sentScreencast = false; + wl_client* m_savedClient = nullptr; - SP m_tickCallback; - void onTick(); - - void captureToplevel(CHyprlandToplevelExportManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, PHLWINDOW handle); + void captureToplevel(uint32_t frame, int32_t overlayCursor, PHLWINDOW handle); friend class CToplevelExportProtocol; }; class CToplevelExportFrame { public: - CToplevelExportFrame(SP resource_, int32_t overlayCursor, PHLWINDOW pWindow); + CToplevelExportFrame(SP resource, WP session, bool overlayCursor); - bool good(); - - WP m_self; - WP m_client; + bool good(); private: - SP m_resource; + SP m_resource; + WP m_self; + WP m_client; - PHLWINDOW m_window; - bool m_cursorOverlayRequested = false; - bool m_ignoreDamage = false; + WP m_session; + UP m_frame; - CHLBufferReference m_buffer; - bool m_bufferDMA = false; - uint32_t m_shmFormat = 0; - uint32_t m_dmabufFormat = 0; - int m_shmStride = 0; - CBox m_box = {}; + CHLBufferReference m_buffer; + Time::steady_tp m_timestamp; - void copy(CHyprlandToplevelExportFrameV1* pFrame, wl_resource* buffer, int32_t ignoreDamage); - bool copyDmabuf(const Time::steady_tp& now); - bool copyShm(const Time::steady_tp& now); - void share(); - bool shouldOverlayCursor() const; + // + void shareFrame(wl_resource* buffer, bool ignoreDamage); friend class CToplevelExportProtocol; + friend class CToplevelExportClient; }; class CToplevelExportProtocol : IWaylandProtocol { @@ -82,15 +69,9 @@ class CToplevelExportProtocol : IWaylandProtocol { private: std::vector> m_clients; std::vector> m_frames; - std::vector> m_framesAwaitingWrite; void onWindowUnmap(PHLWINDOW pWindow); - void shareFrame(CToplevelExportFrame* frame); - bool copyFrameDmabuf(CToplevelExportFrame* frame, const Time::steady_tp& now); - bool copyFrameShm(CToplevelExportFrame* frame, const Time::steady_tp& now); - void sendDamage(CToplevelExportFrame* frame); - friend class CToplevelExportClient; friend class CToplevelExportFrame; }; diff --git a/src/protocols/WaylandProtocol.hpp b/src/protocols/WaylandProtocol.hpp index 5f1c9798..5c187c7d 100644 --- a/src/protocols/WaylandProtocol.hpp +++ b/src/protocols/WaylandProtocol.hpp @@ -34,7 +34,7 @@ } else if (level == Log::DEBUG || level == Log::INFO || level == Log::TRACE) { \ oss << "[" << EXTRACT_CLASS_NAME() << "] "; \ } \ - if constexpr (std::tuple_size::value == 1 && std::is_same_v) { \ + if constexpr (std::tuple_size_v == 1 && std::is_same_v) { \ oss << __VA_ARGS__; \ Log::logger->log(level, oss.str()); \ } else { \ diff --git a/src/protocols/XDGOutput.cpp b/src/protocols/XDGOutput.cpp index 8835d4b5..3553a74b 100644 --- a/src/protocols/XDGOutput.cpp +++ b/src/protocols/XDGOutput.cpp @@ -2,7 +2,7 @@ #include "../config/ConfigValue.hpp" #include "../helpers/Monitor.hpp" #include "../xwayland/XWayland.hpp" -#include "../managers/HookSystemManager.hpp" +#include "../event/EventBus.hpp" #include "core/Output.hpp" #define OUTPUT_MANAGER_VERSION 3 @@ -36,8 +36,8 @@ void CXDGOutputProtocol::bindManager(wl_client* client, void* data, uint32_t ver } 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 P = Event::bus()->m_events.monitor.layoutChanged.listen([this] { updateAllOutputs(); }); + static auto P2 = Event::bus()->m_events.config.reloaded.listen([this] { updateAllOutputs(); }); } void CXDGOutputProtocol::onManagerGetXDGOutput(CZxdgOutputManagerV1* mgr, uint32_t id, wl_resource* outputResource) { diff --git a/src/protocols/core/Compositor.cpp b/src/protocols/core/Compositor.cpp index b9e677af..06f430b9 100644 --- a/src/protocols/core/Compositor.cpp +++ b/src/protocols/core/Compositor.cpp @@ -286,16 +286,20 @@ void CWLSurfaceResource::enter(PHLMONITOR monitor) { return; } - auto output = PROTO::outputs.at(monitor->m_name)->outputResourceFrom(m_client); + auto outputs = PROTO::outputs.at(monitor->m_name)->outputResourcesFrom(m_client); - if UNLIKELY (!output || !output->getResource() || !output->getResource()->resource()) { + if UNLIKELY (outputs.empty() || std::ranges::all_of(outputs, [](const auto& o) { return !o->getResource() || !o->getResource()->resource(); })) { LOGM(Log::ERR, "Cannot enter surface {:x} to {}, client hasn't bound the output", (uintptr_t)this, monitor->m_name); return; } m_enteredOutputs.emplace_back(monitor); - m_resource->sendEnter(output->getResource().get()); + for (const auto& o : outputs) { + if (!o->getResource() || !o->getResource()->resource()) + continue; + m_resource->sendEnter(o->getResource().get()); + } m_events.enter.emit(monitor); } @@ -303,16 +307,20 @@ void CWLSurfaceResource::leave(PHLMONITOR monitor) { if UNLIKELY (std::ranges::find(m_enteredOutputs, monitor) == m_enteredOutputs.end()) return; - auto output = PROTO::outputs.at(monitor->m_name)->outputResourceFrom(m_client); + auto outputs = PROTO::outputs.at(monitor->m_name)->outputResourcesFrom(m_client); - if UNLIKELY (!output) { + if UNLIKELY (outputs.empty() || std::ranges::all_of(outputs, [](const auto& o) { return !o->getResource() || !o->getResource()->resource(); })) { LOGM(Log::ERR, "Cannot leave surface {:x} from {}, client hasn't bound the output", (uintptr_t)this, monitor->m_name); return; } std::erase(m_enteredOutputs, monitor); - m_resource->sendLeave(output->getResource().get()); + for (const auto& o : outputs) { + if (!o->getResource() || !o->getResource()->resource()) + continue; + m_resource->sendLeave(o->getResource().get()); + } m_events.leave.emit(monitor); } @@ -500,20 +508,32 @@ void CWLSurfaceResource::scheduleState(WP state) { if (state->updated.bits.acquire) { // wait on acquire point for this surface, from explicit sync protocol - state->acquire.addWaiter([state, whenReadable]() { whenReadable(state, LOCK_REASON_FENCE); }); + if (!state->acquire.addWaiter([state, whenReadable]() { whenReadable(state, LOCK_REASON_FENCE); })) { + Log::logger->log(Log::ERR, "Failed to addWaiter in CWLSurfaceResource::scheduleState"); + whenReadable(state, LOCK_REASON_FENCE); + } } else if (state->buffer && state->buffer->isSynchronous()) { // synchronous (shm) buffers can be read immediately - m_stateQueue.unlock(state); + m_stateQueue.unlock(state, LOCK_REASON_FENCE); } else if (state->buffer && state->buffer->m_syncFd.isValid()) { // async buffer and is dmabuf, then we can wait on implicit fences g_pEventLoopManager->doOnReadable(std::move(state->buffer->m_syncFd), [state, whenReadable]() { whenReadable(state, LOCK_REASON_FENCE); }); } else { // state commit without a buffer. - m_stateQueue.unlock(state); + m_stateQueue.tryProcess(); } } void CWLSurfaceResource::commitState(SSurfaceState& state) { + // TODO might be incorrect. needed for VRR with FIFO to avoid same buffer extra frames for second commit when it's used in this way: + // wp_fifo_v1#43.set_barrier() + // wp_fifo_v1#43.wait_barrier() + // wl_surface#3.commit() + // wp_fifo_v1#43.wait_barrier() + // wl_surface#3.commit() + if (!state.updated.all && m_mapped && state.fifoScheduled) + return; + auto lastTexture = m_current.texture; m_current.updateFrom(state); @@ -614,6 +634,30 @@ bool CWLSurfaceResource::hasVisibleSubsurface() { return false; } +bool CWLSurfaceResource::isTearing() { + if (m_enteredOutputs.empty() && m_hlSurface) { + for (auto& m : g_pCompositor->m_monitors) { + if (!m || !m->m_enabled) + continue; + + auto box = m_hlSurface->getSurfaceBoxGlobal(); + if (box && !box->intersection({m->m_position, m->m_size}).empty()) { + if (m->m_tearingState.activelyTearing) + return true; + } + } + } else { + for (auto& m : m_enteredOutputs) { + if (!m) + continue; + + if (m->m_tearingState.activelyTearing) + return true; + } + } + return false; +} + void CWLSurfaceResource::updateCursorShm(CRegion damage) { if (damage.empty()) return; @@ -658,8 +702,14 @@ void CWLSurfaceResource::presentFeedback(const Time::steady_tp& when, PHLMONITOR FEEDBACK->attachMonitor(pMonitor); if (discarded) FEEDBACK->discarded(); - else + else { FEEDBACK->presented(); + if (!pMonitor->m_lastScanout.expired()) { + const auto WINDOW = m_hlSurface ? Desktop::View::CWindow::fromView(m_hlSurface->view()) : nullptr; + if (WINDOW == pMonitor->m_lastScanout) + FEEDBACK->setPresentationType(true); + } + } PROTO::presentation->queueData(std::move(FEEDBACK)); } diff --git a/src/protocols/core/Compositor.hpp b/src/protocols/core/Compositor.hpp index 89bfb31b..37ca51b7 100644 --- a/src/protocols/core/Compositor.hpp +++ b/src/protocols/core/Compositor.hpp @@ -20,7 +20,7 @@ #include "../../helpers/math/Math.hpp" #include "../../helpers/time/Time.hpp" #include "../types/Buffer.hpp" -#include "../types/ColorManagement.hpp" +#include "../../helpers/cm/ColorManagement.hpp" #include "../types/SurfaceRole.hpp" #include "../types/SurfaceState.hpp" @@ -128,6 +128,7 @@ class CWLSurfaceResource { NColorManagement::PImageDescription getPreferredImageDescription(); void sortSubsurfaces(); bool hasVisibleSubsurface(); + bool isTearing(); // returns a pair: found surface (null if not found) and surface local coords. // localCoords param is relative to 0,0 of this surface diff --git a/src/protocols/core/DataDevice.cpp b/src/protocols/core/DataDevice.cpp index 41f07273..22ccae6c 100644 --- a/src/protocols/core/DataDevice.cpp +++ b/src/protocols/core/DataDevice.cpp @@ -10,11 +10,11 @@ #include "../../xwayland/XWayland.hpp" #include "../../xwayland/Server.hpp" #include "../../managers/input/InputManager.hpp" -#include "../../managers/HookSystemManager.hpp" #include "../../managers/cursor/CursorShapeOverrideController.hpp" #include "../../helpers/Monitor.hpp" #include "../../render/Renderer.hpp" #include "../../xwayland/Dnd.hpp" +#include "../../event/EventBus.hpp" using namespace Hyprutils::OS; CWLDataOfferResource::CWLDataOfferResource(SP resource_, SP source_) : m_source(source_), m_resource(resource_) { @@ -586,29 +586,26 @@ void CWLDataDeviceProtocol::initiateDrag(WP currentSource }); } - m_dnd.mouseButton = g_pHookSystem->hookDynamic("mouseButton", [this](void* self, SCallbackInfo& info, std::any e) { - auto E = std::any_cast(e); - if (E.state == WL_POINTER_BUTTON_STATE_RELEASED) { + m_dnd.mouseButton = Event::bus()->m_events.input.mouse.button.listen([this](IPointer::SButtonEvent e, Event::SCallbackInfo&) { + if (e.state == WL_POINTER_BUTTON_STATE_RELEASED) { LOGM(Log::DEBUG, "Dropping drag on mouseUp"); dropDrag(); } }); - m_dnd.touchUp = g_pHookSystem->hookDynamic("touchUp", [this](void* self, SCallbackInfo& info, std::any e) { + m_dnd.touchUp = Event::bus()->m_events.input.touch.up.listen([this](ITouch::SUpEvent e, Event::SCallbackInfo&) { LOGM(Log::DEBUG, "Dropping drag on touchUp"); dropDrag(); }); - m_dnd.tabletTip = g_pHookSystem->hookDynamic("tabletTip", [this](void* self, SCallbackInfo& info, std::any e) { - auto E = std::any_cast(e); - if (!E.in) { + m_dnd.tabletTip = Event::bus()->m_events.input.tablet.tip.listen([this](CTablet::STipEvent e, Event::SCallbackInfo&) { + if (!e.in) { LOGM(Log::DEBUG, "Dropping drag on tablet tipUp"); dropDrag(); } }); - m_dnd.mouseMove = g_pHookSystem->hookDynamic("mouseMove", [this](void* self, SCallbackInfo& info, std::any e) { - auto V = std::any_cast(e); + m_dnd.mouseMove = Event::bus()->m_events.input.mouse.move.listen([this](Vector2D pos, Event::SCallbackInfo&) { if (m_dnd.focusedDevice && g_pSeatManager->m_state.dndPointerFocus) { auto surf = Desktop::View::CWLSurface::fromResource(g_pSeatManager->m_state.dndPointerFocus.lock()); @@ -620,13 +617,12 @@ void CWLDataDeviceProtocol::initiateDrag(WP currentSource if (!box.has_value()) return; - m_dnd.focusedDevice->sendMotion(Time::millis(Time::steadyNow()), V - box->pos()); - LOGM(Log::DEBUG, "Drag motion {}", V - box->pos()); + m_dnd.focusedDevice->sendMotion(Time::millis(Time::steadyNow()), pos - box->pos()); + LOGM(Log::DEBUG, "Drag motion {}", pos - box->pos()); } }); - m_dnd.touchMove = g_pHookSystem->hookDynamic("touchMove", [this](void* self, SCallbackInfo& info, std::any e) { - auto E = std::any_cast(e); + m_dnd.touchMove = Event::bus()->m_events.input.touch.motion.listen([this](ITouch::SMotionEvent e, Event::SCallbackInfo&) { if (m_dnd.focusedDevice && g_pSeatManager->m_state.dndPointerFocus) { auto surf = Desktop::View::CWLSurface::fromResource(g_pSeatManager->m_state.dndPointerFocus.lock()); @@ -638,8 +634,8 @@ void CWLDataDeviceProtocol::initiateDrag(WP currentSource if (!box.has_value()) return; - m_dnd.focusedDevice->sendMotion(E.timeMs, E.pos); - LOGM(Log::DEBUG, "Drag motion {}", E.pos); + m_dnd.focusedDevice->sendMotion(e.timeMs, e.pos); + LOGM(Log::DEBUG, "Drag motion {}", e.pos); } }); diff --git a/src/protocols/core/DataDevice.hpp b/src/protocols/core/DataDevice.hpp index b4ad378f..f3717f78 100644 --- a/src/protocols/core/DataDevice.hpp +++ b/src/protocols/core/DataDevice.hpp @@ -178,11 +178,11 @@ class CWLDataDeviceProtocol : public IWaylandProtocol { CHyprSignalListener dndSurfaceCommit; // for ending a dnd - SP mouseMove; - SP mouseButton; - SP touchUp; - SP touchMove; - SP tabletTip; + CHyprSignalListener mouseMove; + CHyprSignalListener mouseButton; + CHyprSignalListener touchUp; + CHyprSignalListener touchMove; + CHyprSignalListener tabletTip; } m_dnd; void abortDrag(); diff --git a/src/protocols/core/Output.cpp b/src/protocols/core/Output.cpp index 755470e4..dd9c3166 100644 --- a/src/protocols/core/Output.cpp +++ b/src/protocols/core/Output.cpp @@ -117,15 +117,17 @@ void CWLOutputProtocol::destroyResource(CWLOutputResource* resource) { PROTO::outputs.erase(m_name); } -SP CWLOutputProtocol::outputResourceFrom(wl_client* client) { +std::vector> CWLOutputProtocol::outputResourcesFrom(wl_client* client) { + std::vector> ret; + for (auto const& r : m_outputs) { if (r->client() != client) continue; - return r; + ret.emplace_back(r); } - return nullptr; + return ret; } void CWLOutputProtocol::remove() { diff --git a/src/protocols/core/Output.hpp b/src/protocols/core/Output.hpp index d0c6fb13..cf266685 100644 --- a/src/protocols/core/Output.hpp +++ b/src/protocols/core/Output.hpp @@ -34,13 +34,13 @@ class CWLOutputProtocol : public IWaylandProtocol { public: CWLOutputProtocol(const wl_interface* iface, const int& ver, const std::string& name, PHLMONITOR pMonitor); - virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); + virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); - SP outputResourceFrom(wl_client* client); - void sendDone(); + std::vector> outputResourcesFrom(wl_client* client); + void sendDone(); - PHLMONITORREF m_monitor; - WP m_self; + PHLMONITORREF m_monitor; + WP m_self; // will mark the protocol for removal, will be removed when no. of bound outputs is 0 (or when overwritten by a new global) void remove(); diff --git a/src/protocols/core/Seat.cpp b/src/protocols/core/Seat.cpp index bfe70a75..52015fd8 100644 --- a/src/protocols/core/Seat.cpp +++ b/src/protocols/core/Seat.cpp @@ -141,6 +141,10 @@ CWLPointerResource::CWLPointerResource(SP resource_, SPm_state.pointerFocus.lock(), {-1, -1} /* Coords don't really matter that much, they will be updated next move */); } +CWLPointerResource::~CWLPointerResource() { + m_events.destroyed.emit(); +} + int CWLPointerResource::version() { return m_resource->version(); } diff --git a/src/protocols/core/Seat.hpp b/src/protocols/core/Seat.hpp index c30bbd71..85dc5c39 100644 --- a/src/protocols/core/Seat.hpp +++ b/src/protocols/core/Seat.hpp @@ -71,6 +71,7 @@ class CWLTouchResource { class CWLPointerResource { public: CWLPointerResource(SP resource_, SP owner_); + ~CWLPointerResource(); bool good(); int version(); @@ -88,6 +89,10 @@ class CWLPointerResource { WP m_owner; + struct { + CSignalT<> destroyed; + } m_events; + // static SP fromResource(wl_resource* res); diff --git a/src/protocols/core/Shm.cpp b/src/protocols/core/Shm.cpp index 476b58e3..7cca3814 100644 --- a/src/protocols/core/Shm.cpp +++ b/src/protocols/core/Shm.cpp @@ -176,6 +176,7 @@ CWLSHMResource::CWLSHMResource(UP&& resource_) : m_resource(std::move(re if UNLIKELY (!good()) return; + m_resource->setRelease([this](CWlShm* r) { PROTO::shm->destroyResource(this); }); m_resource->setOnDestroy([this](CWlShm* r) { PROTO::shm->destroyResource(this); }); m_resource->setCreatePool([](CWlShm* r, uint32_t id, int32_t fd, int32_t size) { diff --git a/src/protocols/types/Buffer.cpp b/src/protocols/types/Buffer.cpp index ad19cb32..93bd5d20 100644 --- a/src/protocols/types/Buffer.cpp +++ b/src/protocols/types/Buffer.cpp @@ -59,6 +59,10 @@ CHLBufferReference::CHLBufferReference(const CHLBufferReference& other) : m_buff m_buffer->lock(); } +CHLBufferReference::CHLBufferReference(CHLBufferReference&& other) noexcept : m_buffer(std::move(other.m_buffer)) { + ; +} + CHLBufferReference::CHLBufferReference(SP buffer_) : m_buffer(buffer_) { if (m_buffer) m_buffer->lock(); @@ -70,6 +74,9 @@ CHLBufferReference::~CHLBufferReference() { } CHLBufferReference& CHLBufferReference::operator=(const CHLBufferReference& other) { + if (m_buffer == other.m_buffer) + return *this; // same buffer, do nothing + if (other.m_buffer) other.m_buffer->lock(); if (m_buffer) @@ -78,6 +85,16 @@ CHLBufferReference& CHLBufferReference::operator=(const CHLBufferReference& othe return *this; } +CHLBufferReference& CHLBufferReference::operator=(CHLBufferReference&& other) { + if (this != &other) { + if (m_buffer) + m_buffer->unlock(); + m_buffer = other.m_buffer; + other.m_buffer = nullptr; + } + return *this; +} + bool CHLBufferReference::operator==(const CHLBufferReference& other) const { return m_buffer == other.m_buffer; } diff --git a/src/protocols/types/Buffer.hpp b/src/protocols/types/Buffer.hpp index f85670ef..afff11a5 100644 --- a/src/protocols/types/Buffer.hpp +++ b/src/protocols/types/Buffer.hpp @@ -26,7 +26,7 @@ class IHLBuffer : public Aquamarine::IBuffer { void onBackendRelease(const std::function& fn); void addReleasePoint(CDRMSyncPointState& point); - SP m_texture; + SP m_texture; bool m_opaque = false; SP m_resource; std::vector> m_syncReleasers; @@ -49,10 +49,13 @@ class CHLBufferReference { public: CHLBufferReference(); CHLBufferReference(const CHLBufferReference& other); + CHLBufferReference(CHLBufferReference&& other) noexcept; CHLBufferReference(SP buffer); ~CHLBufferReference(); CHLBufferReference& operator=(const CHLBufferReference& other); + CHLBufferReference& operator=(CHLBufferReference&& other); + bool operator==(const CHLBufferReference& other) const; bool operator==(const SP& other) const; bool operator==(const SP& other) const; diff --git a/src/protocols/types/ColorManagement.cpp b/src/protocols/types/ColorManagement.cpp deleted file mode 100644 index 5d23d1c9..00000000 --- a/src/protocols/types/ColorManagement.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include "ColorManagement.hpp" -#include "../../macros.hpp" -#include -#include -#include - -namespace NColorManagement { - // expected to be small - static std::vector> knownPrimaries; - static std::vector> knownDescriptions; - static std::map, Hyprgraphics::CMatrix3> primariesConversion; - - const SPCPRimaries& getPrimaries(ePrimaries name) { - switch (name) { - case CM_PRIMARIES_SRGB: return NColorPrimaries::BT709; - case CM_PRIMARIES_BT2020: return NColorPrimaries::BT2020; - case CM_PRIMARIES_PAL_M: return NColorPrimaries::PAL_M; - case CM_PRIMARIES_PAL: return NColorPrimaries::PAL; - case CM_PRIMARIES_NTSC: return NColorPrimaries::NTSC; - case CM_PRIMARIES_GENERIC_FILM: return NColorPrimaries::GENERIC_FILM; - case CM_PRIMARIES_CIE1931_XYZ: return NColorPrimaries::CIE1931_XYZ; - case CM_PRIMARIES_DCI_P3: return NColorPrimaries::DCI_P3; - case CM_PRIMARIES_DISPLAY_P3: return NColorPrimaries::DISPLAY_P3; - case CM_PRIMARIES_ADOBE_RGB: return NColorPrimaries::ADOBE_RGB; - default: return NColorPrimaries::DEFAULT_PRIMARIES; - } - } - - CPrimaries::CPrimaries(const SPCPRimaries& primaries, const uint primariesId) : m_id(primariesId), m_primaries(primaries) { - m_primaries2XYZ = m_primaries.toXYZ(); - } - - WP CPrimaries::from(const SPCPRimaries& primaries) { - for (const auto& known : knownPrimaries) { - if (known->value() == primaries) - return known; - } - - knownPrimaries.emplace_back(CUniquePointer(new CPrimaries(primaries, knownPrimaries.size() + 1))); - return knownPrimaries.back(); - } - - WP CPrimaries::from(const ePrimaries name) { - return from(getPrimaries(name)); - } - - WP CPrimaries::from(const uint primariesId) { - ASSERT(primariesId <= knownPrimaries.size()); - return knownPrimaries[primariesId - 1]; - } - - const SPCPRimaries& CPrimaries::value() const { - return m_primaries; - } - - uint CPrimaries::id() const { - return m_id; - } - - const Hyprgraphics::CMatrix3& CPrimaries::toXYZ() const { - return m_primaries2XYZ; - } - - const Hyprgraphics::CMatrix3& CPrimaries::convertMatrix(const WP dst) const { - const auto cacheKey = std::make_pair(m_id, dst->m_id); - if (!primariesConversion.contains(cacheKey)) - primariesConversion.insert(std::make_pair(cacheKey, m_primaries.convertMatrix(dst->m_primaries))); - - return primariesConversion[cacheKey]; - } - - CImageDescription::CImageDescription(const SImageDescription& imageDescription, const uint imageDescriptionId) : - m_id(imageDescriptionId), m_imageDescription(imageDescription) { - m_primariesId = CPrimaries::from(m_imageDescription.getPrimaries())->id(); - } - - PImageDescription CImageDescription::from(const SImageDescription& imageDescription) { - for (const auto& known : knownDescriptions) { - if (known->value() == imageDescription) - return known; - } - - knownDescriptions.emplace_back(CUniquePointer(new CImageDescription(imageDescription, knownDescriptions.size() + 1))); - return knownDescriptions.back(); - } - - PImageDescription CImageDescription::from(const uint imageDescriptionId) { - ASSERT(imageDescriptionId <= knownDescriptions.size()); - return knownDescriptions[imageDescriptionId - 1]; - } - - PImageDescription CImageDescription::with(const SImageDescription::SPCLuminances& luminances) const { - auto desc = m_imageDescription; - desc.luminances = luminances; - return CImageDescription::from(desc); - } - - const SImageDescription& CImageDescription::value() const { - return m_imageDescription; - } - - uint CImageDescription::id() const { - return m_id; - } - - WP CImageDescription::getPrimaries() const { - return CPrimaries::from(m_primariesId); - } - -} \ No newline at end of file diff --git a/src/protocols/types/DMABuffer.cpp b/src/protocols/types/DMABuffer.cpp index f3c3e067..86db8ca6 100644 --- a/src/protocols/types/DMABuffer.cpp +++ b/src/protocols/types/DMABuffer.cpp @@ -13,31 +13,28 @@ using namespace Hyprutils::OS; CDMABuffer::CDMABuffer(uint32_t id, wl_client* client, Aquamarine::SDMABUFAttrs const& attrs_) : m_attrs(attrs_) { - g_pHyprRenderer->makeEGLCurrent(); - m_listeners.resourceDestroy = events.destroy.listen([this] { closeFDs(); m_listeners.resourceDestroy.reset(); }); - size = m_attrs.size; - m_resource = CWLBufferResource::create(makeShared(client, 1, id)); - auto eglImage = g_pHyprOpenGL->createEGLImage(m_attrs); + size = m_attrs.size; + m_resource = CWLBufferResource::create(makeShared(client, 1, id)); + m_opaque = NFormatUtils::isFormatOpaque(m_attrs.format); + m_texture = g_pHyprRenderer->createTexture(m_attrs, m_opaque); // texture takes ownership of the eglImage - if UNLIKELY (!eglImage) { + if UNLIKELY (!m_texture) { Log::logger->log(Log::ERR, "CDMABuffer: failed to import EGLImage, retrying as implicit"); m_attrs.modifier = DRM_FORMAT_MOD_INVALID; - eglImage = g_pHyprOpenGL->createEGLImage(m_attrs); + m_texture = g_pHyprRenderer->createTexture(m_attrs, m_opaque); - if UNLIKELY (!eglImage) { + if UNLIKELY (!m_texture) { Log::logger->log(Log::ERR, "CDMABuffer: failed to import EGLImage"); return; } } - m_texture = makeShared(m_attrs, eglImage); // texture takes ownership of the eglImage - m_opaque = NFormatUtils::isFormatOpaque(m_attrs.format); - m_success = m_texture->m_texID; + m_success = m_texture->ok(); if UNLIKELY (!m_success) Log::logger->log(Log::ERR, "Failed to create a dmabuf: texture is null"); diff --git a/src/protocols/types/SurfaceState.cpp b/src/protocols/types/SurfaceState.cpp index ecead008..da98d3fb 100644 --- a/src/protocols/types/SurfaceState.cpp +++ b/src/protocols/types/SurfaceState.cpp @@ -1,6 +1,7 @@ #include "SurfaceState.hpp" #include "helpers/Format.hpp" #include "protocols/types/Buffer.hpp" +#include "render/Renderer.hpp" #include "render/Texture.hpp" Vector2D SSurfaceState::sourceSize() { @@ -34,7 +35,7 @@ CRegion SSurfaceState::accumulateBufferDamage() { return bufferDamage; } -void SSurfaceState::updateSynchronousTexture(SP lastTexture) { +void SSurfaceState::updateSynchronousTexture(SP lastTexture) { auto [dataPtr, fmt, size] = buffer->beginDataPtr(0); if (dataPtr) { auto drmFmt = NFormatUtils::shmToDRM(fmt); @@ -43,7 +44,7 @@ void SSurfaceState::updateSynchronousTexture(SP lastTexture) { texture = lastTexture; texture->update(drmFmt, dataPtr, stride, accumulateBufferDamage()); } else - texture = makeShared(drmFmt, dataPtr, stride, bufferSize); + texture = g_pHyprRenderer->createTexture(drmFmt, dataPtr, stride, bufferSize); } buffer->endDataPtr(); } @@ -63,6 +64,13 @@ void SSurfaceState::reset() { callbacks.clear(); lockMask = LOCK_REASON_NONE; + + barrierSet = false; + surfaceLocked = false; + fifoScheduled = false; + + pendingTimeout.reset(); + timer.reset(); // CEventLoopManager::nudgeTimers should handle it eventually } void SSurfaceState::updateFrom(SSurfaceState& ref) { @@ -112,4 +120,7 @@ void SSurfaceState::updateFrom(SSurfaceState& ref) { callbacks.insert(callbacks.end(), std::make_move_iterator(ref.callbacks.begin()), std::make_move_iterator(ref.callbacks.end())); ref.callbacks.clear(); } + + if (ref.barrierSet) + barrierSet = ref.barrierSet; } diff --git a/src/protocols/types/SurfaceState.hpp b/src/protocols/types/SurfaceState.hpp index dd767962..d5b7e4b9 100644 --- a/src/protocols/types/SurfaceState.hpp +++ b/src/protocols/types/SurfaceState.hpp @@ -1,10 +1,12 @@ #pragma once #include "../../helpers/math/Math.hpp" +#include "../../helpers/time/Time.hpp" +#include "../../managers/eventLoop/EventLoopTimer.hpp" #include "../WaylandProtocol.hpp" #include "./Buffer.hpp" -class CTexture; +class ITexture; class CDRMSyncPointState; class CWLCallbackResource; @@ -48,6 +50,7 @@ struct SSurfaceState { bool acquire : 1; bool acked : 1; bool frame : 1; + bool fifo : 1; } bits; } updated; @@ -85,8 +88,17 @@ struct SSurfaceState { eLockReason lockMask = LOCK_REASON_NONE; // texture of surface content, used for rendering - SP texture; - void updateSynchronousTexture(SP lastTexture); + SP texture; + void updateSynchronousTexture(SP lastTexture); + + // fifo + bool barrierSet = false; + bool surfaceLocked = false; + bool fifoScheduled = false; + + // commit timing + std::optional pendingTimeout; + SP timer; // helpers CRegion accumulateBufferDamage(); // transforms state.damage and merges it into state.bufferDamage diff --git a/src/protocols/types/SurfaceStateQueue.cpp b/src/protocols/types/SurfaceStateQueue.cpp index c10b556f..82a04878 100644 --- a/src/protocols/types/SurfaceStateQueue.cpp +++ b/src/protocols/types/SurfaceStateQueue.cpp @@ -21,6 +21,7 @@ void CSurfaceStateQueue::dropState(const WP& state) { } void CSurfaceStateQueue::lock(const WP& weakState, eLockReason reason) { + ASSERT(reason != LOCK_REASON_NONE); auto it = find(weakState); if (it == m_queue.end()) return; @@ -29,6 +30,7 @@ void CSurfaceStateQueue::lock(const WP& weakState, eLockReason re } void CSurfaceStateQueue::unlock(const WP& state, eLockReason reason) { + ASSERT(reason != LOCK_REASON_NONE); auto it = find(state); if (it == m_queue.end()) return; @@ -38,6 +40,7 @@ void CSurfaceStateQueue::unlock(const WP& state, eLockReason reas } void CSurfaceStateQueue::unlockFirst(eLockReason reason) { + ASSERT(reason != LOCK_REASON_NONE); for (auto& it : m_queue) { if ((it->lockMask & reason) != LOCK_REASON_NONE) { it->lockMask &= ~reason; @@ -65,6 +68,9 @@ auto CSurfaceStateQueue::find(const WP& state) -> std::dequelockMask & LOCK_REASON_FIFO && !m_surface->m_current.barrierSet) + front->lockMask &= ~LOCK_REASON_FIFO; + if (front->lockMask != LOCK_REASON_NONE) return; diff --git a/src/protocols/types/SurfaceStateQueue.hpp b/src/protocols/types/SurfaceStateQueue.hpp index 1841ed20..8d9db7a5 100644 --- a/src/protocols/types/SurfaceStateQueue.hpp +++ b/src/protocols/types/SurfaceStateQueue.hpp @@ -15,7 +15,7 @@ class CSurfaceStateQueue { WP enqueue(UP&& state); void dropState(const WP& state); void lock(const WP& state, eLockReason reason); - void unlock(const WP& state, eLockReason reason = LOCK_REASON_NONE); + void unlock(const WP& state, eLockReason reason); void unlockFirst(eLockReason reason); void tryProcess(); diff --git a/src/render/Framebuffer.cpp b/src/render/Framebuffer.cpp index 98947297..b2ff7e68 100644 --- a/src/render/Framebuffer.cpp +++ b/src/render/Framebuffer.cpp @@ -1,127 +1,30 @@ #include "Framebuffer.hpp" -#include "OpenGL.hpp" -CFramebuffer::CFramebuffer() { - ; -} +IFramebuffer::IFramebuffer(const std::string& name) : m_name(name) {} -bool CFramebuffer::alloc(int w, int h, uint32_t drmFormat) { - bool firstAlloc = false; +bool IFramebuffer::alloc(int w, int h, uint32_t format) { RASSERT((w > 0 && h > 0), "cannot alloc a FB with negative / zero size! (attempted {}x{})", w, h); - uint32_t glFormat = NFormatUtils::drmFormatToGL(drmFormat); - uint32_t glType = NFormatUtils::glFormatToType(glFormat); + const bool sizeChanged = (m_size != Vector2D(w, h)); + const bool formatChanged = (format != m_drmFormat); - if (drmFormat != m_drmFormat || m_size != Vector2D{w, h}) - release(); + if (m_fbAllocated && !sizeChanged && !formatChanged) + return true; - m_drmFormat = drmFormat; - - if (!m_tex) { - m_tex = makeShared(); - m_tex->allocate(); - m_tex->bind(); - m_tex->setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - m_tex->setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - m_tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR); - m_tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); - firstAlloc = true; - } - - if (!m_fbAllocated) { - glGenFramebuffers(1, &m_fb); - m_fbAllocated = true; - firstAlloc = true; - } - - if (firstAlloc || m_size != Vector2D(w, h)) { - m_tex->bind(); - glTexImage2D(GL_TEXTURE_2D, 0, glFormat, w, h, 0, GL_RGBA, glType, nullptr); - glBindFramebuffer(GL_FRAMEBUFFER, m_fb); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_tex->m_texID, 0); - - if (m_stencilTex) { - m_stencilTex->bind(); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, w, h, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_stencilTex->m_texID, 0); - } - - auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - RASSERT((status == GL_FRAMEBUFFER_COMPLETE), "Framebuffer incomplete, couldn't create! (FB status: {}, GL Error: 0x{:x})", status, sc(glGetError())); - - Log::logger->log(Log::DEBUG, "Framebuffer created, status {}", status); - } - - glBindTexture(GL_TEXTURE_2D, 0); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - m_size = Vector2D(w, h); - - return true; + m_size = {w, h}; + m_drmFormat = format; + m_fbAllocated = internalAlloc(w, h, format); + return m_fbAllocated; } -void CFramebuffer::addStencil(SP tex) { - m_stencilTex = tex; - m_stencilTex->bind(); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, m_size.x, m_size.y, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr); - - glBindFramebuffer(GL_FRAMEBUFFER, m_fb); - - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_stencilTex->m_texID, 0); - - auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - RASSERT((status == GL_FRAMEBUFFER_COMPLETE), "Failed adding a stencil to fbo!", status); - - m_stencilTex->unbind(); - glBindFramebuffer(GL_FRAMEBUFFER, 0); -} - -void CFramebuffer::bind() { - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fb); - - if (g_pHyprOpenGL) - g_pHyprOpenGL->setViewport(0, 0, g_pHyprOpenGL->m_renderData.pMonitor->m_pixelSize.x, g_pHyprOpenGL->m_renderData.pMonitor->m_pixelSize.y); - else - glViewport(0, 0, m_size.x, m_size.y); -} - -void CFramebuffer::unbind() { - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); -} - -void CFramebuffer::release() { - if (m_fbAllocated) { - glBindFramebuffer(GL_FRAMEBUFFER, m_fb); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - glDeleteFramebuffers(1, &m_fb); - m_fbAllocated = false; - m_fb = 0; - } - - if (m_tex) - m_tex.reset(); - - m_size = Vector2D(); -} - -CFramebuffer::~CFramebuffer() { - release(); -} - -bool CFramebuffer::isAllocated() { +bool IFramebuffer::isAllocated() { return m_fbAllocated && m_tex; } -SP CFramebuffer::getTexture() { +SP IFramebuffer::getTexture() { return m_tex; } -GLuint CFramebuffer::getFBID() { - return m_fbAllocated ? m_fb : 0; -} - -SP CFramebuffer::getStencilTex() { +SP IFramebuffer::getStencilTex() { return m_stencilTex; } diff --git a/src/render/Framebuffer.hpp b/src/render/Framebuffer.hpp index 0e18df5f..7e33f227 100644 --- a/src/render/Framebuffer.hpp +++ b/src/render/Framebuffer.hpp @@ -3,32 +3,38 @@ #include "../defines.hpp" #include "../helpers/Format.hpp" #include "Texture.hpp" +#include +#include -class CFramebuffer { +class CHLBufferReference; + +class IFramebuffer { public: - CFramebuffer(); - ~CFramebuffer(); + IFramebuffer() = default; + IFramebuffer(const std::string& name); + virtual ~IFramebuffer() = default; + + virtual bool alloc(int w, int h, uint32_t format = DRM_FORMAT_ARGB8888); + virtual void release() = 0; + virtual bool readPixels(CHLBufferReference buffer, uint32_t offsetX = 0, uint32_t offsetY = 0, uint32_t width = 0, uint32_t height = 0) = 0; + + virtual void bind() = 0; - bool alloc(int w, int h, uint32_t format = GL_RGBA); - void addStencil(SP tex); - void bind(); - void unbind(); - void release(); - void reset(); bool isAllocated(); - SP getTexture(); - SP getStencilTex(); - GLuint getFBID(); + SP getTexture(); + SP getStencilTex(); + + virtual void addStencil(SP tex) = 0; Vector2D m_size; - DRMFormat m_drmFormat = 0 /* DRM_FORMAT_INVALID */; + DRMFormat m_drmFormat = DRM_FORMAT_INVALID; - private: - SP m_tex; - GLuint m_fb = -1; + protected: + virtual bool internalAlloc(int w, int h, uint32_t format = DRM_FORMAT_ARGB8888) = 0; + + SP m_tex; bool m_fbAllocated = false; - SP m_stencilTex; - - friend class CRenderbuffer; + SP m_stencilTex; + std::string m_name; // name for logging }; diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index 33e380e1..83ad05ca 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -10,6 +11,7 @@ #include "../Compositor.hpp" #include "../helpers/MiscFunctions.hpp" #include "../helpers/CursorShapes.hpp" +#include "../helpers/TransferFunction.hpp" #include "../config/ConfigValue.hpp" #include "../config/ConfigManager.hpp" #include "../managers/PointerManager.hpp" @@ -18,8 +20,7 @@ #include "../protocols/LayerShell.hpp" #include "../protocols/core/Compositor.hpp" #include "../protocols/ColorManagement.hpp" -#include "../protocols/types/ColorManagement.hpp" -#include "../managers/HookSystemManager.hpp" +#include "../helpers/cm/ColorManagement.hpp" #include "../managers/input/InputManager.hpp" #include "../managers/eventLoop/EventLoopManager.hpp" #include "../managers/CursorManager.hpp" @@ -27,8 +28,11 @@ #include "../helpers/env/Env.hpp" #include "../helpers/MainLoopExecutor.hpp" #include "../i18n/Engine.hpp" +#include "../event/EventBus.hpp" +#include "../managers/screenshare/ScreenshareManager.hpp" #include "debug/HyprNotificationOverlay.hpp" #include "hyprerror/HyprError.hpp" +#include "macros.hpp" #include "pass/TexPassElement.hpp" #include "pass/RectPassElement.hpp" #include "pass/PreBlurElement.hpp" @@ -42,19 +46,16 @@ #include #include #include +#include +#include "ShaderLoader.hpp" +#include "Texture.hpp" #include -#include "./shaders/Shaders.hpp" +#include "gl/GLFramebuffer.hpp" +#include "gl/GLTexture.hpp" using namespace Hyprutils::OS; using namespace NColorManagement; - -const std::vector ASSET_PATHS = { -#ifdef DATAROOTDIR - DATAROOTDIR, -#endif - "/usr/share", - "/usr/local/share", -}; +using namespace Render; static inline void loadGLProc(void* pProc, const char* name) { void* proc = rc(eglGetProcAddress(name)); @@ -159,6 +160,7 @@ void CHyprOpenGLImpl::initEGL(bool gbm) { m_exts.EXT_create_context_robustness = EGLEXTENSIONS.contains("EXT_create_context_robustness"); m_exts.EXT_image_dma_buf_import = EGLEXTENSIONS.contains("EXT_image_dma_buf_import"); m_exts.EXT_image_dma_buf_import_modifiers = EGLEXTENSIONS.contains("EXT_image_dma_buf_import_modifiers"); + m_exts.KHR_context_flush_control = EGLEXTENSIONS.contains("EGL_KHR_context_flush_control"); if (m_exts.IMG_context_priority) { Log::logger->log(Log::DEBUG, "EGL: IMG_context_priority supported, requesting high"); @@ -172,6 +174,12 @@ void CHyprOpenGLImpl::initEGL(bool gbm) { attrs.push_back(EGL_LOSE_CONTEXT_ON_RESET_EXT); } + if (m_exts.KHR_context_flush_control) { + Log::logger->log(Log::DEBUG, "EGL: Using KHR_context_flush_control"); + attrs.push_back(EGL_CONTEXT_RELEASE_BEHAVIOR_KHR); + attrs.push_back(EGL_CONTEXT_RELEASE_BEHAVIOR_NONE_KHR); // or _FLUSH_KHR + } + auto attrsNoVer = attrs; attrs.push_back(EGL_CONTEXT_MAJOR_VERSION); @@ -296,7 +304,8 @@ CHyprOpenGLImpl::CHyprOpenGLImpl() : m_drmFD(g_pCompositor->m_drmRenderNode.fd > loadGLProc(&m_proc.eglQueryDisplayAttribEXT, "eglQueryDisplayAttribEXT"); } - if (EGLEXTENSIONS.contains("EGL_KHR_debug")) { + static const auto GLDEBUG = CConfigValue("debug:gl_debugging"); + if (EGLEXTENSIONS.contains("EGL_KHR_debug") && *GLDEBUG) { loadGLProc(&m_proc.eglDebugMessageControlKHR, "eglDebugMessageControlKHR"); static const EGLAttrib debugAttrs[] = { EGL_DEBUG_MSG_CRITICAL_KHR, EGL_TRUE, EGL_DEBUG_MSG_ERROR_KHR, EGL_TRUE, EGL_DEBUG_MSG_WARN_KHR, EGL_TRUE, EGL_DEBUG_MSG_INFO_KHR, EGL_TRUE, EGL_NONE, @@ -382,7 +391,7 @@ CHyprOpenGLImpl::CHyprOpenGLImpl() : m_drmFD(g_pCompositor->m_drmRenderNode.fd > initAssets(); - static auto P = g_pHookSystem->hookDynamic("preRender", [&](void* self, SCallbackInfo& info, std::any data) { preRender(std::any_cast(data)); }); + static auto P = Event::bus()->m_events.render.pre.listen([&](PHLMONITOR mon) { preRender(mon); }); RASSERT(eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT), "Couldn't unset current EGL!"); @@ -413,26 +422,24 @@ CHyprOpenGLImpl::CHyprOpenGLImpl() : m_drmFD(g_pCompositor->m_drmRenderNode.fd > #endif }; - static auto P2 = g_pHookSystem->hookDynamic("mouseButton", [](void* self, SCallbackInfo& info, std::any e) { - auto E = std::any_cast(e); - - if (E.state != WL_POINTER_BUTTON_STATE_PRESSED) + static auto P2 = Event::bus()->m_events.input.mouse.button.listen([](IPointer::SButtonEvent e, Event::SCallbackInfo&) { + if (e.state != WL_POINTER_BUTTON_STATE_PRESSED) return; addLastPressToHistory(g_pInputManager->getMouseCoordsInternal(), g_pInputManager->getClickMode() == CLICKMODE_KILL, false); }); - static auto P3 = g_pHookSystem->hookDynamic("touchDown", [](void* self, SCallbackInfo& info, std::any e) { - auto E = std::any_cast(e); - - auto PMONITOR = g_pCompositor->getMonitorFromName(!E.device->m_boundOutput.empty() ? E.device->m_boundOutput : ""); + static auto P3 = Event::bus()->m_events.input.touch.down.listen([](ITouch::SDownEvent e, Event::SCallbackInfo&) { + auto PMONITOR = g_pCompositor->getMonitorFromName(!e.device->m_boundOutput.empty() ? e.device->m_boundOutput : ""); PMONITOR = PMONITOR ? PMONITOR : Desktop::focusState()->monitor(); - const auto TOUCH_COORDS = PMONITOR->m_position + (E.pos * PMONITOR->m_size); + const auto TOUCH_COORDS = PMONITOR->m_position + (e.pos * PMONITOR->m_size); addLastPressToHistory(TOUCH_COORDS, g_pInputManager->getClickMode() == CLICKMODE_KILL, true); }); + + m_finalScreenShader = makeShared(); } CHyprOpenGLImpl::~CHyprOpenGLImpl() { @@ -639,95 +646,7 @@ EGLImageKHR CHyprOpenGLImpl::createEGLImage(const Aquamarine::SDMABUFAttrs& attr return image; } -void CHyprOpenGLImpl::logShaderError(const GLuint& shader, bool program, bool silent) { - GLint maxLength = 0; - if (program) - glGetProgramiv(shader, GL_INFO_LOG_LENGTH, &maxLength); - else - glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength); - - std::vector errorLog(maxLength); - if (program) - glGetProgramInfoLog(shader, maxLength, &maxLength, errorLog.data()); - else - glGetShaderInfoLog(shader, maxLength, &maxLength, errorLog.data()); - std::string errorStr(errorLog.begin(), errorLog.end()); - - const auto FULLERROR = (program ? "Screen shader parser: Error linking program:" : "Screen shader parser: Error compiling shader: ") + errorStr; - - Log::logger->log(Log::ERR, "Failed to link shader: {}", FULLERROR); - - if (!silent) - g_pConfigManager->addParseError(FULLERROR); -} - -GLuint CHyprOpenGLImpl::createProgram(const std::string& vert, const std::string& frag, bool dynamic, bool silent) { - auto vertCompiled = compileShader(GL_VERTEX_SHADER, vert, dynamic, silent); - if (dynamic) { - if (vertCompiled == 0) - return 0; - } else - RASSERT(vertCompiled, "Compiling shader failed. VERTEX nullptr! Shader source:\n\n{}", vert); - - auto fragCompiled = compileShader(GL_FRAGMENT_SHADER, frag, dynamic, silent); - if (dynamic) { - if (fragCompiled == 0) - return 0; - } else - RASSERT(fragCompiled, "Compiling shader failed. FRAGMENT nullptr! Shader source:\n\n{}", frag); - - auto prog = glCreateProgram(); - glAttachShader(prog, vertCompiled); - glAttachShader(prog, fragCompiled); - glLinkProgram(prog); - - glDetachShader(prog, vertCompiled); - glDetachShader(prog, fragCompiled); - glDeleteShader(vertCompiled); - glDeleteShader(fragCompiled); - - GLint ok; - glGetProgramiv(prog, GL_LINK_STATUS, &ok); - if (dynamic) { - if (ok == GL_FALSE) { - logShaderError(prog, true, silent); - return 0; - } - } else { - if (ok != GL_TRUE) - logShaderError(prog, true); - RASSERT(ok != GL_FALSE, "createProgram() failed! GL_LINK_STATUS not OK!"); - } - - return prog; -} - -GLuint CHyprOpenGLImpl::compileShader(const GLuint& type, std::string src, bool dynamic, bool silent) { - auto shader = glCreateShader(type); - - auto shaderSource = src.c_str(); - - glShaderSource(shader, 1, &shaderSource, nullptr); - glCompileShader(shader); - - GLint ok; - glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); - - if (dynamic) { - if (ok == GL_FALSE) { - logShaderError(shader, false, silent); - return 0; - } - } else { - if (ok != GL_TRUE) - logShaderError(shader, false); - RASSERT(ok != GL_FALSE, "compileShader() failed! GL_COMPILE_STATUS not OK!"); - } - - return shader; -} - -void CHyprOpenGLImpl::beginSimple(PHLMONITOR pMonitor, const CRegion& damage, SP rb, CFramebuffer* fb) { +void CHyprOpenGLImpl::beginSimple(PHLMONITOR pMonitor, const CRegion& damage, SP rb, SP fb) { m_renderData.pMonitor = pMonitor; const GLenum RESETSTATUS = glGetGraphicsResetStatus(); @@ -762,6 +681,7 @@ void CHyprOpenGLImpl::beginSimple(PHLMONITOR pMonitor, const CRegion& damage, SP if (!m_shadersInitialized) initShaders(); + m_renderData.transformDamage = true; m_renderData.damage.set(damage); m_renderData.finalDamage.set(damage); @@ -779,7 +699,7 @@ void CHyprOpenGLImpl::beginSimple(PHLMONITOR pMonitor, const CRegion& damage, SP pushMonitorTransformEnabled(false); } -void CHyprOpenGLImpl::begin(PHLMONITOR pMonitor, const CRegion& damage_, CFramebuffer* fb, std::optional finalDamage) { +void CHyprOpenGLImpl::begin(PHLMONITOR pMonitor, const CRegion& damage_, SP fb, std::optional finalDamage) { m_renderData.pMonitor = pMonitor; const GLenum RESETSTATUS = glGetGraphicsResetStatus(); @@ -803,7 +723,8 @@ void CHyprOpenGLImpl::begin(PHLMONITOR pMonitor, const CRegion& damage_, CFrameb m_renderData.monitorProjection = pMonitor->m_projMatrix; - if (m_monitorRenderResources.contains(pMonitor) && m_monitorRenderResources.at(pMonitor).offloadFB.m_size != pMonitor->m_pixelSize) + if (m_monitorRenderResources.contains(pMonitor) && + (!m_monitorRenderResources.at(pMonitor).offloadFB || m_monitorRenderResources.at(pMonitor).offloadFB->m_size != pMonitor->m_pixelSize)) destroyMonitorResources(pMonitor); m_renderData.pCurrentMonData = &m_monitorRenderResources[pMonitor]; @@ -814,23 +735,40 @@ void CHyprOpenGLImpl::begin(PHLMONITOR pMonitor, const CRegion& damage_, CFrameb const auto DRM_FORMAT = fb ? fb->m_drmFormat : pMonitor->m_output->state->state().drmFormat; // ensure a framebuffer for the monitor exists - if (m_renderData.pCurrentMonData->offloadFB.m_size != pMonitor->m_pixelSize || DRM_FORMAT != m_renderData.pCurrentMonData->offloadFB.m_drmFormat) { - m_renderData.pCurrentMonData->stencilTex->allocate(); + if (!m_renderData.pCurrentMonData->offloadFB || m_renderData.pCurrentMonData->offloadFB->m_size != pMonitor->m_pixelSize || + DRM_FORMAT != m_renderData.pCurrentMonData->offloadFB->m_drmFormat) { + m_renderData.pCurrentMonData->stencilTex = g_pHyprRenderer->createStencilTexture(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y); + m_renderData.pCurrentMonData->offloadFB = g_pHyprRenderer->createFB(); + m_renderData.pCurrentMonData->mirrorFB = g_pHyprRenderer->createFB(); + m_renderData.pCurrentMonData->mirrorSwapFB = g_pHyprRenderer->createFB(); - m_renderData.pCurrentMonData->offloadFB.alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, DRM_FORMAT); - m_renderData.pCurrentMonData->mirrorFB.alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, DRM_FORMAT); - m_renderData.pCurrentMonData->mirrorSwapFB.alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, DRM_FORMAT); + m_renderData.pCurrentMonData->offloadFB->addStencil(m_renderData.pCurrentMonData->stencilTex); + m_renderData.pCurrentMonData->mirrorFB->addStencil(m_renderData.pCurrentMonData->stencilTex); + m_renderData.pCurrentMonData->mirrorSwapFB->addStencil(m_renderData.pCurrentMonData->stencilTex); - m_renderData.pCurrentMonData->offloadFB.addStencil(m_renderData.pCurrentMonData->stencilTex); - m_renderData.pCurrentMonData->mirrorFB.addStencil(m_renderData.pCurrentMonData->stencilTex); - m_renderData.pCurrentMonData->mirrorSwapFB.addStencil(m_renderData.pCurrentMonData->stencilTex); + m_renderData.pCurrentMonData->offloadFB->alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, DRM_FORMAT); + m_renderData.pCurrentMonData->mirrorFB->alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, DRM_FORMAT); + m_renderData.pCurrentMonData->mirrorSwapFB->alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, DRM_FORMAT); } - if (m_renderData.pCurrentMonData->monitorMirrorFB.isAllocated() && m_renderData.pMonitor->m_mirrors.empty()) - m_renderData.pCurrentMonData->monitorMirrorFB.release(); + const bool HAS_MIRROR_FB = m_renderData.pCurrentMonData->monitorMirrorFB && m_renderData.pCurrentMonData->monitorMirrorFB->isAllocated(); + const bool NEEDS_COPY_FB = needsACopyFB(m_renderData.pMonitor.lock()); - m_renderData.damage.set(damage_); - m_renderData.finalDamage.set(finalDamage.value_or(damage_)); + if (HAS_MIRROR_FB && !NEEDS_COPY_FB) + m_renderData.pCurrentMonData->monitorMirrorFB->release(); + else if (!HAS_MIRROR_FB && NEEDS_COPY_FB && m_renderData.pCurrentMonData->monitorMirrorFB) + m_renderData.pCurrentMonData->monitorMirrorFB->alloc(m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y, + m_renderData.pMonitor->m_output->state->state().drmFormat); + + m_renderData.transformDamage = true; + if (HAS_MIRROR_FB != NEEDS_COPY_FB) { + // force full damage because the mirror fb will be empty + m_renderData.damage.set({0, 0, INT32_MAX, INT32_MAX}); + m_renderData.finalDamage.set(m_renderData.damage); + } else { + m_renderData.damage.set(damage_); + m_renderData.finalDamage.set(finalDamage.value_or(damage_)); + } m_fakeFrame = fb; @@ -840,8 +778,8 @@ void CHyprOpenGLImpl::begin(PHLMONITOR pMonitor, const CRegion& damage_, CFrameb applyScreenShader(*PSHADER); } - m_renderData.pCurrentMonData->offloadFB.bind(); - m_renderData.currentFB = &m_renderData.pCurrentMonData->offloadFB; + m_renderData.pCurrentMonData->offloadFB->bind(); + m_renderData.currentFB = m_renderData.pCurrentMonData->offloadFB; m_offloadedFramebuffer = true; m_renderData.mainFB = m_renderData.currentFB; @@ -855,33 +793,42 @@ void CHyprOpenGLImpl::end() { TRACY_GPU_ZONE("RenderEnd"); + m_renderData.currentWindow.reset(); + m_renderData.surface.reset(); + m_renderData.currentLS.reset(); + m_renderData.clipBox = {}; + m_renderData.clipRegion.clear(); + // end the render, copy the data to the main framebuffer - if (m_offloadedFramebuffer) { + if LIKELY (m_offloadedFramebuffer) { m_renderData.damage = m_renderData.finalDamage; pushMonitorTransformEnabled(true); CBox monbox = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y}; - if (g_pHyprRenderer->m_renderMode == RENDER_MODE_NORMAL && m_renderData.mouseZoomFactor == 1.0f) + if LIKELY (g_pHyprRenderer->m_renderMode == RENDER_MODE_NORMAL && m_renderData.mouseZoomFactor == 1.0f) m_renderData.pMonitor->m_zoomController.m_resetCameraState = true; m_renderData.pMonitor->m_zoomController.applyZoomTransform(monbox, m_renderData); m_applyFinalShader = !m_renderData.blockScreenShader; - if (m_renderData.mouseZoomUseMouse && *PZOOMDISABLEAA) + if UNLIKELY (m_renderData.mouseZoomFactor != 1.F && m_renderData.mouseZoomUseMouse && *PZOOMDISABLEAA) m_renderData.useNearestNeighbor = true; // copy the damaged areas into the mirror buffer - // we can't use the offloadFB for mirroring, as it contains artifacts from blurring - if (!m_renderData.pMonitor->m_mirrors.empty() && !m_fakeFrame) + // we can't use the offloadFB for mirroring / ss, as it contains artifacts from blurring + if UNLIKELY (needsACopyFB(m_renderData.pMonitor.lock()) && !m_fakeFrame) saveBufferForMirror(monbox); m_renderData.outFB->bind(); blend(false); - if (m_finalScreenShader.program < 1 && !g_pHyprRenderer->m_crashingInProgress) - renderTexturePrimitive(m_renderData.pCurrentMonData->offloadFB.getTexture(), monbox); - else - renderTexture(m_renderData.pCurrentMonData->offloadFB.getTexture(), monbox, {}); + const auto PRIMITIVE_BLOCKED = + m_finalScreenShader->program() >= 1 || g_pHyprRenderer->m_crashingInProgress || m_renderData.pMonitor->m_imageDescription->value() != SImageDescription{}; + + if LIKELY (!PRIMITIVE_BLOCKED || g_pHyprRenderer->m_renderMode != RENDER_MODE_NORMAL) + renderTexturePrimitive(m_renderData.pCurrentMonData->offloadFB->getTexture(), monbox); + else // we need to use renderTexture if we do any CM whatsoever. + renderTexture(m_renderData.pCurrentMonData->offloadFB->getTexture(), monbox, {.finalMonitorCM = true}); blend(true); @@ -890,6 +837,24 @@ void CHyprOpenGLImpl::end() { popMonitorTransformEnabled(); } + // invalidate our render FBs to signal to the driver we don't need them anymore + if (m_renderData.pCurrentMonData->mirrorFB) { + m_renderData.pCurrentMonData->mirrorFB->bind(); + GLFB(m_renderData.pCurrentMonData->mirrorFB)->invalidate({GL_STENCIL_ATTACHMENT, GL_COLOR_ATTACHMENT0}); + } + if (m_renderData.pCurrentMonData->mirrorSwapFB) { + m_renderData.pCurrentMonData->mirrorSwapFB->bind(); + GLFB(m_renderData.pCurrentMonData->mirrorSwapFB)->invalidate({GL_STENCIL_ATTACHMENT, GL_COLOR_ATTACHMENT0}); + } + if (m_renderData.pCurrentMonData->offloadFB) { + m_renderData.pCurrentMonData->offloadFB->bind(); + GLFB(m_renderData.pCurrentMonData->offloadFB)->invalidate({GL_STENCIL_ATTACHMENT, GL_COLOR_ATTACHMENT0}); + } + if (m_renderData.pCurrentMonData->offMainFB) { + m_renderData.pCurrentMonData->offMainFB->bind(); + GLFB(m_renderData.pCurrentMonData->offMainFB)->invalidate({GL_STENCIL_ATTACHMENT, GL_COLOR_ATTACHMENT0}); + } + // reset our data m_renderData.pMonitor.reset(); m_renderData.mouseZoomFactor = 1.f; @@ -903,14 +868,22 @@ void CHyprOpenGLImpl::end() { // if we dropped to offMain, release it now. // if there is a plugin constantly using it, this might be a bit slow, // but I haven't seen a single plugin yet use these, so it's better to drop a bit of vram. - if (m_renderData.pCurrentMonData->offMainFB.isAllocated()) - m_renderData.pCurrentMonData->offMainFB.release(); + if UNLIKELY (m_renderData.pCurrentMonData->offMainFB && m_renderData.pCurrentMonData->offMainFB->isAllocated()) + m_renderData.pCurrentMonData->offMainFB->release(); - // check for gl errors - const GLenum ERR = glGetError(); + static const auto GLDEBUG = CConfigValue("debug:gl_debugging"); - if (ERR == GL_CONTEXT_LOST) /* We don't have infra to recover from this */ - RASSERT(false, "glGetError at Opengl::end() returned GL_CONTEXT_LOST. Cannot continue until proper GPU reset handling is implemented."); + if (*GLDEBUG) { + // check for gl errors + const GLenum ERR = glGetError(); + + if UNLIKELY (ERR == GL_CONTEXT_LOST) /* We don't have infra to recover from this */ + RASSERT(false, "glGetError at Opengl::end() returned GL_CONTEXT_LOST. Cannot continue until proper GPU reset handling is implemented."); + } +} + +bool CHyprOpenGLImpl::needsACopyFB(PHLMONITOR mon) { + return !mon->m_mirrors.empty() || Screenshare::mgr()->isOutputBeingSSd(mon); } void CHyprOpenGLImpl::setDamage(const CRegion& damage_, std::optional finalDamage) { @@ -918,324 +891,30 @@ void CHyprOpenGLImpl::setDamage(const CRegion& damage_, std::optional f m_renderData.finalDamage.set(finalDamage.value_or(damage_)); } -// TODO notify user if bundled shader is newer than ~/.config override -static std::string loadShader(const std::string& filename) { - const auto home = Hyprutils::Path::getHome(); - if (home.has_value()) { - const auto src = NFsUtils::readFileAsString(home.value() + "/hypr/shaders/" + filename); - if (src.has_value()) - return src.value(); - } - for (auto& e : ASSET_PATHS) { - const auto src = NFsUtils::readFileAsString(std::string{e} + "/hypr/shaders/" + filename); - if (src.has_value()) - return src.value(); - } - if (SHADERS.contains(filename)) - return SHADERS.at(filename); - throw std::runtime_error(std::format("Couldn't load shader {}", filename)); -} +static const std::vector SHADER_INCLUDES = { + "defines.h", "constants.h", "cm_helpers.glsl", "rounding.glsl", "CM.glsl", "tonemap.glsl", "gain.glsl", + "border.glsl", "shadow.glsl", "blurprepare.glsl", "blur1.glsl", "blur2.glsl", "blurFinish.glsl", +}; -static void loadShaderInclude(const std::string& filename, std::map& includes) { - includes.insert({filename, loadShader(filename)}); -} +// order matters, see ePreparedFragmentShader +const std::array FRAG_SHADERS = { + "quad.frag", "passthru.frag", "rgbamatte.frag", "ext.frag", "blur1.frag", "blur2.frag", + "blurprepare.frag", "blurfinish.frag", "shadow.frag", "surface.frag", "border.frag", "glitch.frag", +}; -static void processShaderIncludes(std::string& source, const std::map& includes) { - for (auto it = includes.begin(); it != includes.end(); ++it) { - Hyprutils::String::replaceInString(source, "#include \"" + it->first + "\"", it->second); - } -} - -static std::string processShader(const std::string& filename, const std::map& includes) { - auto source = loadShader(filename); - processShaderIncludes(source, includes); - return source; -} - -// shader has #include "CM.glsl" -static void getCMShaderUniforms(SShader& shader) { - shader.uniformLocations[SHADER_SKIP_CM] = glGetUniformLocation(shader.program, "skipCM"); - shader.uniformLocations[SHADER_SOURCE_TF] = glGetUniformLocation(shader.program, "sourceTF"); - shader.uniformLocations[SHADER_TARGET_TF] = glGetUniformLocation(shader.program, "targetTF"); - shader.uniformLocations[SHADER_SRC_TF_RANGE] = glGetUniformLocation(shader.program, "srcTFRange"); - shader.uniformLocations[SHADER_DST_TF_RANGE] = glGetUniformLocation(shader.program, "dstTFRange"); - shader.uniformLocations[SHADER_TARGET_PRIMARIES] = glGetUniformLocation(shader.program, "targetPrimaries"); - shader.uniformLocations[SHADER_MAX_LUMINANCE] = glGetUniformLocation(shader.program, "maxLuminance"); - shader.uniformLocations[SHADER_SRC_REF_LUMINANCE] = glGetUniformLocation(shader.program, "srcRefLuminance"); - shader.uniformLocations[SHADER_DST_MAX_LUMINANCE] = glGetUniformLocation(shader.program, "dstMaxLuminance"); - shader.uniformLocations[SHADER_DST_REF_LUMINANCE] = glGetUniformLocation(shader.program, "dstRefLuminance"); - shader.uniformLocations[SHADER_SDR_SATURATION] = glGetUniformLocation(shader.program, "sdrSaturation"); - shader.uniformLocations[SHADER_SDR_BRIGHTNESS] = glGetUniformLocation(shader.program, "sdrBrightnessMultiplier"); - shader.uniformLocations[SHADER_CONVERT_MATRIX] = glGetUniformLocation(shader.program, "convertMatrix"); -} - -// shader has #include "rounding.glsl" -static void getRoundingShaderUniforms(SShader& shader) { - shader.uniformLocations[SHADER_TOP_LEFT] = glGetUniformLocation(shader.program, "topLeft"); - shader.uniformLocations[SHADER_FULL_SIZE] = glGetUniformLocation(shader.program, "fullSize"); - shader.uniformLocations[SHADER_RADIUS] = glGetUniformLocation(shader.program, "radius"); - shader.uniformLocations[SHADER_ROUNDING_POWER] = glGetUniformLocation(shader.program, "roundingPower"); -} - -bool CHyprOpenGLImpl::initShaders() { - auto shaders = makeShared(); - const bool isDynamic = m_shadersInitialized; - static const auto PCM = CConfigValue("render:cm_enabled"); +bool CHyprOpenGLImpl::initShaders(const std::string& path) { + auto shaders = makeShared(); + static const auto PCM = CConfigValue("render:cm_enabled"); try { - std::map includes; - loadShaderInclude("rounding.glsl", includes); - loadShaderInclude("CM.glsl", includes); + auto shaderLoader = makeUnique(SHADER_INCLUDES, FRAG_SHADERS, path); - shaders->TEXVERTSRC = processShader("tex300.vert", includes); - shaders->TEXVERTSRC320 = processShader("tex320.vert", includes); + shaders->TEXVERTSRC = shaderLoader->process("tex300.vert"); + shaders->TEXVERTSRC320 = shaderLoader->process("tex320.vert"); - GLuint prog; + m_cmSupported = *PCM; - if (!*PCM) - m_cmSupported = false; - else { - const auto TEXFRAGSRCCM = processShader("CM.frag", includes); - - prog = createProgram(shaders->TEXVERTSRC, TEXFRAGSRCCM, true, true); - if (m_shadersInitialized && m_cmSupported && prog == 0) - g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_CM_RELOAD_FAILED), CHyprColor{}, 15000, ICON_WARNING); - - m_cmSupported = prog > 0; - if (m_cmSupported) { - shaders->m_shCM.program = prog; - getCMShaderUniforms(shaders->m_shCM); - getRoundingShaderUniforms(shaders->m_shCM); - shaders->m_shCM.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shCM.uniformLocations[SHADER_TEX] = glGetUniformLocation(prog, "tex"); - shaders->m_shCM.uniformLocations[SHADER_TEX_TYPE] = glGetUniformLocation(prog, "texType"); - shaders->m_shCM.uniformLocations[SHADER_ALPHA_MATTE] = glGetUniformLocation(prog, "texMatte"); - shaders->m_shCM.uniformLocations[SHADER_ALPHA] = glGetUniformLocation(prog, "alpha"); - shaders->m_shCM.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoord"); - shaders->m_shCM.uniformLocations[SHADER_MATTE_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoordMatte"); - shaders->m_shCM.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shCM.uniformLocations[SHADER_DISCARD_OPAQUE] = glGetUniformLocation(prog, "discardOpaque"); - shaders->m_shCM.uniformLocations[SHADER_DISCARD_ALPHA] = glGetUniformLocation(prog, "discardAlpha"); - shaders->m_shCM.uniformLocations[SHADER_DISCARD_ALPHA_VALUE] = glGetUniformLocation(prog, "discardAlphaValue"); - shaders->m_shCM.uniformLocations[SHADER_APPLY_TINT] = glGetUniformLocation(prog, "applyTint"); - shaders->m_shCM.uniformLocations[SHADER_TINT] = glGetUniformLocation(prog, "tint"); - shaders->m_shCM.uniformLocations[SHADER_USE_ALPHA_MATTE] = glGetUniformLocation(prog, "useAlphaMatte"); - shaders->m_shCM.createVao(); - } else - Log::logger->log( - Log::ERR, - "WARNING: CM Shader failed compiling, color management will not work. It's likely because your GPU is an old piece of garbage, don't file bug reports " - "about this!"); - } - - const auto FRAGSHADOW = processShader("shadow.frag", includes); - const auto FRAGBORDER1 = processShader("border.frag", includes); - const auto FRAGBLURPREPARE = processShader("blurprepare.frag", includes); - const auto FRAGBLURFINISH = processShader("blurfinish.frag", includes); - const auto QUADFRAGSRC = processShader("quad.frag", includes); - const auto TEXFRAGSRCRGBA = processShader("rgba.frag", includes); - const auto TEXFRAGSRCRGBAPASSTHRU = processShader("passthru.frag", includes); - const auto TEXFRAGSRCRGBAMATTE = processShader("rgbamatte.frag", includes); - const auto FRAGGLITCH = processShader("glitch.frag", includes); - const auto TEXFRAGSRCRGBX = processShader("rgbx.frag", includes); - const auto TEXFRAGSRCEXT = processShader("ext.frag", includes); - const auto FRAGBLUR1 = processShader("blur1.frag", includes); - const auto FRAGBLUR2 = processShader("blur2.frag", includes); - - prog = createProgram(shaders->TEXVERTSRC, QUADFRAGSRC, isDynamic); - if (!prog) - return false; - shaders->m_shQUAD.program = prog; - getRoundingShaderUniforms(shaders->m_shQUAD); - shaders->m_shQUAD.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shQUAD.uniformLocations[SHADER_COLOR] = glGetUniformLocation(prog, "color"); - shaders->m_shQUAD.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shQUAD.createVao(); - - prog = createProgram(shaders->TEXVERTSRC, TEXFRAGSRCRGBA, isDynamic); - if (!prog) - return false; - shaders->m_shRGBA.program = prog; - getRoundingShaderUniforms(shaders->m_shRGBA); - shaders->m_shRGBA.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shRGBA.uniformLocations[SHADER_TEX] = glGetUniformLocation(prog, "tex"); - shaders->m_shRGBA.uniformLocations[SHADER_ALPHA_MATTE] = glGetUniformLocation(prog, "texMatte"); - shaders->m_shRGBA.uniformLocations[SHADER_ALPHA] = glGetUniformLocation(prog, "alpha"); - shaders->m_shRGBA.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoord"); - shaders->m_shRGBA.uniformLocations[SHADER_MATTE_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoordMatte"); - shaders->m_shRGBA.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shRGBA.uniformLocations[SHADER_DISCARD_OPAQUE] = glGetUniformLocation(prog, "discardOpaque"); - shaders->m_shRGBA.uniformLocations[SHADER_DISCARD_ALPHA] = glGetUniformLocation(prog, "discardAlpha"); - shaders->m_shRGBA.uniformLocations[SHADER_DISCARD_ALPHA_VALUE] = glGetUniformLocation(prog, "discardAlphaValue"); - shaders->m_shRGBA.uniformLocations[SHADER_APPLY_TINT] = glGetUniformLocation(prog, "applyTint"); - shaders->m_shRGBA.uniformLocations[SHADER_TINT] = glGetUniformLocation(prog, "tint"); - shaders->m_shRGBA.uniformLocations[SHADER_USE_ALPHA_MATTE] = glGetUniformLocation(prog, "useAlphaMatte"); - shaders->m_shRGBA.createVao(); - - prog = createProgram(shaders->TEXVERTSRC, TEXFRAGSRCRGBAPASSTHRU, isDynamic); - if (!prog) - return false; - shaders->m_shPASSTHRURGBA.program = prog; - shaders->m_shPASSTHRURGBA.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shPASSTHRURGBA.uniformLocations[SHADER_TEX] = glGetUniformLocation(prog, "tex"); - shaders->m_shPASSTHRURGBA.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoord"); - shaders->m_shPASSTHRURGBA.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shPASSTHRURGBA.createVao(); - - prog = createProgram(shaders->TEXVERTSRC, TEXFRAGSRCRGBAMATTE, isDynamic); - if (!prog) - return false; - shaders->m_shMATTE.program = prog; - shaders->m_shMATTE.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shMATTE.uniformLocations[SHADER_TEX] = glGetUniformLocation(prog, "tex"); - shaders->m_shMATTE.uniformLocations[SHADER_ALPHA_MATTE] = glGetUniformLocation(prog, "texMatte"); - shaders->m_shMATTE.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoord"); - shaders->m_shMATTE.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shMATTE.createVao(); - - prog = createProgram(shaders->TEXVERTSRC, FRAGGLITCH, isDynamic); - if (!prog) - return false; - shaders->m_shGLITCH.program = prog; - shaders->m_shGLITCH.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shGLITCH.uniformLocations[SHADER_TEX] = glGetUniformLocation(prog, "tex"); - shaders->m_shGLITCH.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoord"); - shaders->m_shGLITCH.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shGLITCH.uniformLocations[SHADER_DISTORT] = glGetUniformLocation(prog, "distort"); - shaders->m_shGLITCH.uniformLocations[SHADER_TIME] = glGetUniformLocation(prog, "time"); - shaders->m_shGLITCH.uniformLocations[SHADER_FULL_SIZE] = glGetUniformLocation(prog, "screenSize"); - shaders->m_shGLITCH.createVao(); - - prog = createProgram(shaders->TEXVERTSRC, TEXFRAGSRCRGBX, isDynamic); - if (!prog) - return false; - shaders->m_shRGBX.program = prog; - getRoundingShaderUniforms(shaders->m_shRGBX); - shaders->m_shRGBX.uniformLocations[SHADER_TEX] = glGetUniformLocation(prog, "tex"); - shaders->m_shRGBX.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shRGBX.uniformLocations[SHADER_ALPHA] = glGetUniformLocation(prog, "alpha"); - shaders->m_shRGBX.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoord"); - shaders->m_shRGBX.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shRGBX.uniformLocations[SHADER_DISCARD_OPAQUE] = glGetUniformLocation(prog, "discardOpaque"); - shaders->m_shRGBX.uniformLocations[SHADER_DISCARD_ALPHA] = glGetUniformLocation(prog, "discardAlpha"); - shaders->m_shRGBX.uniformLocations[SHADER_DISCARD_ALPHA_VALUE] = glGetUniformLocation(prog, "discardAlphaValue"); - shaders->m_shRGBX.uniformLocations[SHADER_APPLY_TINT] = glGetUniformLocation(prog, "applyTint"); - shaders->m_shRGBX.uniformLocations[SHADER_TINT] = glGetUniformLocation(prog, "tint"); - shaders->m_shRGBX.createVao(); - - prog = createProgram(shaders->TEXVERTSRC, TEXFRAGSRCEXT, isDynamic); - if (!prog) - return false; - shaders->m_shEXT.program = prog; - getRoundingShaderUniforms(shaders->m_shEXT); - shaders->m_shEXT.uniformLocations[SHADER_TEX] = glGetUniformLocation(prog, "tex"); - shaders->m_shEXT.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shEXT.uniformLocations[SHADER_ALPHA] = glGetUniformLocation(prog, "alpha"); - shaders->m_shEXT.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shEXT.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoord"); - shaders->m_shEXT.uniformLocations[SHADER_DISCARD_OPAQUE] = glGetUniformLocation(prog, "discardOpaque"); - shaders->m_shEXT.uniformLocations[SHADER_DISCARD_ALPHA] = glGetUniformLocation(prog, "discardAlpha"); - shaders->m_shEXT.uniformLocations[SHADER_DISCARD_ALPHA_VALUE] = glGetUniformLocation(prog, "discardAlphaValue"); - shaders->m_shEXT.uniformLocations[SHADER_APPLY_TINT] = glGetUniformLocation(prog, "applyTint"); - shaders->m_shEXT.uniformLocations[SHADER_TINT] = glGetUniformLocation(prog, "tint"); - shaders->m_shEXT.createVao(); - - prog = createProgram(shaders->TEXVERTSRC, FRAGBLUR1, isDynamic); - if (!prog) - return false; - shaders->m_shBLUR1.program = prog; - shaders->m_shBLUR1.uniformLocations[SHADER_TEX] = glGetUniformLocation(prog, "tex"); - shaders->m_shBLUR1.uniformLocations[SHADER_ALPHA] = glGetUniformLocation(prog, "alpha"); - shaders->m_shBLUR1.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shBLUR1.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shBLUR1.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoord"); - shaders->m_shBLUR1.uniformLocations[SHADER_RADIUS] = glGetUniformLocation(prog, "radius"); - shaders->m_shBLUR1.uniformLocations[SHADER_HALFPIXEL] = glGetUniformLocation(prog, "halfpixel"); - shaders->m_shBLUR1.uniformLocations[SHADER_PASSES] = glGetUniformLocation(prog, "passes"); - shaders->m_shBLUR1.uniformLocations[SHADER_VIBRANCY] = glGetUniformLocation(prog, "vibrancy"); - shaders->m_shBLUR1.uniformLocations[SHADER_VIBRANCY_DARKNESS] = glGetUniformLocation(prog, "vibrancy_darkness"); - shaders->m_shBLUR1.createVao(); - - prog = createProgram(shaders->TEXVERTSRC, FRAGBLUR2, isDynamic); - if (!prog) - return false; - shaders->m_shBLUR2.program = prog; - shaders->m_shBLUR2.uniformLocations[SHADER_TEX] = glGetUniformLocation(prog, "tex"); - shaders->m_shBLUR2.uniformLocations[SHADER_ALPHA] = glGetUniformLocation(prog, "alpha"); - shaders->m_shBLUR2.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shBLUR2.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shBLUR2.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoord"); - shaders->m_shBLUR2.uniformLocations[SHADER_RADIUS] = glGetUniformLocation(prog, "radius"); - shaders->m_shBLUR2.uniformLocations[SHADER_HALFPIXEL] = glGetUniformLocation(prog, "halfpixel"); - shaders->m_shBLUR2.createVao(); - - prog = createProgram(shaders->TEXVERTSRC, FRAGBLURPREPARE, isDynamic); - if (!prog) - return false; - shaders->m_shBLURPREPARE.program = prog; - getCMShaderUniforms(shaders->m_shBLURPREPARE); - - shaders->m_shBLURPREPARE.uniformLocations[SHADER_TEX] = glGetUniformLocation(prog, "tex"); - shaders->m_shBLURPREPARE.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shBLURPREPARE.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shBLURPREPARE.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoord"); - shaders->m_shBLURPREPARE.uniformLocations[SHADER_CONTRAST] = glGetUniformLocation(prog, "contrast"); - shaders->m_shBLURPREPARE.uniformLocations[SHADER_BRIGHTNESS] = glGetUniformLocation(prog, "brightness"); - shaders->m_shBLURPREPARE.createVao(); - - prog = createProgram(shaders->TEXVERTSRC, FRAGBLURFINISH, isDynamic); - if (!prog) - return false; - shaders->m_shBLURFINISH.program = prog; - // getCMShaderUniforms(shaders->m_shBLURFINISH); - - shaders->m_shBLURFINISH.uniformLocations[SHADER_TEX] = glGetUniformLocation(prog, "tex"); - shaders->m_shBLURFINISH.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shBLURFINISH.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shBLURFINISH.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoord"); - shaders->m_shBLURFINISH.uniformLocations[SHADER_BRIGHTNESS] = glGetUniformLocation(prog, "brightness"); - shaders->m_shBLURFINISH.uniformLocations[SHADER_NOISE] = glGetUniformLocation(prog, "noise"); - shaders->m_shBLURFINISH.createVao(); - - prog = createProgram(shaders->TEXVERTSRC, FRAGSHADOW, isDynamic); - if (!prog) - return false; - - shaders->m_shSHADOW.program = prog; - getCMShaderUniforms(shaders->m_shSHADOW); - getRoundingShaderUniforms(shaders->m_shSHADOW); - shaders->m_shSHADOW.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shSHADOW.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shSHADOW.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoord"); - shaders->m_shSHADOW.uniformLocations[SHADER_BOTTOM_RIGHT] = glGetUniformLocation(prog, "bottomRight"); - shaders->m_shSHADOW.uniformLocations[SHADER_RANGE] = glGetUniformLocation(prog, "range"); - shaders->m_shSHADOW.uniformLocations[SHADER_SHADOW_POWER] = glGetUniformLocation(prog, "shadowPower"); - shaders->m_shSHADOW.uniformLocations[SHADER_COLOR] = glGetUniformLocation(prog, "color"); - shaders->m_shSHADOW.createVao(); - - prog = createProgram(shaders->TEXVERTSRC, FRAGBORDER1, isDynamic); - if (!prog) - return false; - - shaders->m_shBORDER1.program = prog; - getCMShaderUniforms(shaders->m_shBORDER1); - getRoundingShaderUniforms(shaders->m_shBORDER1); - shaders->m_shBORDER1.uniformLocations[SHADER_PROJ] = glGetUniformLocation(prog, "proj"); - shaders->m_shBORDER1.uniformLocations[SHADER_THICK] = glGetUniformLocation(prog, "thick"); - shaders->m_shBORDER1.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(prog, "pos"); - shaders->m_shBORDER1.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(prog, "texcoord"); - shaders->m_shBORDER1.uniformLocations[SHADER_BOTTOM_RIGHT] = glGetUniformLocation(prog, "bottomRight"); - shaders->m_shBORDER1.uniformLocations[SHADER_FULL_SIZE_UNTRANSFORMED] = glGetUniformLocation(prog, "fullSizeUntransformed"); - shaders->m_shBORDER1.uniformLocations[SHADER_RADIUS_OUTER] = glGetUniformLocation(prog, "radiusOuter"); - shaders->m_shBORDER1.uniformLocations[SHADER_GRADIENT] = glGetUniformLocation(prog, "gradient"); - shaders->m_shBORDER1.uniformLocations[SHADER_GRADIENT2] = glGetUniformLocation(prog, "gradient2"); - shaders->m_shBORDER1.uniformLocations[SHADER_GRADIENT_LENGTH] = glGetUniformLocation(prog, "gradientLength"); - shaders->m_shBORDER1.uniformLocations[SHADER_GRADIENT2_LENGTH] = glGetUniformLocation(prog, "gradient2Length"); - shaders->m_shBORDER1.uniformLocations[SHADER_ANGLE] = glGetUniformLocation(prog, "angle"); - shaders->m_shBORDER1.uniformLocations[SHADER_ANGLE2] = glGetUniformLocation(prog, "angle2"); - shaders->m_shBORDER1.uniformLocations[SHADER_GRADIENT_LERP] = glGetUniformLocation(prog, "gradientLerp"); - shaders->m_shBORDER1.uniformLocations[SHADER_ALPHA] = glGetUniformLocation(prog, "alpha"); - shaders->m_shBORDER1.createVao(); + g_pShaderLoader = std::move(shaderLoader); } catch (const std::exception& e) { if (!m_shadersInitialized) @@ -1256,7 +935,7 @@ void CHyprOpenGLImpl::applyScreenShader(const std::string& path) { static auto PDT = CConfigValue("debug:damage_tracking"); - m_finalScreenShader.destroy(); + m_finalScreenShader->destroy(); if (path.empty() || path == STRVAL_EMPTY) return; @@ -1270,47 +949,23 @@ void CHyprOpenGLImpl::applyScreenShader(const std::string& path) { std::string fragmentShader((std::istreambuf_iterator(infile)), (std::istreambuf_iterator())); - m_finalScreenShader.program = createProgram( // - fragmentShader.starts_with("#version 320 es") // do not break existing custom shaders - ? - m_shaders->TEXVERTSRC320 : - m_shaders->TEXVERTSRC, - fragmentShader, true); - - if (!m_finalScreenShader.program) { + if (!m_finalScreenShader->createProgram( // + fragmentShader.starts_with("#version 320 es") // do not break existing custom shaders + ? + m_shaders->TEXVERTSRC320 : + m_shaders->TEXVERTSRC, + fragmentShader, true)) { // Error will have been sent by now by the underlying cause return; } - m_finalScreenShader.uniformLocations[SHADER_POINTER_HIDDEN] = glGetUniformLocation(m_finalScreenShader.program, "pointer_hidden"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_KILLING] = glGetUniformLocation(m_finalScreenShader.program, "pointer_killing"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_SHAPE] = glGetUniformLocation(m_finalScreenShader.program, "pointer_shape"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_SHAPE_PREVIOUS] = glGetUniformLocation(m_finalScreenShader.program, "pointer_shape_previous"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_SWITCH_TIME] = glGetUniformLocation(m_finalScreenShader.program, "pointer_switch_time"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_PRESSED_POSITIONS] = glGetUniformLocation(m_finalScreenShader.program, "pointer_pressed_positions"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_PRESSED_TIMES] = glGetUniformLocation(m_finalScreenShader.program, "pointer_pressed_times"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_PRESSED_KILLED] = glGetUniformLocation(m_finalScreenShader.program, "pointer_pressed_killed"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_PRESSED_TOUCHED] = glGetUniformLocation(m_finalScreenShader.program, "pointer_pressed_touched"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_INACTIVE_TIMEOUT] = glGetUniformLocation(m_finalScreenShader.program, "pointer_inactive_timeout"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_LAST_ACTIVE] = glGetUniformLocation(m_finalScreenShader.program, "pointer_last_active"); - m_finalScreenShader.uniformLocations[SHADER_POINTER_SIZE] = glGetUniformLocation(m_finalScreenShader.program, "pointer_size"); - m_finalScreenShader.uniformLocations[SHADER_POINTER] = glGetUniformLocation(m_finalScreenShader.program, "pointer_position"); - m_finalScreenShader.uniformLocations[SHADER_PROJ] = glGetUniformLocation(m_finalScreenShader.program, "proj"); - m_finalScreenShader.uniformLocations[SHADER_TEX] = glGetUniformLocation(m_finalScreenShader.program, "tex"); - m_finalScreenShader.uniformLocations[SHADER_TIME] = glGetUniformLocation(m_finalScreenShader.program, "time"); - if (m_finalScreenShader.uniformLocations[SHADER_TIME] != -1) - m_finalScreenShader.initialTime = m_globalTimer.getSeconds(); - m_finalScreenShader.uniformLocations[SHADER_WL_OUTPUT] = glGetUniformLocation(m_finalScreenShader.program, "wl_output"); - m_finalScreenShader.uniformLocations[SHADER_FULL_SIZE] = glGetUniformLocation(m_finalScreenShader.program, "screen_size"); - if (m_finalScreenShader.uniformLocations[SHADER_FULL_SIZE] == -1) - m_finalScreenShader.uniformLocations[SHADER_FULL_SIZE] = glGetUniformLocation(m_finalScreenShader.program, "screenSize"); - m_finalScreenShader.uniformLocations[SHADER_TEX_ATTRIB] = glGetAttribLocation(m_finalScreenShader.program, "texcoord"); - m_finalScreenShader.uniformLocations[SHADER_POS_ATTRIB] = glGetAttribLocation(m_finalScreenShader.program, "pos"); + if (m_finalScreenShader->getUniformLocation(SHADER_TIME) != -1) + m_finalScreenShader->setInitialTime(m_globalTimer.getSeconds()); static auto uniformRequireNoDamage = [this](eShaderUniform uniform, const std::string& name) { if (*PDT == 0) return; - if (m_finalScreenShader.uniformLocations[uniform] == -1) + if (m_finalScreenShader->getUniformLocation(uniform) == -1) return; // The screen shader uses the uniform @@ -1334,8 +989,6 @@ void CHyprOpenGLImpl::applyScreenShader(const std::string& path) { uniformRequireNoDamage(SHADER_POINTER_KILLING, "pointer_killing"); uniformRequireNoDamage(SHADER_POINTER_SHAPE, "pointer_shape"); uniformRequireNoDamage(SHADER_POINTER_SHAPE_PREVIOUS, "pointer_shape_previous"); - - m_finalScreenShader.createVao(); } void CHyprOpenGLImpl::clear(const CHyprColor& color) { @@ -1343,22 +996,20 @@ void CHyprOpenGLImpl::clear(const CHyprColor& color) { TRACY_GPU_ZONE("RenderClear"); - glClearColor(color.r, color.g, color.b, color.a); + GLCALL(glClearColor(color.r, color.g, color.b, color.a)); if (!m_renderData.damage.empty()) { m_renderData.damage.forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, m_renderData.transformDamage); glClear(GL_COLOR_BUFFER_BIT); }); } - - scissor(nullptr); } void CHyprOpenGLImpl::blend(bool enabled) { if (enabled) { setCapStatus(GL_BLEND, true); - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // everything is premultiplied + GLCALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); // everything is premultiplied } else setCapStatus(GL_BLEND, false); @@ -1377,7 +1028,7 @@ void CHyprOpenGLImpl::scissor(const CBox& originalBox, bool transform) { box.transform(TR, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y); if (box != m_lastScissorBox) { - glScissor(box.x, box.y, box.width, box.height); + GLCALL(glScissor(box.x, box.y, box.width, box.height)); m_lastScissorBox = box; } @@ -1386,7 +1037,7 @@ void CHyprOpenGLImpl::scissor(const CBox& originalBox, bool transform) { } if (originalBox != m_lastScissorBox) { - glScissor(originalBox.x, originalBox.y, originalBox.width, originalBox.height); + GLCALL(glScissor(originalBox.x, originalBox.y, originalBox.width, originalBox.height)); m_lastScissorBox = originalBox; } @@ -1428,44 +1079,19 @@ void CHyprOpenGLImpl::renderRectWithBlurInternal(const CBox& box, const CHyprCol CRegion damage{m_renderData.damage}; damage.intersect(box); - CFramebuffer* POUTFB = data.xray ? &m_renderData.pCurrentMonData->blurFB : blurMainFramebufferWithDamage(data.blurA, &damage); + auto POUTFB = data.xray ? m_renderData.pCurrentMonData->blurFB : blurMainFramebufferWithDamage(data.blurA, &damage); m_renderData.currentFB->bind(); - // make a stencil for rounded corners to work with blur - scissor(nullptr); // allow the entire window and stencil to render - glClearStencil(0); - glClear(GL_STENCIL_BUFFER_BIT); - - setCapStatus(GL_STENCIL_TEST, true); - - glStencilFunc(GL_ALWAYS, 1, 0xFF); - glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); - - glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); - renderRect(box, CHyprColor(0, 0, 0, 0), SRectRenderData{.round = data.round, .roundingPower = data.roundingPower}); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - - glStencilFunc(GL_EQUAL, 1, 0xFF); - glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); - - scissor(box); CBox MONITORBOX = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y}; pushMonitorTransformEnabled(true); const auto SAVEDRENDERMODIF = m_renderData.renderModif; m_renderData.renderModif = {}; // fix shit renderTexture(POUTFB->getTexture(), MONITORBOX, - STextureRenderData{.damage = &damage, .a = data.blurA, .round = 0, .roundingPower = 2.0f, .allowCustomUV = false, .allowDim = false, .noAA = false}); + STextureRenderData{.damage = &damage, .a = data.blurA, .round = data.round, .roundingPower = 2.F, .allowCustomUV = false, .allowDim = false, .noAA = false}); popMonitorTransformEnabled(); m_renderData.renderModif = SAVEDRENDERMODIF; - glClearStencil(0); - glClear(GL_STENCIL_BUFFER_BIT); - setCapStatus(GL_STENCIL_TEST, false); - glStencilMask(0xFF); - glStencilFunc(GL_ALWAYS, 1, 0xFF); - scissor(nullptr); - renderRectWithDamageInternal(box, col, data); } @@ -1482,11 +1108,11 @@ void CHyprOpenGLImpl::renderRectWithDamageInternal(const CBox& box, const CHyprC newBox, Math::wlTransformToHyprutils(Math::invertTransform(!m_monitorTransformEnabled ? WL_OUTPUT_TRANSFORM_NORMAL : m_renderData.pMonitor->m_transform)), newBox.rot); Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix); - useProgram(m_shaders->m_shQUAD.program); - m_shaders->m_shQUAD.setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); + auto shader = useShader(getShaderVariant(SH_FRAG_QUAD, data.round > 0 ? SH_FEAT_ROUNDING : 0)); + shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); // premultiply the color as well as we don't work with straight alpha - m_shaders->m_shQUAD.setUniformFloat4(SHADER_COLOR, col.r * col.a, col.g * col.a, col.b * col.a, col.a); + shader->setUniformFloat4(SHADER_COLOR, col.r * col.a, col.g * col.a, col.b * col.a, col.a); CBox transformedBox = box; transformedBox.transform(Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x, @@ -1496,12 +1122,12 @@ void CHyprOpenGLImpl::renderRectWithDamageInternal(const CBox& box, const CHyprC const auto FULLSIZE = Vector2D(transformedBox.width, transformedBox.height); // Rounded corners - m_shaders->m_shQUAD.setUniformFloat2(SHADER_TOP_LEFT, sc(TOPLEFT.x), sc(TOPLEFT.y)); - m_shaders->m_shQUAD.setUniformFloat2(SHADER_FULL_SIZE, sc(FULLSIZE.x), sc(FULLSIZE.y)); - m_shaders->m_shQUAD.setUniformFloat(SHADER_RADIUS, data.round); - m_shaders->m_shQUAD.setUniformFloat(SHADER_ROUNDING_POWER, data.roundingPower); + shader->setUniformFloat2(SHADER_TOP_LEFT, sc(TOPLEFT.x), sc(TOPLEFT.y)); + shader->setUniformFloat2(SHADER_FULL_SIZE, sc(FULLSIZE.x), sc(FULLSIZE.y)); + shader->setUniformFloat(SHADER_RADIUS, data.round); + shader->setUniformFloat(SHADER_ROUNDING_POWER, data.roundingPower); - glBindVertexArray(m_shaders->m_shQUAD.uniformLocations[SHADER_SHADER_VAO]); + glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); if (m_renderData.clipBox.width != 0 && m_renderData.clipBox.height != 0) { CRegion damageClip{m_renderData.clipBox.x, m_renderData.clipBox.y, m_renderData.clipBox.width, m_renderData.clipBox.height}; @@ -1509,13 +1135,13 @@ void CHyprOpenGLImpl::renderRectWithDamageInternal(const CBox& box, const CHyprC if (!damageClip.empty()) { damageClip.forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } } else { data.damage->forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } @@ -1524,7 +1150,7 @@ void CHyprOpenGLImpl::renderRectWithDamageInternal(const CBox& box, const CHyprC scissor(nullptr); } -void CHyprOpenGLImpl::renderTexture(SP tex, const CBox& box, STextureRenderData data) { +void CHyprOpenGLImpl::renderTexture(SP tex, const CBox& box, STextureRenderData data) { RASSERT(m_renderData.pMonitor, "Tried to render texture without begin()!"); if (!data.damage) { @@ -1560,192 +1186,83 @@ static bool isHDR2SDR(const NColorManagement::SImageDescription& imageDescriptio targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22); } -void CHyprOpenGLImpl::passCMUniforms(SShader& shader, const NColorManagement::PImageDescription imageDescription, const NColorManagement::PImageDescription targetImageDescription, - bool modifySDR, float sdrMinLuminance, int sdrMaxLuminance) { - static auto PSDREOTF = CConfigValue("render:cm_sdr_eotf"); +void CHyprOpenGLImpl::passCMUniforms(WP shader, const NColorManagement::PImageDescription imageDescription, + const NColorManagement::PImageDescription targetImageDescription, bool modifySDR, float sdrMinLuminance, int sdrMaxLuminance) { + const auto settings = g_pHyprRenderer->getCMSettings(imageDescription, targetImageDescription, m_renderData.surface.valid() ? m_renderData.surface.lock() : nullptr, modifySDR, + sdrMinLuminance, sdrMaxLuminance); - if (m_renderData.surface.valid() && - ((!m_renderData.surface->m_colorManagement.valid() && *PSDREOTF >= 1) || - (*PSDREOTF == 2 && m_renderData.surface->m_colorManagement.valid() && - imageDescription->value().transferFunction == NColorManagement::eTransferFunction::CM_TRANSFER_FUNCTION_SRGB))) { - shader.setUniformInt(SHADER_SOURCE_TF, NColorManagement::eTransferFunction::CM_TRANSFER_FUNCTION_GAMMA22); - } else - shader.setUniformInt(SHADER_SOURCE_TF, imageDescription->value().transferFunction); + shader->setUniformInt(SHADER_SOURCE_TF, settings.sourceTF); + shader->setUniformInt(SHADER_TARGET_TF, settings.targetTF); + shader->setUniformFloat2(SHADER_SRC_TF_RANGE, settings.srcTFRange.min, settings.srcTFRange.max); + shader->setUniformFloat2(SHADER_DST_TF_RANGE, settings.dstTFRange.min, settings.dstTFRange.max); + shader->setUniformFloat(SHADER_SRC_REF_LUMINANCE, settings.srcRefLuminance); + shader->setUniformFloat(SHADER_DST_REF_LUMINANCE, settings.dstRefLuminance); + shader->setUniformFloat(SHADER_MAX_LUMINANCE, settings.maxLuminance); + shader->setUniformFloat(SHADER_DST_MAX_LUMINANCE, settings.dstMaxLuminance); + shader->setUniformFloat(SHADER_SDR_SATURATION, settings.sdrSaturation); + shader->setUniformFloat(SHADER_SDR_BRIGHTNESS, settings.sdrBrightnessMultiplier); - shader.setUniformInt(SHADER_TARGET_TF, targetImageDescription->value().transferFunction); + if (!targetImageDescription->value().icc.present) { + const auto cacheKey = std::make_pair(imageDescription->id(), targetImageDescription->id()); + if (!primariesConversionCache.contains(cacheKey)) { + const auto& mat = settings.convertMatrix; + const std::array glConvertMatrix = { + mat[0][0], mat[1][0], mat[2][0], // + mat[0][1], mat[1][1], mat[2][1], // + mat[0][2], mat[1][2], mat[2][2], // + }; + primariesConversionCache.insert(std::make_pair(cacheKey, glConvertMatrix)); + } + shader->setUniformMatrix3fv(SHADER_CONVERT_MATRIX, 1, false, primariesConversionCache[cacheKey]); - const auto targetPrimaries = targetImageDescription->getPrimaries(); - - const std::array glTargetPrimaries = { - targetPrimaries->value().red.x, targetPrimaries->value().red.y, targetPrimaries->value().green.x, targetPrimaries->value().green.y, - targetPrimaries->value().blue.x, targetPrimaries->value().blue.y, targetPrimaries->value().white.x, targetPrimaries->value().white.y, - }; - shader.setUniformMatrix4x2fv(SHADER_TARGET_PRIMARIES, 1, false, glTargetPrimaries); - - const bool needsSDRmod = modifySDR && isSDR2HDR(imageDescription->value(), targetImageDescription->value()); - const bool needsHDRmod = !needsSDRmod && isHDR2SDR(imageDescription->value(), targetImageDescription->value()); - - shader.setUniformFloat2(SHADER_SRC_TF_RANGE, imageDescription->value().getTFMinLuminance(needsSDRmod ? sdrMinLuminance : -1), - imageDescription->value().getTFMaxLuminance(needsSDRmod ? sdrMaxLuminance : -1)); - shader.setUniformFloat2(SHADER_DST_TF_RANGE, targetImageDescription->value().getTFMinLuminance(needsSDRmod ? sdrMinLuminance : -1), - targetImageDescription->value().getTFMaxLuminance(needsSDRmod ? sdrMaxLuminance : -1)); - - shader.setUniformFloat(SHADER_SRC_REF_LUMINANCE, imageDescription->value().getTFRefLuminance(-1)); - shader.setUniformFloat(SHADER_DST_REF_LUMINANCE, targetImageDescription->value().getTFRefLuminance(-1)); - - const float maxLuminance = needsHDRmod ? - imageDescription->value().getTFMaxLuminance(-1) : - (imageDescription->value().luminances.max > 0 ? imageDescription->value().luminances.max : imageDescription->value().luminances.reference); - shader.setUniformFloat(SHADER_MAX_LUMINANCE, - maxLuminance * targetImageDescription->value().luminances.reference / - (needsHDRmod ? imageDescription->value().getTFRefLuminance(-1) : imageDescription->value().luminances.reference)); - shader.setUniformFloat(SHADER_DST_MAX_LUMINANCE, targetImageDescription->value().luminances.max > 0 ? targetImageDescription->value().luminances.max : 10000); - shader.setUniformFloat(SHADER_SDR_SATURATION, needsSDRmod && m_renderData.pMonitor->m_sdrSaturation > 0 ? m_renderData.pMonitor->m_sdrSaturation : 1.0f); - shader.setUniformFloat(SHADER_SDR_BRIGHTNESS, needsSDRmod && m_renderData.pMonitor->m_sdrBrightness > 0 ? m_renderData.pMonitor->m_sdrBrightness : 1.0f); - const auto cacheKey = std::make_pair(imageDescription->id(), targetImageDescription->id()); - if (!primariesConversionCache.contains(cacheKey)) { - auto conversion = imageDescription->getPrimaries()->convertMatrix(targetImageDescription->getPrimaries()); - const auto mat = conversion.mat(); - const std::array glConvertMatrix = { + const auto mat = settings.dstPrimaries2XYZ; + const std::array glTargetPrimariesXYZ = { mat[0][0], mat[1][0], mat[2][0], // mat[0][1], mat[1][1], mat[2][1], // mat[0][2], mat[1][2], mat[2][2], // }; - primariesConversionCache.insert(std::make_pair(cacheKey, glConvertMatrix)); + shader->setUniformMatrix3fv(SHADER_TARGET_PRIMARIES_XYZ, 1, false, glTargetPrimariesXYZ); + } else { + // TODO: this sucks + GLCALL(glActiveTexture(GL_TEXTURE8)); + targetImageDescription->value().icc.lutTexture->bind(); + + shader->setUniformInt(SHADER_LUT_3D, 8); + shader->setUniformFloat(SHADER_LUT_SIZE, targetImageDescription->value().icc.lutSize); + + GLCALL(glActiveTexture(GL_TEXTURE0)); } - shader.setUniformMatrix3fv(SHADER_CONVERT_MATRIX, 1, false, primariesConversionCache[cacheKey]); } -void CHyprOpenGLImpl::passCMUniforms(SShader& shader, const PImageDescription imageDescription) { - passCMUniforms(shader, imageDescription, m_renderData.pMonitor->m_imageDescription, true, m_renderData.pMonitor->m_sdrMinLuminance, m_renderData.pMonitor->m_sdrMaxLuminance); +void CHyprOpenGLImpl::passCMUniforms(WP shader, const PImageDescription imageDescription) { + passCMUniforms(shader, imageDescription, g_pHyprRenderer->workBufferImageDescription(), true, m_renderData.pMonitor->m_sdrMinLuminance, + m_renderData.pMonitor->m_sdrMaxLuminance); } -void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, const STextureRenderData& data) { - RASSERT(m_renderData.pMonitor, "Tried to render texture without begin()!"); - RASSERT((tex->m_texID > 0), "Attempted to draw nullptr texture!"); - - TRACY_GPU_ZONE("RenderTextureInternalWithDamage"); - - float alpha = std::clamp(data.a, 0.f, 1.f); - - if (data.damage->empty()) - return; - - CBox newBox = box; - m_renderData.renderModif.applyToBox(newBox); - +WP CHyprOpenGLImpl::renderToOutputInternal() { static const auto PDT = CConfigValue("debug:damage_tracking"); - static const auto PPASS = CConfigValue("render:cm_fs_passthrough"); - static const auto PENABLECM = CConfigValue("render:cm_enabled"); static const auto PCURSORTIMEOUT = CConfigValue("cursor:inactive_timeout"); - // get the needed transform for this texture - const auto MONITOR_INVERTED = Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)); - Hyprutils::Math::eTransform TRANSFORM = tex->m_transform; + WP shader = + g_pHyprRenderer->m_crashingInProgress ? getShaderVariant(SH_FRAG_GLITCH) : (m_finalScreenShader->program() ? m_finalScreenShader : getShaderVariant(SH_FRAG_PASSTHRURGBA)); - if (m_monitorTransformEnabled) - TRANSFORM = Math::composeTransform(MONITOR_INVERTED, TRANSFORM); + shader = useShader(shader); - Mat3x3 matrix = m_renderData.monitorProjection.projectBox(newBox, TRANSFORM, newBox.rot); - Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix); - - SShader* shader = nullptr; - - bool usingFinalShader = false; - - const bool CRASHING = m_applyFinalShader && g_pHyprRenderer->m_crashingInProgress; - - auto texType = tex->m_type; - - if (CRASHING) { - shader = &m_shaders->m_shGLITCH; - usingFinalShader = true; - } else if (m_applyFinalShader && m_finalScreenShader.program) { - shader = &m_finalScreenShader; - usingFinalShader = true; - } else { - if (m_applyFinalShader) { - shader = &m_shaders->m_shPASSTHRURGBA; - usingFinalShader = true; - } else { - switch (tex->m_type) { - case TEXTURE_RGBA: shader = &m_shaders->m_shRGBA; break; - case TEXTURE_RGBX: shader = &m_shaders->m_shRGBX; break; - - case TEXTURE_EXTERNAL: shader = &m_shaders->m_shEXT; break; // might be unused - default: RASSERT(false, "tex->m_iTarget unsupported!"); - } - } - } - - if (m_renderData.currentWindow && m_renderData.currentWindow->m_ruleApplicator->RGBX().valueOrDefault()) { - shader = &m_shaders->m_shRGBX; - texType = TEXTURE_RGBX; - } - - glActiveTexture(GL_TEXTURE0); - tex->bind(); - - tex->setTexParameter(GL_TEXTURE_WRAP_S, data.wrapX); - tex->setTexParameter(GL_TEXTURE_WRAP_T, data.wrapY); - - if (m_renderData.useNearestNeighbor) { - tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST); - tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST); - } else { - tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR); - tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); - } - - const bool isHDRSurface = m_renderData.surface.valid() && m_renderData.surface->m_colorManagement.valid() ? m_renderData.surface->m_colorManagement->isHDR() : false; - const bool canPassHDRSurface = isHDRSurface && !m_renderData.surface->m_colorManagement->isWindowsScRGB(); // windows scRGB requires CM shader - - const auto imageDescription = m_renderData.surface.valid() && m_renderData.surface->m_colorManagement.valid() ? - CImageDescription::from(m_renderData.surface->m_colorManagement->imageDescription()) : - (data.cmBackToSRGB ? data.cmBackToSRGBSource->m_imageDescription : DEFAULT_IMAGE_DESCRIPTION); - - const bool skipCM = !*PENABLECM || !m_cmSupported /* CM unsupported or disabled */ - || m_renderData.pMonitor->doesNoShaderCM() /* no shader needed */ - || (imageDescription->id() == m_renderData.pMonitor->m_imageDescription->id() && !data.cmBackToSRGB) /* Source and target have the same image description */ - || (((*PPASS && canPassHDRSurface) || - (*PPASS == 1 && !isHDRSurface && m_renderData.pMonitor->m_cmType != NCMType::CM_HDR && m_renderData.pMonitor->m_cmType != NCMType::CM_HDR_EDID)) && - m_renderData.pMonitor->inFullscreenMode()) /* Fullscreen window with pass cm enabled */; - - if (!skipCM && !usingFinalShader && (texType == TEXTURE_RGBA || texType == TEXTURE_RGBX)) - shader = &m_shaders->m_shCM; - - useProgram(shader->program); - - if (shader == &m_shaders->m_shCM) { - shader->setUniformInt(SHADER_TEX_TYPE, texType); - if (data.cmBackToSRGB) { - static auto PSDREOTF = CConfigValue("render:cm_sdr_eotf"); - auto chosenSdrEotf = *PSDREOTF != 3 ? NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22 : NColorManagement::CM_TRANSFER_FUNCTION_SRGB; - passCMUniforms(*shader, imageDescription, CImageDescription::from(NColorManagement::SImageDescription{.transferFunction = chosenSdrEotf}), true, -1, -1); - } else - passCMUniforms(*shader, imageDescription); - } - - shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); - shader->setUniformInt(SHADER_TEX, 0); - - if ((usingFinalShader && *PDT == 0) || CRASHING) - shader->setUniformFloat(SHADER_TIME, m_globalTimer.getSeconds() - shader->initialTime); - else if (usingFinalShader) + if (*PDT == 0 || g_pHyprRenderer->m_crashingInProgress) + shader->setUniformFloat(SHADER_TIME, m_globalTimer.getSeconds() - shader->getInitialTime()); + else shader->setUniformFloat(SHADER_TIME, 0.f); - if (usingFinalShader) { - shader->setUniformInt(SHADER_WL_OUTPUT, m_renderData.pMonitor->m_id); - shader->setUniformFloat2(SHADER_FULL_SIZE, m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y); - shader->setUniformFloat(SHADER_POINTER_INACTIVE_TIMEOUT, *PCURSORTIMEOUT); - shader->setUniformInt(SHADER_POINTER_HIDDEN, g_pHyprRenderer->m_cursorHiddenByCondition); - shader->setUniformInt(SHADER_POINTER_KILLING, g_pInputManager->getClickMode() == CLICKMODE_KILL); - shader->setUniformInt(SHADER_POINTER_SHAPE, g_pHyprRenderer->m_lastCursorData.shape); - shader->setUniformInt(SHADER_POINTER_SHAPE_PREVIOUS, g_pHyprRenderer->m_lastCursorData.shapePrevious); - shader->setUniformFloat(SHADER_POINTER_SIZE, g_pCursorManager->getScaledSize()); - } + shader->setUniformInt(SHADER_WL_OUTPUT, m_renderData.pMonitor->m_id); + shader->setUniformFloat2(SHADER_FULL_SIZE, m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y); + shader->setUniformFloat(SHADER_POINTER_INACTIVE_TIMEOUT, *PCURSORTIMEOUT); + shader->setUniformInt(SHADER_POINTER_HIDDEN, g_pHyprRenderer->m_cursorHiddenByCondition); + shader->setUniformInt(SHADER_POINTER_KILLING, g_pInputManager->getClickMode() == CLICKMODE_KILL); + shader->setUniformInt(SHADER_POINTER_SHAPE, g_pHyprRenderer->m_lastCursorData.shape); + shader->setUniformInt(SHADER_POINTER_SHAPE_PREVIOUS, g_pHyprRenderer->m_lastCursorData.shapePrevious); + shader->setUniformFloat(SHADER_POINTER_SIZE, g_pCursorManager->getScaledSize()); - if (usingFinalShader && *PDT == 0) { + if (*PDT == 0) { PHLMONITORREF pMonitor = m_renderData.pMonitor; Vector2D p = ((g_pInputManager->getMouseCoordsInternal() - pMonitor->m_position) * pMonitor->m_scale); p = p.transform(Math::wlTransformToHyprutils(pMonitor->m_transform), pMonitor->m_pixelSize); @@ -1771,7 +1288,7 @@ void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, c shader->setUniformFloat(SHADER_POINTER_LAST_ACTIVE, g_pInputManager->m_lastCursorMovement.getSeconds()); shader->setUniformFloat(SHADER_POINTER_SWITCH_TIME, g_pHyprRenderer->m_lastCursorData.switchedTimer.getSeconds()); - } else if (usingFinalShader) { + } else { shader->setUniformFloat2(SHADER_POINTER, 0.f, 0.f); static const std::vector pressedPosDefault(POINTER_PRESSED_HISTORY_LENGTH * 2uz, 0.f); @@ -1785,68 +1302,249 @@ void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, c shader->setUniformFloat(SHADER_POINTER_SWITCH_TIME, 0.f); } - if (CRASHING) { + if (g_pHyprRenderer->m_crashingInProgress) { shader->setUniformFloat(SHADER_DISTORT, g_pHyprRenderer->m_crashingDistort); shader->setUniformFloat2(SHADER_FULL_SIZE, m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y); } - if (!usingFinalShader) { - shader->setUniformFloat(SHADER_ALPHA, alpha); + return shader; +} - if (data.discardActive) { - shader->setUniformInt(SHADER_DISCARD_OPAQUE, !!(m_renderData.discardMode & DISCARD_OPAQUE)); - shader->setUniformInt(SHADER_DISCARD_ALPHA, !!(m_renderData.discardMode & DISCARD_ALPHA)); - shader->setUniformFloat(SHADER_DISCARD_ALPHA_VALUE, m_renderData.discardOpacity); - } else { - shader->setUniformInt(SHADER_DISCARD_OPAQUE, 0); - shader->setUniformInt(SHADER_DISCARD_ALPHA, 0); +WP CHyprOpenGLImpl::renderToFBInternal(const STextureRenderData& data, eTextureType texType, const CBox& newBox) { + static const auto PPASS = CConfigValue("render:cm_fs_passthrough"); + static const auto PENABLECM = CConfigValue("render:cm_enabled"); + static auto PBLEND = CConfigValue("render:use_shader_blur_blend"); + + float alpha = std::clamp(data.a, 0.f, 1.f); + + WP shader; + ShaderFeatureFlags shaderFeatures = 0; + + switch (texType) { + case TEXTURE_RGBA: shaderFeatures |= SH_FEAT_RGBA; break; + case TEXTURE_RGBX: shaderFeatures &= ~SH_FEAT_RGBA; break; + + // TODO set correct features + case TEXTURE_EXTERNAL: shader = getShaderVariant(SH_FRAG_EXT, SH_FEAT_ROUNDING | SH_FEAT_DISCARD | SH_FEAT_TINT); break; // might be unused + default: RASSERT(false, "tex->m_iTarget unsupported!"); + } + + if (data.finalMonitorCM || (m_renderData.currentWindow && m_renderData.currentWindow->m_ruleApplicator->RGBX().valueOrDefault())) + shaderFeatures &= ~SH_FEAT_RGBA; + + const auto surface = m_renderData.surface; + const bool isHDRSurface = surface.valid() && surface->m_colorManagement.valid() ? surface->m_colorManagement->isHDR() : false; + const bool canPassHDRSurface = isHDRSurface && !surface->m_colorManagement->isWindowsScRGB(); // windows scRGB requires CM shader + + const auto WORK_BUFFER_IMAGE_DESCRIPTION = g_pHyprRenderer->workBufferImageDescription(); + + // chosenSdrEotf contains the valid eotf for this display + + const auto SOURCE_IMAGE_DESCRIPTION = [&] { + // if valid CM surface, use that as a source + if (m_renderData.surface.valid() && m_renderData.surface->m_colorManagement.valid()) + return CImageDescription::from(m_renderData.surface->m_colorManagement->imageDescription()); + + // otherwise, if we are CM'ing back into source, use chosen, because that's what our work buffer is in + // the same applies to the final monitor CM + if (data.cmBackToSRGB || data.finalMonitorCM) // NOLINTNEXTLINE + return WORK_BUFFER_IMAGE_DESCRIPTION; + + // otherwise, default + return DEFAULT_IMAGE_DESCRIPTION; + }(); + + const auto TARGET_IMAGE_DESCRIPTION = [&] { + // if we are CM'ing back, use default sRGB + if (data.cmBackToSRGB) + return DEFAULT_IMAGE_DESCRIPTION; + + // for final CM, use the target description + if (data.finalMonitorCM) + return m_renderData.pMonitor->m_imageDescription; + // otherwise, use chosen, we're drawing into the work buffer + // NOLINTNEXTLINE + return WORK_BUFFER_IMAGE_DESCRIPTION; + }(); + + if (data.blur && *PBLEND && data.blurredBG) + shaderFeatures |= SH_FEAT_BLUR; + + if (data.discardActive) + shaderFeatures |= SH_FEAT_DISCARD; + + const bool CANT_CHECK_CM_EQUALITY = data.cmBackToSRGB || data.finalMonitorCM || (!m_renderData.surface || !m_renderData.surface->m_colorManagement); + + const bool skipCM = !*PENABLECM || !m_cmSupported /* CM unsupported or disabled */ + || m_renderData.pMonitor->doesNoShaderCM() /* no shader needed */ + || (SOURCE_IMAGE_DESCRIPTION->id() == TARGET_IMAGE_DESCRIPTION->id() && !CANT_CHECK_CM_EQUALITY) /* Source and target have the same image description */ + || (((*PPASS && canPassHDRSurface) || + (*PPASS == 1 && !isHDRSurface && m_renderData.pMonitor->m_cmType != NCMType::CM_HDR && m_renderData.pMonitor->m_cmType != NCMType::CM_HDR_EDID)) && + m_renderData.pMonitor->inFullscreenMode()) /* Fullscreen window with pass cm enabled */; + + if (data.allowDim && m_renderData.currentWindow && (m_renderData.currentWindow->m_notRespondingTint->value() > 0 || m_renderData.currentWindow->m_dimPercent->value() > 0)) + shaderFeatures |= SH_FEAT_TINT; + + if (data.round > 0) + shaderFeatures |= SH_FEAT_ROUNDING; + + if (!skipCM) { + shaderFeatures |= SH_FEAT_CM; + + if (TARGET_IMAGE_DESCRIPTION->value().icc.present) + shaderFeatures |= SH_FEAT_ICC; + else { + const bool needsSDRmod = isSDR2HDR(SOURCE_IMAGE_DESCRIPTION->value(), TARGET_IMAGE_DESCRIPTION->value()); + const bool needsHDRmod = !needsSDRmod && isHDR2SDR(SOURCE_IMAGE_DESCRIPTION->value(), TARGET_IMAGE_DESCRIPTION->value()); + const float maxLuminance = needsHDRmod ? + SOURCE_IMAGE_DESCRIPTION->value().getTFMaxLuminance(-1) : + (SOURCE_IMAGE_DESCRIPTION->value().luminances.max > 0 ? SOURCE_IMAGE_DESCRIPTION->value().luminances.max : SOURCE_IMAGE_DESCRIPTION->value().luminances.reference); + const auto dstMaxLuminance = TARGET_IMAGE_DESCRIPTION->value().luminances.max > 0 ? TARGET_IMAGE_DESCRIPTION->value().luminances.max : 10000; + + if (maxLuminance >= dstMaxLuminance * 1.01) + shaderFeatures |= SH_FEAT_TONEMAP; + + if (data.finalMonitorCM && + (SOURCE_IMAGE_DESCRIPTION->value().transferFunction == CM_TRANSFER_FUNCTION_SRGB || + SOURCE_IMAGE_DESCRIPTION->value().transferFunction == CM_TRANSFER_FUNCTION_GAMMA22) && + TARGET_IMAGE_DESCRIPTION->value().transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ && + ((m_renderData.pMonitor->m_sdrSaturation > 0 && m_renderData.pMonitor->m_sdrSaturation != 1.0f) || + (m_renderData.pMonitor->m_sdrBrightness > 0 && m_renderData.pMonitor->m_sdrBrightness != 1.0f))) + shaderFeatures |= SH_FEAT_SDR_MOD; } } + if (!shader) + shader = getShaderVariant(SH_FRAG_SURFACE, shaderFeatures); + shader = useShader(shader); + + if (!skipCM) { + if (data.finalMonitorCM || data.cmBackToSRGB) + passCMUniforms(shader, SOURCE_IMAGE_DESCRIPTION, TARGET_IMAGE_DESCRIPTION, true, m_renderData.pMonitor->m_sdrMinLuminance, m_renderData.pMonitor->m_sdrMaxLuminance); + else + passCMUniforms(shader, SOURCE_IMAGE_DESCRIPTION); + } + + shader->setUniformFloat(SHADER_ALPHA, alpha); + + if (shaderFeatures & SH_FEAT_BLUR) { + shader->setUniformInt(SHADER_BLURRED_BG, 1); + // shader->setUniformFloat2(SHADER_UV_OFFSET, 0, 0); + shader->setUniformFloat2(SHADER_UV_OFFSET, newBox.x / m_renderData.pMonitor->m_transformedSize.x, newBox.y / m_renderData.pMonitor->m_transformedSize.y); + shader->setUniformFloat2(SHADER_UV_SIZE, newBox.width / m_renderData.pMonitor->m_transformedSize.x, newBox.height / m_renderData.pMonitor->m_transformedSize.y); + + glActiveTexture(GL_TEXTURE0 + 1); + data.blurredBG->bind(); + } + + if (data.discardActive) { + shader->setUniformInt(SHADER_DISCARD_OPAQUE, !!(m_renderData.discardMode & DISCARD_OPAQUE)); + shader->setUniformInt(SHADER_DISCARD_ALPHA, !!(m_renderData.discardMode & DISCARD_ALPHA)); + shader->setUniformFloat(SHADER_DISCARD_ALPHA_VALUE, m_renderData.discardOpacity); + } else { + shader->setUniformInt(SHADER_DISCARD_OPAQUE, 0); + shader->setUniformInt(SHADER_DISCARD_ALPHA, 0); + } + CBox transformedBox = newBox; transformedBox.transform(Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y); const auto TOPLEFT = Vector2D(transformedBox.x, transformedBox.y); const auto FULLSIZE = Vector2D(transformedBox.width, transformedBox.height); + // Rounded corners + shader->setUniformFloat2(SHADER_TOP_LEFT, TOPLEFT.x, TOPLEFT.y); + shader->setUniformFloat2(SHADER_FULL_SIZE, FULLSIZE.x, FULLSIZE.y); + shader->setUniformFloat(SHADER_RADIUS, data.round); + shader->setUniformFloat(SHADER_ROUNDING_POWER, data.roundingPower); - if (!usingFinalShader) { - // Rounded corners - shader->setUniformFloat2(SHADER_TOP_LEFT, TOPLEFT.x, TOPLEFT.y); - shader->setUniformFloat2(SHADER_FULL_SIZE, FULLSIZE.x, FULLSIZE.y); - shader->setUniformFloat(SHADER_RADIUS, data.round); - shader->setUniformFloat(SHADER_ROUNDING_POWER, data.roundingPower); - - if (data.allowDim && m_renderData.currentWindow) { - if (m_renderData.currentWindow->m_notRespondingTint->value() > 0) { - const auto DIM = m_renderData.currentWindow->m_notRespondingTint->value(); - shader->setUniformInt(SHADER_APPLY_TINT, 1); - shader->setUniformFloat3(SHADER_TINT, 1.f - DIM, 1.f - DIM, 1.f - DIM); - } else if (m_renderData.currentWindow->m_dimPercent->value() > 0) { - shader->setUniformInt(SHADER_APPLY_TINT, 1); - const auto DIM = m_renderData.currentWindow->m_dimPercent->value(); - shader->setUniformFloat3(SHADER_TINT, 1.f - DIM, 1.f - DIM, 1.f - DIM); - } else - shader->setUniformInt(SHADER_APPLY_TINT, 0); + if (data.allowDim && m_renderData.currentWindow) { + if (m_renderData.currentWindow->m_notRespondingTint->value() > 0) { + const auto DIM = m_renderData.currentWindow->m_notRespondingTint->value(); + shader->setUniformInt(SHADER_APPLY_TINT, 1); + shader->setUniformFloat3(SHADER_TINT, 1.f - DIM, 1.f - DIM, 1.f - DIM); + } else if (m_renderData.currentWindow->m_dimPercent->value() > 0) { + shader->setUniformInt(SHADER_APPLY_TINT, 1); + const auto DIM = m_renderData.currentWindow->m_dimPercent->value(); + shader->setUniformFloat3(SHADER_TINT, 1.f - DIM, 1.f - DIM, 1.f - DIM); } else shader->setUniformInt(SHADER_APPLY_TINT, 0); - } + } else + shader->setUniformInt(SHADER_APPLY_TINT, 0); - glBindVertexArray(shader->uniformLocations[SHADER_SHADER_VAO]); - if (data.allowCustomUV && m_renderData.primarySurfaceUVTopLeft != Vector2D(-1, -1)) { - const float customUVs[] = { - m_renderData.primarySurfaceUVBottomRight.x, m_renderData.primarySurfaceUVTopLeft.y, m_renderData.primarySurfaceUVTopLeft.x, - m_renderData.primarySurfaceUVTopLeft.y, m_renderData.primarySurfaceUVBottomRight.x, m_renderData.primarySurfaceUVBottomRight.y, - m_renderData.primarySurfaceUVTopLeft.x, m_renderData.primarySurfaceUVBottomRight.y, - }; + return shader; +} - glBindBuffer(GL_ARRAY_BUFFER, shader->uniformLocations[SHADER_SHADER_VBO_UV]); - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(customUVs), customUVs); +void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, const STextureRenderData& data) { + RASSERT(m_renderData.pMonitor, "Tried to render texture without begin()!"); + RASSERT((tex->ok()), "Attempted to draw nullptr texture!"); + + TRACY_GPU_ZONE("RenderTextureInternalWithDamage"); + + if (data.damage->empty()) + return; + + CBox newBox = box; + m_renderData.renderModif.applyToBox(newBox); + + // get the needed transform for this texture + const auto MONITOR_INVERTED = Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)); + Hyprutils::Math::eTransform TRANSFORM = tex->m_transform; + + if (m_monitorTransformEnabled) + TRANSFORM = Math::composeTransform(MONITOR_INVERTED, TRANSFORM); + + Mat3x3 matrix = m_renderData.monitorProjection.projectBox(newBox, TRANSFORM, newBox.rot); + Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix); + + const bool renderToOutput = m_applyFinalShader && g_pHyprRenderer->workBufferImageDescription()->id() == m_renderData.pMonitor->m_imageDescription->id(); + + glActiveTexture(GL_TEXTURE0); + tex->bind(); + + tex->setTexParameter(GL_TEXTURE_WRAP_S, data.wrapX); + tex->setTexParameter(GL_TEXTURE_WRAP_T, data.wrapY); + + if (m_renderData.useNearestNeighbor) { + tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST); + tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST); } else { - glBindBuffer(GL_ARRAY_BUFFER, shader->uniformLocations[SHADER_SHADER_VBO_UV]); - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(fullVerts), fullVerts); + tex->setTexParameter(GL_TEXTURE_MAG_FILTER, tex->magFilter); + tex->setTexParameter(GL_TEXTURE_MIN_FILTER, tex->minFilter); } + auto shader = renderToOutput ? renderToOutputInternal() : renderToFBInternal(data, tex->m_type, newBox); + + shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); + shader->setUniformInt(SHADER_TEX, 0); + GLCALL(glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO))); + GLCALL(glBindBuffer(GL_ARRAY_BUFFER, shader->getUniformLocation(SHADER_SHADER_VBO))); + + // this tells GPU can keep reading the old block for previous draws while the CPU writes to a new one. + // to avoid stalls if renderTextureInternal is called multiple times on same renderpass + // at the cost of some temporar vram usage. + glBufferData(GL_ARRAY_BUFFER, sizeof(fullVerts), nullptr, GL_DYNAMIC_DRAW); + + auto verts = fullVerts; + + if (data.allowCustomUV && m_renderData.primarySurfaceUVTopLeft != Vector2D(-1, -1)) { + const float u0 = m_renderData.primarySurfaceUVTopLeft.x; + const float v0 = m_renderData.primarySurfaceUVTopLeft.y; + const float u1 = m_renderData.primarySurfaceUVBottomRight.x; + const float v1 = m_renderData.primarySurfaceUVBottomRight.y; + + verts[0].u = u0; + verts[0].v = v0; + verts[1].u = u0; + verts[1].v = v1; + verts[2].u = u1; + verts[2].v = v0; + verts[3].u = u1; + verts[3].v = v1; + } + + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(verts), verts.data()); + if (!m_renderData.clipBox.empty() || !m_renderData.clipRegion.empty()) { CRegion damageClip = m_renderData.clipBox; @@ -1859,25 +1557,25 @@ void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, c if (!damageClip.empty()) { damageClip.forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } } else { data.damage->forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); + GLCALL(glBindVertexArray(0)); + GLCALL(glBindBuffer(GL_ARRAY_BUFFER, 0)); tex->unbind(); } -void CHyprOpenGLImpl::renderTexturePrimitive(SP tex, const CBox& box) { +void CHyprOpenGLImpl::renderTexturePrimitive(SP tex, const CBox& box) { RASSERT(m_renderData.pMonitor, "Tried to render texture without begin()!"); - RASSERT((tex->m_texID > 0), "Attempted to draw nullptr texture!"); + RASSERT((tex->ok()), "Attempted to draw nullptr texture!"); TRACY_GPU_ZONE("RenderTexturePrimitive"); @@ -1892,8 +1590,6 @@ void CHyprOpenGLImpl::renderTexturePrimitive(SP tex, const CBox& box) Mat3x3 matrix = m_renderData.monitorProjection.projectBox(newBox, TRANSFORM, newBox.rot); Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix); - SShader* shader = &m_shaders->m_shPASSTHRURGBA; - glActiveTexture(GL_TEXTURE0); tex->bind(); @@ -1903,17 +1599,17 @@ void CHyprOpenGLImpl::renderTexturePrimitive(SP tex, const CBox& box) tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST); tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST); } else { - tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR); - tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); + tex->setTexParameter(GL_TEXTURE_MAG_FILTER, tex->magFilter); + tex->setTexParameter(GL_TEXTURE_MIN_FILTER, tex->minFilter); } - useProgram(shader->program); + auto shader = useShader(getShaderVariant(SH_FRAG_PASSTHRURGBA)); shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); shader->setUniformInt(SHADER_TEX, 0); - glBindVertexArray(shader->uniformLocations[SHADER_SHADER_VAO]); + glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); m_renderData.damage.forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); @@ -1922,9 +1618,9 @@ void CHyprOpenGLImpl::renderTexturePrimitive(SP tex, const CBox& box) tex->unbind(); } -void CHyprOpenGLImpl::renderTextureMatte(SP tex, const CBox& box, CFramebuffer& matte) { +void CHyprOpenGLImpl::renderTextureMatte(SP tex, const CBox& box, SP matte) { RASSERT(m_renderData.pMonitor, "Tried to render texture without begin()!"); - RASSERT((tex->m_texID > 0), "Attempted to draw nullptr texture!"); + RASSERT((tex->ok()), "Attempted to draw nullptr texture!"); TRACY_GPU_ZONE("RenderTextureMatte"); @@ -1939,9 +1635,7 @@ void CHyprOpenGLImpl::renderTextureMatte(SP tex, const CBox& box, CFra Mat3x3 matrix = m_renderData.monitorProjection.projectBox(newBox, TRANSFORM, newBox.rot); Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix); - SShader* shader = &m_shaders->m_shMATTE; - - useProgram(shader->program); + auto shader = useShader(getShaderVariant(SH_FRAG_MATTE)); shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); shader->setUniformInt(SHADER_TEX, 0); shader->setUniformInt(SHADER_ALPHA_MATTE, 1); @@ -1950,13 +1644,13 @@ void CHyprOpenGLImpl::renderTextureMatte(SP tex, const CBox& box, CFra tex->bind(); glActiveTexture(GL_TEXTURE0 + 1); - auto matteTex = matte.getTexture(); + auto matteTex = matte->getTexture(); matteTex->bind(); - glBindVertexArray(shader->uniformLocations[SHADER_SHADER_VAO]); + glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); m_renderData.damage.forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); @@ -1969,16 +1663,16 @@ void CHyprOpenGLImpl::renderTextureMatte(SP tex, const CBox& box, CFra // but it works... well, I guess? // // Dual (or more) kawase blur -CFramebuffer* CHyprOpenGLImpl::blurMainFramebufferWithDamage(float a, CRegion* originalDamage) { +SP CHyprOpenGLImpl::blurMainFramebufferWithDamage(float a, CRegion* originalDamage) { if (!m_renderData.currentFB->getTexture()) { Log::logger->log(Log::ERR, "BUG THIS: null fb texture while attempting to blur main fb?! (introspection off?!)"); - return &m_renderData.pCurrentMonData->mirrorFB; // return something to sample from at least + return m_renderData.pCurrentMonData->mirrorFB; // return something to sample from at least } - return blurFramebufferWithDamage(a, originalDamage, *m_renderData.currentFB); + return blurFramebufferWithDamage(a, originalDamage, *GLFB(m_renderData.currentFB)); } -CFramebuffer* CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* originalDamage, CFramebuffer& source) { +SP CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* originalDamage, CGLFramebuffer& source) { TRACY_GPU_ZONE("RenderBlurFramebufferWithDamage"); const auto BLENDBEFORE = m_blend; @@ -2006,16 +1700,17 @@ CFramebuffer* CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* origi damage.expand(std::clamp(*PBLURSIZE, sc(1), sc(40)) * pow(2, BLUR_PASSES)); // helper - const auto PMIRRORFB = &m_renderData.pCurrentMonData->mirrorFB; - const auto PMIRRORSWAPFB = &m_renderData.pCurrentMonData->mirrorSwapFB; + const auto PMIRRORFB = m_renderData.pCurrentMonData->mirrorFB; + const auto PMIRRORSWAPFB = m_renderData.pCurrentMonData->mirrorSwapFB; - CFramebuffer* currentRenderToFB = PMIRRORFB; + auto currentRenderToFB = PMIRRORFB; // Begin with base color adjustments - global brightness and contrast // TODO: make this a part of the first pass maybe to save on a drawcall? { static auto PBLURCONTRAST = CConfigValue("decoration:blur:contrast"); static auto PBLURBRIGHTNESS = CConfigValue("decoration:blur:brightness"); + static auto PBLEND = CConfigValue("render:use_shader_blur_blend"); PMIRRORSWAPFB->bind(); @@ -2026,33 +1721,34 @@ CFramebuffer* CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* origi currentTex->bind(); currentTex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); - useProgram(m_shaders->m_shBLURPREPARE.program); + WP shader; // From FB to sRGB - const bool skipCM = !m_cmSupported || m_renderData.pMonitor->m_imageDescription->id() == DEFAULT_IMAGE_DESCRIPTION->id(); - m_shaders->m_shBLURPREPARE.setUniformInt(SHADER_SKIP_CM, skipCM); + const bool skipCM = !m_cmSupported || g_pHyprRenderer->workBufferImageDescription()->id() == DEFAULT_IMAGE_DESCRIPTION->id(); if (!skipCM) { - passCMUniforms(m_shaders->m_shBLURPREPARE, m_renderData.pMonitor->m_imageDescription, DEFAULT_IMAGE_DESCRIPTION); - m_shaders->m_shBLURPREPARE.setUniformFloat(SHADER_SDR_SATURATION, - m_renderData.pMonitor->m_sdrSaturation > 0 && - m_renderData.pMonitor->m_imageDescription->value().transferFunction == - NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ? - m_renderData.pMonitor->m_sdrSaturation : - 1.0f); - m_shaders->m_shBLURPREPARE.setUniformFloat(SHADER_SDR_BRIGHTNESS, - m_renderData.pMonitor->m_sdrBrightness > 0 && - m_renderData.pMonitor->m_imageDescription->value().transferFunction == - NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ? - m_renderData.pMonitor->m_sdrBrightness : - 1.0f); - } + shader = useShader(getShaderVariant(SH_FRAG_BLURPREPARE, SH_FEAT_CM)); + passCMUniforms(shader, m_renderData.pMonitor->m_imageDescription, DEFAULT_IMAGE_DESCRIPTION); + shader->setUniformFloat(SHADER_SDR_SATURATION, + m_renderData.pMonitor->m_sdrSaturation > 0 && + m_renderData.pMonitor->m_imageDescription->value().transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ? + m_renderData.pMonitor->m_sdrSaturation : + 1.0f); + shader->setUniformFloat(SHADER_SDR_BRIGHTNESS, + m_renderData.pMonitor->m_sdrBrightness > 0 && + m_renderData.pMonitor->m_imageDescription->value().transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ? + m_renderData.pMonitor->m_sdrBrightness : + 1.0f); + } else + shader = useShader(getShaderVariant(SH_FRAG_BLURPREPARE)); - m_shaders->m_shBLURPREPARE.setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); - m_shaders->m_shBLURPREPARE.setUniformFloat(SHADER_CONTRAST, *PBLURCONTRAST); - m_shaders->m_shBLURPREPARE.setUniformFloat(SHADER_BRIGHTNESS, *PBLURBRIGHTNESS); - m_shaders->m_shBLURPREPARE.setUniformInt(SHADER_TEX, 0); + Mat3x3 matrix = m_renderData.monitorProjection.projectBox(MONITORBOX, *PBLEND ? HYPRUTILS_TRANSFORM_NORMAL : TRANSFORM); + Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix); + shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); + shader->setUniformFloat(SHADER_CONTRAST, *PBLURCONTRAST); + shader->setUniformFloat(SHADER_BRIGHTNESS, *PBLURBRIGHTNESS); + shader->setUniformInt(SHADER_TEX, 0); - glBindVertexArray(m_shaders->m_shBLURPREPARE.uniformLocations[SHADER_SHADER_VAO]); + glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); if (!damage.empty()) { damage.forEachRect([this](const auto& RECT) { @@ -2066,7 +1762,7 @@ CFramebuffer* CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* origi } // declare the draw func - auto drawPass = [&](SShader* pShader, CRegion* pDamage) { + auto drawPass = [&](WP shader, ePreparedFragmentShader frag, CRegion* pDamage) { if (currentRenderToFB == PMIRRORFB) PMIRRORSWAPFB->bind(); else @@ -2080,21 +1776,19 @@ CFramebuffer* CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* origi currentTex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); - useProgram(pShader->program); - // prep two shaders - pShader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); - pShader->setUniformFloat(SHADER_RADIUS, *PBLURSIZE * a); // this makes the blursize change with a - if (pShader == &m_shaders->m_shBLUR1) { - m_shaders->m_shBLUR1.setUniformFloat2(SHADER_HALFPIXEL, 0.5f / (m_renderData.pMonitor->m_pixelSize.x / 2.f), 0.5f / (m_renderData.pMonitor->m_pixelSize.y / 2.f)); - m_shaders->m_shBLUR1.setUniformInt(SHADER_PASSES, BLUR_PASSES); - m_shaders->m_shBLUR1.setUniformFloat(SHADER_VIBRANCY, *PBLURVIBRANCY); - m_shaders->m_shBLUR1.setUniformFloat(SHADER_VIBRANCY_DARKNESS, *PBLURVIBRANCYDARKNESS); + shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); + shader->setUniformFloat(SHADER_RADIUS, *PBLURSIZE * a); // this makes the blursize change with a + if (frag == SH_FRAG_BLUR1) { + shader->setUniformFloat2(SHADER_HALFPIXEL, 0.5f / (m_renderData.pMonitor->m_pixelSize.x / 2.f), 0.5f / (m_renderData.pMonitor->m_pixelSize.y / 2.f)); + shader->setUniformInt(SHADER_PASSES, BLUR_PASSES); + shader->setUniformFloat(SHADER_VIBRANCY, *PBLURVIBRANCY); + shader->setUniformFloat(SHADER_VIBRANCY_DARKNESS, *PBLURVIBRANCYDARKNESS); } else - m_shaders->m_shBLUR2.setUniformFloat2(SHADER_HALFPIXEL, 0.5f / (m_renderData.pMonitor->m_pixelSize.x * 2.f), 0.5f / (m_renderData.pMonitor->m_pixelSize.y * 2.f)); - pShader->setUniformInt(SHADER_TEX, 0); + shader->setUniformFloat2(SHADER_HALFPIXEL, 0.5f / (m_renderData.pMonitor->m_pixelSize.x * 2.f), 0.5f / (m_renderData.pMonitor->m_pixelSize.y * 2.f)); + shader->setUniformInt(SHADER_TEX, 0); - glBindVertexArray(pShader->uniformLocations[SHADER_SHADER_VAO]); + glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); if (!pDamage->empty()) { pDamage->forEachRect([this](const auto& RECT) { @@ -2120,14 +1814,16 @@ CFramebuffer* CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* origi CRegion tempDamage{damage}; // and draw + auto shader = useShader(getShaderVariant(SH_FRAG_BLUR1)); for (auto i = 1; i <= BLUR_PASSES; ++i) { tempDamage = damage.copy().scale(1.f / (1 << i)); - drawPass(&m_shaders->m_shBLUR1, &tempDamage); // down + drawPass(shader, SH_FRAG_BLUR1, &tempDamage); // down } + shader = useShader(getShaderVariant(SH_FRAG_BLUR2)); for (auto i = BLUR_PASSES - 1; i >= 0; --i) { tempDamage = damage.copy().scale(1.f / (1 << i)); // when upsampling we make the region twice as big - drawPass(&m_shaders->m_shBLUR2, &tempDamage); // up + drawPass(shader, SH_FRAG_BLUR2, &tempDamage); // up } // finalize the image @@ -2148,14 +1844,14 @@ CFramebuffer* CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* origi currentTex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); - useProgram(m_shaders->m_shBLURFINISH.program); - m_shaders->m_shBLURFINISH.setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); - m_shaders->m_shBLURFINISH.setUniformFloat(SHADER_NOISE, *PBLURNOISE); - m_shaders->m_shBLURFINISH.setUniformFloat(SHADER_BRIGHTNESS, *PBLURBRIGHTNESS); + auto shader = useShader(getShaderVariant(SH_FRAG_BLURFINISH)); + shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); + shader->setUniformFloat(SHADER_NOISE, *PBLURNOISE); + shader->setUniformFloat(SHADER_BRIGHTNESS, *PBLURBRIGHTNESS); - m_shaders->m_shBLURFINISH.setUniformInt(SHADER_TEX, 0); + shader->setUniformInt(SHADER_TEX, 0); - glBindVertexArray(m_shaders->m_shBLURFINISH.uniformLocations[SHADER_SHADER_VAO]); + glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); if (!damage.empty()) { damage.forEachRect([this](const auto& RECT) { @@ -2279,9 +1975,12 @@ void CHyprOpenGLImpl::preBlurForCurrentMonitor() { const auto POUTFB = blurMainFramebufferWithDamage(1, &fakeDamage); // render onto blurFB - m_renderData.pCurrentMonData->blurFB.alloc(m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y, - m_renderData.pMonitor->m_output->state->state().drmFormat); - m_renderData.pCurrentMonData->blurFB.bind(); + if (!m_renderData.pCurrentMonData->blurFB) + m_renderData.pCurrentMonData->blurFB = g_pHyprRenderer->createFB(); + + m_renderData.pCurrentMonData->blurFB->alloc(m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y, + m_renderData.pMonitor->m_output->state->state().drmFormat); + m_renderData.pCurrentMonData->blurFB->bind(); clear(CHyprColor(0, 0, 0, 0)); @@ -2317,7 +2016,7 @@ bool CHyprOpenGLImpl::shouldUseNewBlurOptimizations(PHLLS pLayer, PHLWINDOW pWin static auto PBLURNEWOPTIMIZE = CConfigValue("decoration:blur:new_optimizations"); static auto PBLURXRAY = CConfigValue("decoration:blur:xray"); - if (!m_renderData.pCurrentMonData->blurFB.getTexture()) + if (!m_renderData.pCurrentMonData->blurFB || !m_renderData.pCurrentMonData->blurFB->getTexture()) return false; if (pWindow && pWindow->m_ruleApplicator->xray().hasValue() && !pWindow->m_ruleApplicator->xray().valueOrDefault()) @@ -2335,11 +2034,13 @@ bool CHyprOpenGLImpl::shouldUseNewBlurOptimizations(PHLLS pLayer, PHLWINDOW pWin return false; } -void CHyprOpenGLImpl::renderTextureWithBlurInternal(SP tex, const CBox& box, const STextureRenderData& data) { +void CHyprOpenGLImpl::renderTextureWithBlurInternal(SP tex, const CBox& box, const STextureRenderData& data) { RASSERT(m_renderData.pMonitor, "Tried to render texture with blur without begin()!"); TRACY_GPU_ZONE("RenderTextureWithBlur"); + static auto PBLEND = CConfigValue("render:use_shader_blur_blend"); + // make a damage region for this window CRegion texDamage{m_renderData.damage}; texDamage.intersect(box.x, box.y, box.width, box.height); @@ -2373,107 +2074,112 @@ void CHyprOpenGLImpl::renderTextureWithBlurInternal(SP tex, const CBox inverseOpaque.scale(m_renderData.pMonitor->m_scale); // vvv TODO: layered blur fbs? - const bool USENEWOPTIMIZE = shouldUseNewBlurOptimizations(m_renderData.currentLS.lock(), m_renderData.currentWindow.lock()) && !data.blockBlurOptimization; + const bool USENEWOPTIMIZE = shouldUseNewBlurOptimizations(m_renderData.currentLS.lock(), m_renderData.currentWindow.lock()) && !data.blockBlurOptimization; - CFramebuffer* POUTFB = nullptr; + SP POUTFB = nullptr; if (!USENEWOPTIMIZE) { inverseOpaque.translate(box.pos()); m_renderData.renderModif.applyToRegion(inverseOpaque); inverseOpaque.intersect(texDamage); POUTFB = blurMainFramebufferWithDamage(data.a, &inverseOpaque); } else - POUTFB = &m_renderData.pCurrentMonData->blurFB; + POUTFB = m_renderData.pCurrentMonData->blurFB; m_renderData.currentFB->bind(); - // make a stencil for rounded corners to work with blur - scissor(nullptr); // allow the entire window and stencil to render - glClearStencil(0); - glClear(GL_STENCIL_BUFFER_BIT); + auto blurredBG = POUTFB->getTexture(); - setCapStatus(GL_STENCIL_TEST, true); + const auto NEEDS_STENCIL = m_renderData.discardMode != 0 && (!data.blockBlurOptimization || (m_renderData.discardMode & DISCARD_ALPHA)); + if (!*PBLEND) { - glStencilFunc(GL_ALWAYS, 1, 0xFF); - glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + if (NEEDS_STENCIL) { + scissor(nullptr); // allow the entire window and stencil to render + glClearStencil(0); + glClear(GL_STENCIL_BUFFER_BIT); - glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); - if (USENEWOPTIMIZE && !(m_renderData.discardMode & DISCARD_ALPHA)) - renderRect(box, CHyprColor(0, 0, 0, 0), SRectRenderData{.round = data.round, .roundingPower = data.roundingPower}); - else - renderTexture(tex, box, - STextureRenderData{.a = data.a, - .round = data.round, - .roundingPower = data.roundingPower, - .discardActive = true, - .allowCustomUV = true, - .wrapX = data.wrapX, - .wrapY = data.wrapY}); // discard opaque - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + setCapStatus(GL_STENCIL_TEST, true); - glStencilFunc(GL_EQUAL, 1, 0xFF); - glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + glStencilFunc(GL_ALWAYS, 1, 0xFF); + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); - // stencil done. Render everything. - const auto LASTTL = m_renderData.primarySurfaceUVTopLeft; - const auto LASTBR = m_renderData.primarySurfaceUVBottomRight; + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + renderTexture(tex, box, + STextureRenderData{.a = data.a, + .round = data.round, + .roundingPower = data.roundingPower, + .discardActive = true, + .allowCustomUV = true, + .wrapX = data.wrapX, + .wrapY = data.wrapY}); // discard opaque and alpha < discardOpacity - CBox transformedBox = box; - transformedBox.transform(Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x, - m_renderData.pMonitor->m_transformedSize.y); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - CBox monitorSpaceBox = {transformedBox.pos().x / m_renderData.pMonitor->m_pixelSize.x * m_renderData.pMonitor->m_transformedSize.x, - transformedBox.pos().y / m_renderData.pMonitor->m_pixelSize.y * m_renderData.pMonitor->m_transformedSize.y, - transformedBox.width / m_renderData.pMonitor->m_pixelSize.x * m_renderData.pMonitor->m_transformedSize.x, - transformedBox.height / m_renderData.pMonitor->m_pixelSize.y * m_renderData.pMonitor->m_transformedSize.y}; + glStencilFunc(GL_EQUAL, 1, 0xFF); + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + } - m_renderData.primarySurfaceUVTopLeft = monitorSpaceBox.pos() / m_renderData.pMonitor->m_transformedSize; - m_renderData.primarySurfaceUVBottomRight = (monitorSpaceBox.pos() + monitorSpaceBox.size()) / m_renderData.pMonitor->m_transformedSize; + // stencil done. Render everything. + const auto LASTTL = m_renderData.primarySurfaceUVTopLeft; + const auto LASTBR = m_renderData.primarySurfaceUVBottomRight; - static auto PBLURIGNOREOPACITY = CConfigValue("decoration:blur:ignore_opacity"); - pushMonitorTransformEnabled(true); - if (!USENEWOPTIMIZE) - setRenderModifEnabled(false); - renderTextureInternal(POUTFB->getTexture(), box, - STextureRenderData{ - .damage = &texDamage, - .a = (*PBLURIGNOREOPACITY ? data.blurA : data.a * data.blurA) * data.overallA, - .round = data.round, - .roundingPower = data.roundingPower, - .discardActive = false, - .allowCustomUV = true, - .noAA = false, - .wrapX = data.wrapX, - .wrapY = data.wrapY, - }); - if (!USENEWOPTIMIZE) - setRenderModifEnabled(true); - popMonitorTransformEnabled(); + CBox transformedBox = box; + transformedBox.transform(Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x, + m_renderData.pMonitor->m_transformedSize.y); - m_renderData.primarySurfaceUVTopLeft = LASTTL; - m_renderData.primarySurfaceUVBottomRight = LASTBR; + CBox monitorSpaceBox = {transformedBox.pos().x / m_renderData.pMonitor->m_pixelSize.x * m_renderData.pMonitor->m_transformedSize.x, + transformedBox.pos().y / m_renderData.pMonitor->m_pixelSize.y * m_renderData.pMonitor->m_transformedSize.y, + transformedBox.width / m_renderData.pMonitor->m_pixelSize.x * m_renderData.pMonitor->m_transformedSize.x, + transformedBox.height / m_renderData.pMonitor->m_pixelSize.y * m_renderData.pMonitor->m_transformedSize.y}; - // render the window, but clear stencil - glClearStencil(0); - glClear(GL_STENCIL_BUFFER_BIT); + m_renderData.primarySurfaceUVTopLeft = monitorSpaceBox.pos() / m_renderData.pMonitor->m_transformedSize; + m_renderData.primarySurfaceUVBottomRight = (monitorSpaceBox.pos() + monitorSpaceBox.size()) / m_renderData.pMonitor->m_transformedSize; + + static auto PBLURIGNOREOPACITY = CConfigValue("decoration:blur:ignore_opacity"); + pushMonitorTransformEnabled(true); + bool renderModif = m_renderData.renderModif.enabled; + if (!USENEWOPTIMIZE) + setRenderModifEnabled(false); + renderTextureInternal(blurredBG, box, + STextureRenderData{ + .damage = &texDamage, + .a = (*PBLURIGNOREOPACITY ? data.blurA : data.a * data.blurA) * data.overallA, + .round = data.round, + .roundingPower = data.roundingPower, + .discardActive = false, + .allowCustomUV = true, + .noAA = false, + .wrapX = data.wrapX, + .wrapY = data.wrapY, + }); + if (!USENEWOPTIMIZE) + setRenderModifEnabled(renderModif); + popMonitorTransformEnabled(); + + if (NEEDS_STENCIL) + setCapStatus(GL_STENCIL_TEST, false); + + m_renderData.primarySurfaceUVTopLeft = LASTTL; + m_renderData.primarySurfaceUVBottomRight = LASTBR; + } // draw window - setCapStatus(GL_STENCIL_TEST, false); renderTextureInternal(tex, box, STextureRenderData{ .damage = &texDamage, .a = data.a * data.overallA, + .blur = *PBLEND, .round = data.round, .roundingPower = data.roundingPower, - .discardActive = false, + .discardActive = *PBLEND && NEEDS_STENCIL, .allowCustomUV = true, .allowDim = true, .noAA = false, .wrapX = data.wrapX, .wrapY = data.wrapY, + .blurredBG = blurredBG, }); - glStencilMask(0xFF); - glStencilFunc(GL_ALWAYS, 1, 0xFF); + GLFB(m_renderData.currentFB)->invalidate({GL_STENCIL_ATTACHMENT}); scissor(nullptr); } @@ -2510,19 +2216,22 @@ void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& gr const auto BLEND = m_blend; blend(true); - useProgram(m_shaders->m_shBORDER1.program); + WP shader; - const bool skipCM = !m_cmSupported || m_renderData.pMonitor->m_imageDescription->id() == DEFAULT_IMAGE_DESCRIPTION->id(); - m_shaders->m_shBORDER1.setUniformInt(SHADER_SKIP_CM, skipCM); - if (!skipCM) - passCMUniforms(m_shaders->m_shBORDER1, DEFAULT_IMAGE_DESCRIPTION); + const bool IS_ICC = g_pHyprRenderer->workBufferImageDescription()->value().icc.present; + const bool skipCM = !m_cmSupported || g_pHyprRenderer->workBufferImageDescription()->id() == DEFAULT_IMAGE_DESCRIPTION->id(); + if (!skipCM) { + shader = useShader(getShaderVariant(SH_FRAG_BORDER1, SH_FEAT_ROUNDING | SH_FEAT_CM | (IS_ICC ? SH_FEAT_ICC : SH_FEAT_TONEMAP | SH_FEAT_SDR_MOD))); + passCMUniforms(shader, DEFAULT_IMAGE_DESCRIPTION); + } else + shader = useShader(getShaderVariant(SH_FRAG_BORDER1, SH_FEAT_ROUNDING)); - m_shaders->m_shBORDER1.setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); - m_shaders->m_shBORDER1.setUniform4fv(SHADER_GRADIENT, grad.m_colorsOkLabA.size() / 4, grad.m_colorsOkLabA); - m_shaders->m_shBORDER1.setUniformInt(SHADER_GRADIENT_LENGTH, grad.m_colorsOkLabA.size() / 4); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_ANGLE, sc(grad.m_angle / (std::numbers::pi / 180.0)) % 360 * (std::numbers::pi / 180.0)); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_ALPHA, data.a); - m_shaders->m_shBORDER1.setUniformInt(SHADER_GRADIENT2_LENGTH, 0); + shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); + shader->setUniform4fv(SHADER_GRADIENT, grad.m_colorsOkLabA.size() / 4, grad.m_colorsOkLabA); + shader->setUniformInt(SHADER_GRADIENT_LENGTH, grad.m_colorsOkLabA.size() / 4); + shader->setUniformFloat(SHADER_ANGLE, sc(grad.m_angle / (std::numbers::pi / 180.0)) % 360 * (std::numbers::pi / 180.0)); + shader->setUniformFloat(SHADER_ALPHA, data.a); + shader->setUniformInt(SHADER_GRADIENT2_LENGTH, 0); CBox transformedBox = newBox; transformedBox.transform(Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x, @@ -2531,15 +2240,15 @@ void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& gr const auto TOPLEFT = Vector2D(transformedBox.x, transformedBox.y); const auto FULLSIZE = Vector2D(transformedBox.width, transformedBox.height); - m_shaders->m_shBORDER1.setUniformFloat2(SHADER_TOP_LEFT, sc(TOPLEFT.x), sc(TOPLEFT.y)); - m_shaders->m_shBORDER1.setUniformFloat2(SHADER_FULL_SIZE, sc(FULLSIZE.x), sc(FULLSIZE.y)); - m_shaders->m_shBORDER1.setUniformFloat2(SHADER_FULL_SIZE_UNTRANSFORMED, sc(newBox.width), sc(newBox.height)); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_RADIUS, round); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_RADIUS_OUTER, data.outerRound == -1 ? round : data.outerRound); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_ROUNDING_POWER, data.roundingPower); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_THICK, scaledBorderSize); + shader->setUniformFloat2(SHADER_TOP_LEFT, sc(TOPLEFT.x), sc(TOPLEFT.y)); + shader->setUniformFloat2(SHADER_FULL_SIZE, sc(FULLSIZE.x), sc(FULLSIZE.y)); + shader->setUniformFloat2(SHADER_FULL_SIZE_UNTRANSFORMED, sc(newBox.width), sc(newBox.height)); + shader->setUniformFloat(SHADER_RADIUS, round); + shader->setUniformFloat(SHADER_RADIUS_OUTER, data.outerRound == -1 ? round : data.outerRound); + shader->setUniformFloat(SHADER_ROUNDING_POWER, data.roundingPower); + shader->setUniformFloat(SHADER_THICK, scaledBorderSize); - glBindVertexArray(m_shaders->m_shBORDER1.uniformLocations[SHADER_SHADER_VAO]); + glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); // calculate the border's region, which we need to render over. No need to run the shader on // things outside there @@ -2551,7 +2260,7 @@ void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& gr if (!borderRegion.empty()) { borderRegion.forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } @@ -2594,23 +2303,25 @@ void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& gr const auto BLEND = m_blend; blend(true); - useProgram(m_shaders->m_shBORDER1.program); + WP shader; + const bool IS_ICC = g_pHyprRenderer->workBufferImageDescription()->value().icc.present; + const bool skipCM = !m_cmSupported || g_pHyprRenderer->workBufferImageDescription()->id() == DEFAULT_IMAGE_DESCRIPTION->id(); + if (!skipCM) { + shader = useShader(getShaderVariant(SH_FRAG_BORDER1, SH_FEAT_ROUNDING | SH_FEAT_CM | (IS_ICC ? SH_FEAT_ICC : SH_FEAT_TONEMAP | SH_FEAT_SDR_MOD))); + passCMUniforms(shader, DEFAULT_IMAGE_DESCRIPTION); + } else + shader = useShader(getShaderVariant(SH_FRAG_BORDER1, SH_FEAT_ROUNDING)); - const bool skipCM = !m_cmSupported || m_renderData.pMonitor->m_imageDescription->id() == DEFAULT_IMAGE_DESCRIPTION->id(); - m_shaders->m_shBORDER1.setUniformInt(SHADER_SKIP_CM, skipCM); - if (!skipCM) - passCMUniforms(m_shaders->m_shBORDER1, DEFAULT_IMAGE_DESCRIPTION); - - m_shaders->m_shBORDER1.setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); - m_shaders->m_shBORDER1.setUniform4fv(SHADER_GRADIENT, grad1.m_colorsOkLabA.size() / 4, grad1.m_colorsOkLabA); - m_shaders->m_shBORDER1.setUniformInt(SHADER_GRADIENT_LENGTH, grad1.m_colorsOkLabA.size() / 4); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_ANGLE, sc(grad1.m_angle / (std::numbers::pi / 180.0)) % 360 * (std::numbers::pi / 180.0)); + shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); + shader->setUniform4fv(SHADER_GRADIENT, grad1.m_colorsOkLabA.size() / 4, grad1.m_colorsOkLabA); + shader->setUniformInt(SHADER_GRADIENT_LENGTH, grad1.m_colorsOkLabA.size() / 4); + shader->setUniformFloat(SHADER_ANGLE, sc(grad1.m_angle / (std::numbers::pi / 180.0)) % 360 * (std::numbers::pi / 180.0)); if (!grad2.m_colorsOkLabA.empty()) - m_shaders->m_shBORDER1.setUniform4fv(SHADER_GRADIENT2, grad2.m_colorsOkLabA.size() / 4, grad2.m_colorsOkLabA); - m_shaders->m_shBORDER1.setUniformInt(SHADER_GRADIENT2_LENGTH, grad2.m_colorsOkLabA.size() / 4); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_ANGLE2, sc(grad2.m_angle / (std::numbers::pi / 180.0)) % 360 * (std::numbers::pi / 180.0)); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_ALPHA, data.a); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_GRADIENT_LERP, lerp); + shader->setUniform4fv(SHADER_GRADIENT2, grad2.m_colorsOkLabA.size() / 4, grad2.m_colorsOkLabA); + shader->setUniformInt(SHADER_GRADIENT2_LENGTH, grad2.m_colorsOkLabA.size() / 4); + shader->setUniformFloat(SHADER_ANGLE2, sc(grad2.m_angle / (std::numbers::pi / 180.0)) % 360 * (std::numbers::pi / 180.0)); + shader->setUniformFloat(SHADER_ALPHA, data.a); + shader->setUniformFloat(SHADER_GRADIENT_LERP, lerp); CBox transformedBox = newBox; transformedBox.transform(Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x, @@ -2619,15 +2330,15 @@ void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& gr const auto TOPLEFT = Vector2D(transformedBox.x, transformedBox.y); const auto FULLSIZE = Vector2D(transformedBox.width, transformedBox.height); - m_shaders->m_shBORDER1.setUniformFloat2(SHADER_TOP_LEFT, sc(TOPLEFT.x), sc(TOPLEFT.y)); - m_shaders->m_shBORDER1.setUniformFloat2(SHADER_FULL_SIZE, sc(FULLSIZE.x), sc(FULLSIZE.y)); - m_shaders->m_shBORDER1.setUniformFloat2(SHADER_FULL_SIZE_UNTRANSFORMED, sc(newBox.width), sc(newBox.height)); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_RADIUS, round); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_RADIUS_OUTER, data.outerRound == -1 ? round : data.outerRound); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_ROUNDING_POWER, data.roundingPower); - m_shaders->m_shBORDER1.setUniformFloat(SHADER_THICK, scaledBorderSize); + shader->setUniformFloat2(SHADER_TOP_LEFT, sc(TOPLEFT.x), sc(TOPLEFT.y)); + shader->setUniformFloat2(SHADER_FULL_SIZE, sc(FULLSIZE.x), sc(FULLSIZE.y)); + shader->setUniformFloat2(SHADER_FULL_SIZE_UNTRANSFORMED, sc(newBox.width), sc(newBox.height)); + shader->setUniformFloat(SHADER_RADIUS, round); + shader->setUniformFloat(SHADER_RADIUS_OUTER, data.outerRound == -1 ? round : data.outerRound); + shader->setUniformFloat(SHADER_ROUNDING_POWER, data.roundingPower); + shader->setUniformFloat(SHADER_THICK, scaledBorderSize); - glBindVertexArray(m_shaders->m_shBORDER1.uniformLocations[SHADER_SHADER_VAO]); + glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); // calculate the border's region, which we need to render over. No need to run the shader on // things outside there @@ -2639,7 +2350,7 @@ void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& gr if (!borderRegion.empty()) { borderRegion.forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } @@ -2672,29 +2383,29 @@ void CHyprOpenGLImpl::renderRoundedShadow(const CBox& box, int round, float roun blend(true); - useProgram(m_shaders->m_shSHADOW.program); - const bool skipCM = !m_cmSupported || m_renderData.pMonitor->m_imageDescription->id() == DEFAULT_IMAGE_DESCRIPTION->id(); - m_shaders->m_shSHADOW.setUniformInt(SHADER_SKIP_CM, skipCM); + const bool IS_ICC = g_pHyprRenderer->workBufferImageDescription()->value().icc.present; + const bool skipCM = !m_cmSupported || g_pHyprRenderer->workBufferImageDescription()->id() == DEFAULT_IMAGE_DESCRIPTION->id(); + auto shader = useShader(getShaderVariant(SH_FRAG_SHADOW, skipCM ? 0 : SH_FEAT_CM | (IS_ICC ? SH_FEAT_ICC : SH_FEAT_TONEMAP | SH_FEAT_SDR_MOD))); if (!skipCM) - passCMUniforms(m_shaders->m_shSHADOW, DEFAULT_IMAGE_DESCRIPTION); + passCMUniforms(shader, DEFAULT_IMAGE_DESCRIPTION); - m_shaders->m_shSHADOW.setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); - m_shaders->m_shSHADOW.setUniformFloat4(SHADER_COLOR, col.r, col.g, col.b, col.a * a); + shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); + shader->setUniformFloat4(SHADER_COLOR, col.r, col.g, col.b, col.a * a); const auto TOPLEFT = Vector2D(range + round, range + round); const auto BOTTOMRIGHT = Vector2D(newBox.width - (range + round), newBox.height - (range + round)); const auto FULLSIZE = Vector2D(newBox.width, newBox.height); // Rounded corners - m_shaders->m_shSHADOW.setUniformFloat2(SHADER_TOP_LEFT, sc(TOPLEFT.x), sc(TOPLEFT.y)); - m_shaders->m_shSHADOW.setUniformFloat2(SHADER_BOTTOM_RIGHT, sc(BOTTOMRIGHT.x), sc(BOTTOMRIGHT.y)); - m_shaders->m_shSHADOW.setUniformFloat2(SHADER_FULL_SIZE, sc(FULLSIZE.x), sc(FULLSIZE.y)); - m_shaders->m_shSHADOW.setUniformFloat(SHADER_RADIUS, range + round); - m_shaders->m_shSHADOW.setUniformFloat(SHADER_ROUNDING_POWER, roundingPower); - m_shaders->m_shSHADOW.setUniformFloat(SHADER_RANGE, range); - m_shaders->m_shSHADOW.setUniformFloat(SHADER_SHADOW_POWER, SHADOWPOWER); + shader->setUniformFloat2(SHADER_TOP_LEFT, sc(TOPLEFT.x), sc(TOPLEFT.y)); + shader->setUniformFloat2(SHADER_BOTTOM_RIGHT, sc(BOTTOMRIGHT.x), sc(BOTTOMRIGHT.y)); + shader->setUniformFloat2(SHADER_FULL_SIZE, sc(FULLSIZE.x), sc(FULLSIZE.y)); + shader->setUniformFloat(SHADER_RADIUS, range + round); + shader->setUniformFloat(SHADER_ROUNDING_POWER, roundingPower); + shader->setUniformFloat(SHADER_RANGE, range); + shader->setUniformFloat(SHADER_SHADOW_POWER, SHADOWPOWER); - glBindVertexArray(m_shaders->m_shSHADOW.uniformLocations[SHADER_SHADER_VAO]); + glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); if (m_renderData.clipBox.width != 0 && m_renderData.clipBox.height != 0) { CRegion damageClip{m_renderData.clipBox.x, m_renderData.clipBox.y, m_renderData.clipBox.width, m_renderData.clipBox.height}; @@ -2702,13 +2413,13 @@ void CHyprOpenGLImpl::renderRoundedShadow(const CBox& box, int round, float roun if (!damageClip.empty()) { damageClip.forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } } else { m_renderData.damage.forEachRect([this](const auto& RECT) { - scissor(&RECT); + scissor(&RECT, m_renderData.transformDamage); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } @@ -2717,21 +2428,25 @@ void CHyprOpenGLImpl::renderRoundedShadow(const CBox& box, int round, float roun } void CHyprOpenGLImpl::saveBufferForMirror(const CBox& box) { + if (!m_renderData.pCurrentMonData->monitorMirrorFB) + m_renderData.pCurrentMonData->monitorMirrorFB = g_pHyprRenderer->createFB(); - if (!m_renderData.pCurrentMonData->monitorMirrorFB.isAllocated()) - m_renderData.pCurrentMonData->monitorMirrorFB.alloc(m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y, - m_renderData.pMonitor->m_output->state->state().drmFormat); + if (!m_renderData.pCurrentMonData->monitorMirrorFB->isAllocated()) + m_renderData.pCurrentMonData->monitorMirrorFB->alloc(m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y, + m_renderData.pMonitor->m_output->state->state().drmFormat); - m_renderData.pCurrentMonData->monitorMirrorFB.bind(); + m_renderData.pCurrentMonData->monitorMirrorFB->bind(); blend(false); renderTexture(m_renderData.currentFB->getTexture(), box, STextureRenderData{ - .a = 1.f, + .damage = &m_renderData.finalDamage, + .a = 1.F, .round = 0, .discardActive = false, .allowCustomUV = false, + .cmBackToSRGB = true, }); blend(true); @@ -2753,7 +2468,7 @@ void CHyprOpenGLImpl::renderMirrored() { monbox.x = (monitor->m_transformedSize.x - monbox.w) / 2; monbox.y = (monitor->m_transformedSize.y - monbox.h) / 2; - const auto PFB = &m_monitorRenderResources[mirrored].monitorMirrorFB; + auto PFB = m_monitorRenderResources[mirrored].monitorMirrorFB; if (!PFB->isAllocated() || !PFB->getTexture()) return; @@ -2827,7 +2542,7 @@ std::string CHyprOpenGLImpl::resolveAssetPath(const std::string& filename) { return fullPath; } -SP CHyprOpenGLImpl::loadAsset(const std::string& filename) { +SP CHyprOpenGLImpl::loadAsset(const std::string& filename) { const std::string fullPath = resolveAssetPath(filename); @@ -2849,12 +2564,11 @@ SP CHyprOpenGLImpl::loadAsset(const std::string& filename) { return tex; } -SP CHyprOpenGLImpl::texFromCairo(cairo_surface_t* cairo) { +SP CHyprOpenGLImpl::texFromCairo(cairo_surface_t* cairo) { const auto CAIROFORMAT = cairo_image_surface_get_format(cairo); - auto tex = makeShared(); + auto tex = makeShared(); - tex->allocate(); - tex->m_size = {cairo_image_surface_get_width(cairo), cairo_image_surface_get_height(cairo)}; + tex->allocate({cairo_image_surface_get_width(cairo), cairo_image_surface_get_height(cairo)}); const GLint glIFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB32F : GL_RGBA; const GLint glFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB : GL_RGBA; @@ -2875,8 +2589,8 @@ SP CHyprOpenGLImpl::texFromCairo(cairo_surface_t* cairo) { return tex; } -SP CHyprOpenGLImpl::renderText(const std::string& text, CHyprColor col, int pt, bool italic, const std::string& fontFamily, int maxWidth, int weight) { - SP tex = makeShared(); +SP CHyprOpenGLImpl::renderText(const std::string& text, CHyprColor col, int pt, bool italic, const std::string& fontFamily, int maxWidth, int weight) { + SP tex = makeShared(); static auto FONT = CConfigValue("misc:font_family"); @@ -2938,8 +2652,7 @@ SP CHyprOpenGLImpl::renderText(const std::string& text, CHyprColor col cairo_surface_flush(CAIROSURFACE); - tex->allocate(); - tex->m_size = {cairo_image_surface_get_width(CAIROSURFACE), cairo_image_surface_get_height(CAIROSURFACE)}; + tex->allocate({cairo_image_surface_get_width(CAIROSURFACE), cairo_image_surface_get_height(CAIROSURFACE)}); const auto DATA = cairo_image_surface_get_data(CAIROSURFACE); tex->bind(); @@ -2956,8 +2669,8 @@ SP CHyprOpenGLImpl::renderText(const std::string& text, CHyprColor col } void CHyprOpenGLImpl::initMissingAssetTexture() { - SP tex = makeShared(); - tex->allocate(); + SP tex = makeShared(); + tex->allocate({512, 512}); const auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 512, 512); const auto CAIRO = cairo_create(CAIROSURFACE); @@ -2996,12 +2709,14 @@ void CHyprOpenGLImpl::initMissingAssetTexture() { m_missingAssetTexture = tex; } -void CHyprOpenGLImpl::useProgram(GLuint prog) { - if (m_currentProgram == prog) - return; +WP CHyprOpenGLImpl::useShader(WP prog) { + if (m_currentProgram == prog->program()) + return prog; - glUseProgram(prog); - m_currentProgram = prog; + glUseProgram(prog->program()); + m_currentProgram = prog->program(); + + return prog; } void CHyprOpenGLImpl::initAssets() { @@ -3107,16 +2822,19 @@ void CHyprOpenGLImpl::createBGTextureForMonitor(PHLMONITOR pMonitor) { if (!m_backgroundResource->m_ready) return; + if (!m_monitorBGFBs.contains(pMonitor)) + m_monitorBGFBs[pMonitor] = g_pHyprRenderer->createFB(); + // release the last tex if exists - const auto PFB = &m_monitorBGFBs[pMonitor]; + auto PFB = m_monitorBGFBs[pMonitor]; PFB->release(); PFB->alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, pMonitor->m_output->state->state().drmFormat); // create a new one with cairo - SP tex = makeShared(); + SP tex = makeShared(); - tex->allocate(); + tex->allocate(pMonitor->m_pixelSize); const auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y); const auto CAIRO = cairo_create(CAIROSURFACE); @@ -3158,7 +2876,7 @@ void CHyprOpenGLImpl::createBGTextureForMonitor(PHLMONITOR pMonitor) { blend(true); clear(CHyprColor{0, 0, 0, 1}); - SP backgroundTexture = texFromCairo(m_backgroundResource->m_asset.cairoSurface->cairo()); + SP backgroundTexture = texFromCairo(m_backgroundResource->m_asset.cairoSurface->cairo()); // first render the background if (backgroundTexture) { @@ -3213,7 +2931,7 @@ void CHyprOpenGLImpl::clearWithTex() { data.box = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y}; data.a = m_renderData.pMonitor->m_backgroundOpacity->value(); data.flipEndFrame = true; - data.tex = TEXIT->second.getTexture(); + data.tex = TEXIT->second->getTexture(); g_pHyprRenderer->m_renderPass.add(makeUnique(std::move(data))); } } @@ -3226,19 +2944,19 @@ void CHyprOpenGLImpl::destroyMonitorResources(PHLMONITORREF pMonitor) { auto RESIT = g_pHyprOpenGL->m_monitorRenderResources.find(pMonitor); if (RESIT != g_pHyprOpenGL->m_monitorRenderResources.end()) { - RESIT->second.mirrorFB.release(); - RESIT->second.offloadFB.release(); - RESIT->second.mirrorSwapFB.release(); - RESIT->second.monitorMirrorFB.release(); - RESIT->second.blurFB.release(); - RESIT->second.offMainFB.release(); - RESIT->second.stencilTex->destroyTexture(); + RESIT->second.mirrorFB.reset(); + RESIT->second.offloadFB.reset(); + RESIT->second.mirrorSwapFB.reset(); + RESIT->second.monitorMirrorFB.reset(); + RESIT->second.blurFB.reset(); + RESIT->second.offMainFB.reset(); + RESIT->second.stencilTex.reset(); g_pHyprOpenGL->m_monitorRenderResources.erase(RESIT); } auto TEXIT = g_pHyprOpenGL->m_monitorBGFBs.find(pMonitor); if (TEXIT != g_pHyprOpenGL->m_monitorBGFBs.end()) { - TEXIT->second.release(); + TEXIT->second.reset(); g_pHyprOpenGL->m_monitorBGFBs.erase(TEXIT); } @@ -3259,19 +2977,21 @@ void CHyprOpenGLImpl::restoreMatrix() { } void CHyprOpenGLImpl::bindOffMain() { - if (!m_renderData.pCurrentMonData->offMainFB.isAllocated()) { - m_renderData.pCurrentMonData->offMainFB.alloc(m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y, - m_renderData.pMonitor->m_output->state->state().drmFormat); + if (!m_renderData.pCurrentMonData->offMainFB) + m_renderData.pCurrentMonData->offMainFB = g_pHyprRenderer->createFB(); - m_renderData.pCurrentMonData->offMainFB.addStencil(m_renderData.pCurrentMonData->stencilTex); + if (!m_renderData.pCurrentMonData->offMainFB->isAllocated()) { + m_renderData.pCurrentMonData->offMainFB->addStencil(m_renderData.pCurrentMonData->stencilTex); + m_renderData.pCurrentMonData->offMainFB->alloc(m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y, + m_renderData.pMonitor->m_output->state->state().drmFormat); } - m_renderData.pCurrentMonData->offMainFB.bind(); + m_renderData.pCurrentMonData->offMainFB->bind(); clear(CHyprColor(0, 0, 0, 0)); - m_renderData.currentFB = &m_renderData.pCurrentMonData->offMainFB; + m_renderData.currentFB = m_renderData.pCurrentMonData->offMainFB; } -void CHyprOpenGLImpl::renderOffToMain(CFramebuffer* off) { +void CHyprOpenGLImpl::renderOffToMain(CGLFramebuffer* off) { CBox monbox = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y}; renderTexturePrimitive(off->getTexture(), monbox); } @@ -3317,9 +3037,9 @@ void CHyprOpenGLImpl::setCapStatus(int cap, bool status) { if (idx == CAP_STATUS_END) { if (status) - glEnable(cap); + GLCALL(glEnable(cap)) else - glDisable(cap); + GLCALL(glDisable(cap)); return; } @@ -3329,31 +3049,62 @@ void CHyprOpenGLImpl::setCapStatus(int cap, bool status) { if (status) { m_capStatus[idx] = status; - glEnable(cap); + GLCALL(glEnable(cap)); } else { m_capStatus[idx] = status; - glDisable(cap); + GLCALL(glDisable(cap)); } } -uint32_t CHyprOpenGLImpl::getPreferredReadFormat(PHLMONITOR pMonitor) { +DRMFormat CHyprOpenGLImpl::getPreferredReadFormat(PHLMONITOR pMonitor) { static const auto PFORCE8BIT = CConfigValue("misc:screencopy_force_8b"); - if (!*PFORCE8BIT) - return pMonitor->m_output->state->state().drmFormat; + auto monFmt = pMonitor->m_output->state->state().drmFormat; - auto fmt = pMonitor->m_output->state->state().drmFormat; + if (*PFORCE8BIT) + if (monFmt == DRM_FORMAT_BGRA1010102 || monFmt == DRM_FORMAT_ARGB2101010 || monFmt == DRM_FORMAT_XRGB2101010 || monFmt == DRM_FORMAT_BGRX1010102 || + monFmt == DRM_FORMAT_XBGR2101010) + monFmt = DRM_FORMAT_XRGB8888; - if (fmt == DRM_FORMAT_BGRA1010102 || fmt == DRM_FORMAT_ARGB2101010 || fmt == DRM_FORMAT_XRGB2101010 || fmt == DRM_FORMAT_BGRX1010102 || fmt == DRM_FORMAT_XBGR2101010) - return DRM_FORMAT_XRGB8888; + return monFmt; +} - return fmt; +std::vector CHyprOpenGLImpl::getDRMFormatModifiers(DRMFormat drmFormat) { + SDRMFormat format; + + for (const auto& fmt : m_drmFormats) { + if (fmt.drmFormat == drmFormat) { + format = fmt; + break; + } + } + + return format.modifiers; } bool CHyprOpenGLImpl::explicitSyncSupported() { return m_exts.EGL_ANDROID_native_fence_sync_ext; } +WP CHyprOpenGLImpl::getShaderVariant(ePreparedFragmentShader frag, ShaderFeatureFlags features) { + if (!m_shaders->fragVariants[frag].contains(features)) { + auto shader = makeShared(); + + Log::logger->log(Log::INFO, "compiling feature set {} for {}", features, FRAG_SHADERS[frag]); + + const auto fragSrc = g_pShaderLoader->getVariantSource(frag, features); + + if (!shader->createProgram(m_shaders->TEXVERTSRC, fragSrc, true, true)) + Log::logger->log(Log::ERR, "shader features {} failed for {}", features, FRAG_SHADERS[frag]); + + m_shaders->fragVariants[frag][features] = shader; + return shader; + } + + ASSERT(m_shaders->fragVariants[frag][features]); + return m_shaders->fragVariants[frag][features]; +} + std::vector CHyprOpenGLImpl::getDRMFormats() { return m_drmFormats; } @@ -3424,7 +3175,7 @@ UP CEGLSync::create() { EGLSyncKHR sync = g_pHyprOpenGL->m_proc.eglCreateSyncKHR(g_pHyprOpenGL->m_eglDisplay, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr); - if (sync == EGL_NO_SYNC_KHR) { + if UNLIKELY (sync == EGL_NO_SYNC_KHR) { Log::logger->log(Log::ERR, "eglCreateSyncKHR failed"); return nullptr; } @@ -3433,7 +3184,7 @@ UP CEGLSync::create() { glFlush(); int fd = g_pHyprOpenGL->m_proc.eglDupNativeFenceFDANDROID(g_pHyprOpenGL->m_eglDisplay, sync); - if (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) { + if UNLIKELY (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) { Log::logger->log(Log::ERR, "eglDupNativeFenceFDANDROID failed"); return nullptr; } @@ -3447,10 +3198,10 @@ UP CEGLSync::create() { } CEGLSync::~CEGLSync() { - if (m_sync == EGL_NO_SYNC_KHR) + if UNLIKELY (m_sync == EGL_NO_SYNC_KHR) return; - if (g_pHyprOpenGL && g_pHyprOpenGL->m_proc.eglDestroySyncKHR(g_pHyprOpenGL->m_eglDisplay, m_sync) != EGL_TRUE) + if UNLIKELY (g_pHyprOpenGL && g_pHyprOpenGL->m_proc.eglDestroySyncKHR(g_pHyprOpenGL->m_eglDisplay, m_sync) != EGL_TRUE) Log::logger->log(Log::ERR, "eglDestroySyncKHR failed"); } diff --git a/src/render/OpenGL.hpp b/src/render/OpenGL.hpp index e71429b7..c9008447 100644 --- a/src/render/OpenGL.hpp +++ b/src/render/OpenGL.hpp @@ -20,6 +20,7 @@ #include "Texture.hpp" #include "Framebuffer.hpp" #include "Renderbuffer.hpp" +#include "desktop/DesktopTypes.hpp" #include "pass/Pass.hpp" #include @@ -31,17 +32,29 @@ #include "../debug/TracyDefines.hpp" #include "../protocols/core/Compositor.hpp" +#include "render/ShaderLoader.hpp" +#include "render/gl/GLFramebuffer.hpp" +#include "render/gl/GLRenderbuffer.hpp" +#include "render/gl/GLTexture.hpp" + +#define GLFB(ifb) dc(ifb.get()) struct gbm_device; -class CHyprRenderer; +class IHyprRenderer; -inline const float fullVerts[] = { - 1, 0, // top right - 0, 0, // top left - 1, 1, // bottom right - 0, 1, // bottom left +struct SVertex { + float x, y; // position + float u, v; // uv }; -inline const float fanVertsFull[] = {-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f}; + +constexpr std::array fullVerts = {{ + {0.0f, 0.0f, 0.0f, 0.0f}, // top-left + {0.0f, 1.0f, 0.0f, 1.0f}, // bottom-left + {1.0f, 0.0f, 1.0f, 0.0f}, // top-right + {1.0f, 1.0f, 1.0f, 1.0f}, // bottom-right +}}; + +inline const float fanVertsFull[] = {-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f}; enum eDiscardMode : uint8_t { DISCARD_OPAQUE = 1, @@ -81,37 +94,37 @@ enum eMonitorExtraRenderFBs : uint8_t { FB_MONITOR_RENDER_EXTRA_BLUR, }; +struct SFragShaderDesc { + Render::ePreparedFragmentShader id; + const char* file; +}; + struct SPreparedShaders { + // SPreparedShaders() { + // for (auto& f : frag) { + // f = makeShared(); + // } + // } + std::string TEXVERTSRC; std::string TEXVERTSRC320; - SShader m_shQUAD; - SShader m_shRGBA; - SShader m_shPASSTHRURGBA; - SShader m_shMATTE; - SShader m_shRGBX; - SShader m_shEXT; - SShader m_shBLUR1; - SShader m_shBLUR2; - SShader m_shBLURPREPARE; - SShader m_shBLURFINISH; - SShader m_shSHADOW; - SShader m_shBORDER1; - SShader m_shGLITCH; - SShader m_shCM; + // std::array, SH_FRAG_LAST> frag; + // std::map> fragVariants; + std::array>, Render::SH_FRAG_LAST> fragVariants; }; struct SMonitorRenderData { - CFramebuffer offloadFB; - CFramebuffer mirrorFB; // these are used for some effects, - CFramebuffer mirrorSwapFB; // etc - CFramebuffer offMainFB; - CFramebuffer monitorMirrorFB; // used for mirroring outputs, does not contain artifacts like offloadFB - CFramebuffer blurFB; + SP offloadFB; + SP mirrorFB; // these are used for some effects, + SP mirrorSwapFB; // etc + SP offMainFB; + SP monitorMirrorFB; // used for mirroring outputs, does not contain artifacts like offloadFB + SP blurFB; - SP stencilTex = makeShared(); + SP stencilTex = makeShared(); - bool blurFBDirty = true; - bool blurFBShouldRender = false; + bool blurFBDirty = true; + bool blurFBShouldRender = false; }; struct SCurrentRenderData { @@ -122,9 +135,9 @@ struct SCurrentRenderData { // FIXME: raw pointer galore! 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) + SP currentFB = nullptr; // current rendering to + SP mainFB = nullptr; // main to render to + SP outFB = nullptr; // out to render to (if offloaded, etc) CRegion damage; CRegion finalDamage; // damage used for funal off -> main @@ -135,6 +148,8 @@ struct SCurrentRenderData { bool useNearestNeighbor = false; bool blockScreenShader = false; bool simplePass = false; + bool transformDamage = true; + bool noSimplify = false; Vector2D primarySurfaceUVTopLeft = Vector2D(-1, -1); Vector2D primarySurfaceUVBottomRight = Vector2D(-1, -1); @@ -200,8 +215,11 @@ class CHyprOpenGLImpl { bool noAA = false; bool blockBlurOptimization = false; GLenum wrapX = GL_CLAMP_TO_EDGE, wrapY = GL_CLAMP_TO_EDGE; - bool cmBackToSRGB = false; + bool cmBackToSRGB = false; + bool noCM = false; + bool finalMonitorCM = false; SP cmBackToSRGBSource; + SP blurredBG; }; struct SBorderRenderData { @@ -212,16 +230,17 @@ class CHyprOpenGLImpl { int outerRound = -1; /* use round */ }; - void begin(PHLMONITOR, const CRegion& damage, CFramebuffer* fb = nullptr, std::optional finalDamage = {}); - void beginSimple(PHLMONITOR, const CRegion& damage, SP rb = nullptr, CFramebuffer* fb = nullptr); + void begin(PHLMONITOR, const CRegion& damage, SP fb = nullptr, std::optional finalDamage = {}); + void beginSimple(PHLMONITOR, const CRegion& damage, SP rb = nullptr, SP fb = nullptr); void end(); void renderRect(const CBox&, const CHyprColor&, SRectRenderData data); - void renderTexture(SP, const CBox&, STextureRenderData data); + void renderTexture(SP, const CBox&, STextureRenderData data); void renderRoundedShadow(const CBox&, int round, float roundingPower, int range, const CHyprColor& color, float a = 1.0); void renderBorder(const CBox&, const CGradientValueData&, SBorderRenderData data); void renderBorder(const CBox&, const CGradientValueData&, const CGradientValueData&, float lerp, SBorderRenderData data); - void renderTextureMatte(SP tex, const CBox& pBox, CFramebuffer& matte); + void renderTextureMatte(SP tex, const CBox& pBox, SP matte); + void renderTexturePrimitive(SP tex, const CBox& box); void pushMonitorTransformEnabled(bool enabled); void popMonitorTransformEnabled(); @@ -258,49 +277,49 @@ class CHyprOpenGLImpl { void applyScreenShader(const std::string& path); void bindOffMain(); - void renderOffToMain(CFramebuffer* off); + void renderOffToMain(CGLFramebuffer* off); void bindBackOnMain(); + bool needsACopyFB(PHLMONITOR mon); + std::string resolveAssetPath(const std::string& file); - SP loadAsset(const std::string& file); - SP texFromCairo(cairo_surface_t* cairo); - SP renderText(const std::string& text, CHyprColor col, int pt, bool italic = false, const std::string& fontFamily = "", int maxWidth = 0, int weight = 400); + SP loadAsset(const std::string& file); + SP texFromCairo(cairo_surface_t* cairo); + SP renderText(const std::string& text, CHyprColor col, int pt, bool italic = false, const std::string& fontFamily = "", int maxWidth = 0, int weight = 400); void setDamage(const CRegion& damage, std::optional finalDamage = {}); - uint32_t getPreferredReadFormat(PHLMONITOR pMonitor); - std::vector getDRMFormats(); - EGLImageKHR createEGLImage(const Aquamarine::SDMABUFAttrs& attrs); + DRMFormat getPreferredReadFormat(PHLMONITOR pMonitor); + std::vector getDRMFormats(); + std::vector getDRMFormatModifiers(DRMFormat format); + EGLImageKHR createEGLImage(const Aquamarine::SDMABUFAttrs& attrs); - bool initShaders(); + bool initShaders(const std::string& path = ""); - GLuint createProgram(const std::string&, const std::string&, bool dynamic = false, bool silent = false); - GLuint compileShader(const GLuint&, std::string, bool dynamic = false, bool silent = false); - void useProgram(GLuint prog); + WP useShader(WP prog); - void ensureLockTexturesRendered(bool load); + bool explicitSyncSupported(); + WP getShaderVariant(Render::ePreparedFragmentShader frag, Render::ShaderFeatureFlags features = 0); - bool explicitSyncSupported(); + bool m_shadersInitialized = false; + SP m_shaders; - bool m_shadersInitialized = false; - SP m_shaders; + SCurrentRenderData m_renderData; - SCurrentRenderData m_renderData; + Hyprutils::OS::CFileDescriptor m_gbmFD; + gbm_device* m_gbmDevice = nullptr; + EGLContext m_eglContext = nullptr; + EGLDisplay m_eglDisplay = nullptr; + EGLDeviceEXT m_eglDevice = nullptr; + uint m_failedAssetsNo = 0; - Hyprutils::OS::CFileDescriptor m_gbmFD; - gbm_device* m_gbmDevice = nullptr; - EGLContext m_eglContext = nullptr; - EGLDisplay m_eglDisplay = nullptr; - EGLDeviceEXT m_eglDevice = nullptr; - uint m_failedAssetsNo = 0; + bool m_reloadScreenShader = true; // at launch it can be set - bool m_reloadScreenShader = true; // at launch it can be set - - std::map m_windowFramebuffers; - std::map m_layerFramebuffers; - std::map, CFramebuffer> m_popupFramebuffers; - std::map m_monitorRenderResources; - std::map m_monitorBGFBs; + std::map> m_windowFramebuffers; + std::map> m_layerFramebuffers; + std::map, SP> m_popupFramebuffers; + std::map m_monitorRenderResources; + std::map> m_monitorBGFBs; struct { PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC glEGLImageTargetRenderbufferStorageOES = nullptr; @@ -324,13 +343,14 @@ class CHyprOpenGLImpl { bool EXT_read_format_bgra = false; bool EXT_image_dma_buf_import = false; bool EXT_image_dma_buf_import_modifiers = false; + bool KHR_context_flush_control = false; bool KHR_display_reference = false; bool IMG_context_priority = false; bool EXT_create_context_robustness = false; bool EGL_ANDROID_native_fence_sync_ext = false; } m_exts; - SP m_screencopyDeniedTexture; + SP m_screencopyDeniedTexture; enum eEGLContextVersion : uint8_t { EGL_CONTEXT_GLES_2_0 = 0, @@ -371,22 +391,22 @@ class CHyprOpenGLImpl { bool m_monitorTransformEnabled = false; // do not modify directly std::stack m_monitorTransformStack; - SP m_missingAssetTexture; - SP m_lockDeadTexture; - SP m_lockDead2Texture; - SP m_lockTtyTextTexture; - SShader m_finalScreenShader; + SP m_missingAssetTexture; + SP m_lockDeadTexture; + SP m_lockDead2Texture; + SP m_lockTtyTextTexture; + SP m_finalScreenShader; CTimer m_globalTimer; GLuint m_currentProgram; ASP m_backgroundResource; bool m_backgroundResourceFailed = false; - void logShaderError(const GLuint&, bool program = false, bool silent = false); void createBGTextureForMonitor(PHLMONITOR); void initDRMFormats(); void initEGL(bool gbm); EGLDeviceEXT eglDeviceFromDRMFD(int drmFD); void initAssets(); + void ensureLockTexturesRendered(bool load); void initMissingAssetTexture(); void requestBackgroundResource(); @@ -400,21 +420,22 @@ class CHyprOpenGLImpl { std::optional> getModsForFormat(EGLint format); // returns the out FB, can be either Mirror or MirrorSwap - CFramebuffer* blurMainFramebufferWithDamage(float a, CRegion* damage); - CFramebuffer* blurFramebufferWithDamage(float a, CRegion* damage, CFramebuffer& source); + SP blurMainFramebufferWithDamage(float a, CRegion* damage); + SP blurFramebufferWithDamage(float a, CRegion* damage, CGLFramebuffer& source); - void passCMUniforms(SShader&, const NColorManagement::PImageDescription imageDescription, const NColorManagement::PImageDescription targetImageDescription, - bool modifySDR = false, float sdrMinLuminance = -1.0f, int sdrMaxLuminance = -1); - void passCMUniforms(SShader&, const NColorManagement::PImageDescription imageDescription); - void renderTexturePrimitive(SP tex, const CBox& box); - void renderSplash(cairo_t* const, cairo_surface_t* const, double offset, const Vector2D& size); - void renderRectInternal(const CBox&, const CHyprColor&, const SRectRenderData& data); - void renderRectWithBlurInternal(const CBox&, const CHyprColor&, const SRectRenderData& data); - void renderRectWithDamageInternal(const CBox&, const CHyprColor&, const SRectRenderData& data); - void renderTextureInternal(SP, const CBox&, const STextureRenderData& data); - void renderTextureWithBlurInternal(SP, const CBox&, const STextureRenderData& data); + void passCMUniforms(WP, const NColorManagement::PImageDescription imageDescription, const NColorManagement::PImageDescription targetImageDescription, + bool modifySDR = false, float sdrMinLuminance = -1.0f, int sdrMaxLuminance = -1); + void passCMUniforms(WP, const NColorManagement::PImageDescription imageDescription); + void renderSplash(cairo_t* const, cairo_surface_t* const, double offset, const Vector2D& size); + void renderRectInternal(const CBox&, const CHyprColor&, const SRectRenderData& data); + void renderRectWithBlurInternal(const CBox&, const CHyprColor&, const SRectRenderData& data); + void renderRectWithDamageInternal(const CBox&, const CHyprColor&, const SRectRenderData& data); + WP renderToOutputInternal(); + WP renderToFBInternal(const STextureRenderData& data, eTextureType texType, const CBox& newBox); + void renderTextureInternal(SP, const CBox&, const STextureRenderData& data); + void renderTextureWithBlurInternal(SP, const CBox&, const STextureRenderData& data); - void preBlurForCurrentMonitor(); + void preBlurForCurrentMonitor(); friend class CHyprRenderer; friend class CTexPassElement; diff --git a/src/render/Renderbuffer.cpp b/src/render/Renderbuffer.cpp index d7a77b74..bab4f73e 100644 --- a/src/render/Renderbuffer.cpp +++ b/src/render/Renderbuffer.cpp @@ -1,76 +1,21 @@ #include "Renderbuffer.hpp" -#include "Renderer.hpp" -#include "OpenGL.hpp" -#include "../Compositor.hpp" -#include "../protocols/types/Buffer.hpp" +#include "Framebuffer.hpp" +#include "render/Renderer.hpp" +#include "render/gl/GLRenderbuffer.hpp" +#include #include #include #include -CRenderbuffer::~CRenderbuffer() { - if (!g_pCompositor || g_pCompositor->m_isShuttingDown || !g_pHyprRenderer) - return; - - g_pHyprRenderer->makeEGLCurrent(); - - unbind(); - m_framebuffer.release(); - glDeleteRenderbuffers(1, &m_rbo); - - g_pHyprOpenGL->m_proc.eglDestroyImageKHR(g_pHyprOpenGL->m_eglDisplay, m_image); +IRenderbuffer::IRenderbuffer(SP buffer, uint32_t format) : m_hlBuffer(buffer) { + m_listeners.destroyBuffer = buffer->events.destroy.listen([this] { g_pHyprRenderer->onRenderbufferDestroy(dc(this)); }); } -CRenderbuffer::CRenderbuffer(SP buffer, uint32_t format) : m_hlBuffer(buffer), m_drmFormat(format) { - auto dma = buffer->dmabuf(); - - m_image = g_pHyprOpenGL->createEGLImage(dma); - if (m_image == EGL_NO_IMAGE_KHR) { - Log::logger->log(Log::ERR, "rb: createEGLImage failed"); - return; - } - - glGenRenderbuffers(1, &m_rbo); - glBindRenderbuffer(GL_RENDERBUFFER, m_rbo); - g_pHyprOpenGL->m_proc.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, m_image); - glBindRenderbuffer(GL_RENDERBUFFER, 0); - - glGenFramebuffers(1, &m_framebuffer.m_fb); - m_framebuffer.m_fbAllocated = true; - m_framebuffer.m_size = buffer->size; - m_framebuffer.bind(); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_rbo); - - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { - Log::logger->log(Log::ERR, "rbo: glCheckFramebufferStatus failed"); - return; - } - - m_framebuffer.unbind(); - - m_listeners.destroyBuffer = buffer->events.destroy.listen([this] { g_pHyprRenderer->onRenderbufferDestroy(this); }); - - m_good = true; -} - -bool CRenderbuffer::good() { +bool IRenderbuffer::good() { return m_good; } -void CRenderbuffer::bind() { - glBindRenderbuffer(GL_RENDERBUFFER, m_rbo); - bindFB(); -} - -void CRenderbuffer::bindFB() { - m_framebuffer.bind(); -} - -void CRenderbuffer::unbind() { - glBindRenderbuffer(GL_RENDERBUFFER, 0); - m_framebuffer.unbind(); -} - -CFramebuffer* CRenderbuffer::getFB() { - return &m_framebuffer; +SP IRenderbuffer::getFB() { + return m_framebuffer; } diff --git a/src/render/Renderbuffer.hpp b/src/render/Renderbuffer.hpp index c0924141..c33144d3 100644 --- a/src/render/Renderbuffer.hpp +++ b/src/render/Renderbuffer.hpp @@ -5,30 +5,24 @@ #include "Framebuffer.hpp" #include -class CMonitor; - -class CRenderbuffer { +class IRenderbuffer { public: - CRenderbuffer(SP buffer, uint32_t format); - ~CRenderbuffer(); + IRenderbuffer(SP buffer, uint32_t format); + virtual ~IRenderbuffer() = default; bool good(); - void bind(); - void bindFB(); - void unbind(); - CFramebuffer* getFB(); - uint32_t getFormat(); + SP getFB(); + + virtual void bind() = 0; + virtual void unbind() = 0; WP m_hlBuffer; - private: - void* m_image = nullptr; - GLuint m_rbo = 0; - CFramebuffer m_framebuffer; - uint32_t m_drmFormat = 0; - bool m_good = false; + protected: + SP m_framebuffer; + bool m_good = false; struct { CHyprSignalListener destroyBuffer; } m_listeners; -}; \ No newline at end of file +}; diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index 1aa85f15..165f580a 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -3,15 +3,14 @@ #include "../helpers/math/Math.hpp" #include #include +#include #include #include "../config/ConfigValue.hpp" #include "../config/ConfigManager.hpp" #include "../managers/CursorManager.hpp" #include "../managers/PointerManager.hpp" #include "../managers/input/InputManager.hpp" -#include "../managers/HookSystemManager.hpp" #include "../managers/animation/AnimationManager.hpp" -#include "../managers/LayoutManager.hpp" #include "../desktop/view/Window.hpp" #include "../desktop/view/LayerSurface.hpp" #include "../desktop/view/GlobalViewMethods.hpp" @@ -28,9 +27,15 @@ #include "../hyprerror/HyprError.hpp" #include "../debug/HyprDebugOverlay.hpp" #include "../debug/HyprNotificationOverlay.hpp" +#include "../layout/LayoutManager.hpp" +#include "../layout/space/Space.hpp" #include "../i18n/Engine.hpp" +#include "desktop/DesktopTypes.hpp" +#include "../event/EventBus.hpp" #include "helpers/CursorShapes.hpp" +#include "helpers/MainLoopExecutor.hpp" #include "helpers/Monitor.hpp" +#include "macros.hpp" #include "pass/TexPassElement.hpp" #include "pass/ClearPassElement.hpp" #include "pass/RectPassElement.hpp" @@ -40,7 +45,18 @@ #include "../protocols/ColorManagement.hpp" #include "../protocols/types/ContentType.hpp" #include "../helpers/MiscFunctions.hpp" +#include "render/AsyncResourceGatherer.hpp" +#include "render/Framebuffer.hpp" #include "render/OpenGL.hpp" +#include "render/Texture.hpp" +#include "render/gl/GLFramebuffer.hpp" +#include "render/gl/GLTexture.hpp" +#include +#include +#include +#include +#include +#include #include using namespace Hyprutils::Utils; @@ -112,7 +128,7 @@ CHyprRenderer::CHyprRenderer() { // cursor hiding stuff - static auto P = g_pHookSystem->hookDynamic("keyPress", [&](void* self, SCallbackInfo& info, std::any param) { + static auto P = Event::bus()->m_events.input.keyboard.key.listen([&](IKeyboard::SKeyEvent e, Event::SCallbackInfo&) { if (m_cursorHiddenConditions.hiddenOnKeyboard) return; @@ -120,7 +136,7 @@ CHyprRenderer::CHyprRenderer() { ensureCursorRenderingMode(); }); - static auto P2 = g_pHookSystem->hookDynamic("mouseMove", [&](void* self, SCallbackInfo& info, std::any param) { + static auto P2 = Event::bus()->m_events.input.mouse.move.listen([&](Vector2D pos, Event::SCallbackInfo&) { if (!m_cursorHiddenConditions.hiddenOnKeyboard && m_cursorHiddenConditions.hiddenOnTouch == g_pInputManager->m_lastInputTouch && m_cursorHiddenConditions.hiddenOnTablet == g_pInputManager->m_lastInputTablet && !m_cursorHiddenConditions.hiddenOnTimeout) return; @@ -132,7 +148,7 @@ CHyprRenderer::CHyprRenderer() { ensureCursorRenderingMode(); }); - static auto P3 = g_pHookSystem->hookDynamic("focusedMon", [&](void* self, SCallbackInfo& info, std::any param) { + static auto P3 = Event::bus()->m_events.monitor.focused.listen([&](PHLMONITOR mon) { g_pEventLoopManager->doLater([this]() { if (!g_pHyprError->active()) return; @@ -142,11 +158,9 @@ CHyprRenderer::CHyprRenderer() { }); }); - static auto P4 = g_pHookSystem->hookDynamic("windowUpdateRules", [&](void* self, SCallbackInfo& info, std::any param) { - const auto PWINDOW = std::any_cast(param); - - if (PWINDOW->m_ruleApplicator->renderUnfocused().valueOrDefault()) - addWindowToRenderUnfocused(PWINDOW); + static auto P4 = Event::bus()->m_events.window.updateRules.listen([&](PHLWINDOW window) { + if (window->m_ruleApplicator->renderUnfocused().valueOrDefault()) + addWindowToRenderUnfocused(window); }); m_cursorTicker = wl_event_loop_add_timer(g_pCompositor->m_wlEventLoop, cursorTicker, nullptr); @@ -289,7 +303,7 @@ bool CHyprRenderer::shouldRenderWindow(PHLWINDOW pWindow) { void CHyprRenderer::renderWorkspaceWindowsFullscreen(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& time) { PHLWINDOW pWorkspaceWindow = nullptr; - EMIT_HOOK_EVENT("render", RENDER_PRE_WINDOWS); + Event::bus()->m_events.render.stage.emit(RENDER_PRE_WINDOWS); // loop over the tiled windows that are fading out for (auto const& w : g_pCompositor->m_windows) { @@ -380,7 +394,7 @@ void CHyprRenderer::renderWorkspaceWindowsFullscreen(PHLMONITOR pMonitor, PHLWOR void CHyprRenderer::renderWorkspaceWindows(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& time) { PHLWINDOW lastWindow; - EMIT_HOOK_EVENT("render", RENDER_PRE_WINDOWS); + Event::bus()->m_events.render.stage.emit(RENDER_PRE_WINDOWS); std::vector windows, tiledFadingOut; windows.reserve(g_pCompositor->m_windows.size()); @@ -544,7 +558,7 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T // for plugins g_pHyprOpenGL->m_renderData.currentWindow = pWindow; - EMIT_HOOK_EVENT("render", RENDER_PRE_WINDOW); + Event::bus()->m_events.render.stage.emit(RENDER_PRE_WINDOW); const auto fullAlpha = renderdata.alpha * renderdata.fadeAlpha; @@ -643,13 +657,13 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T } if (TRANSFORMERSPRESENT) { - CFramebuffer* last = g_pHyprOpenGL->m_renderData.currentFB; + IFramebuffer* last = g_pHyprOpenGL->m_renderData.currentFB.get(); for (auto const& t : pWindow->m_transformers) { last = t->transform(last); } g_pHyprOpenGL->bindBackOnMain(); - g_pHyprOpenGL->renderOffToMain(last); + g_pHyprOpenGL->renderOffToMain(dc(last)); } } @@ -728,11 +742,41 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T } } - EMIT_HOOK_EVENT("render", RENDER_POST_WINDOW); + Event::bus()->m_events.render.stage.emit(RENDER_POST_WINDOW); g_pHyprOpenGL->m_renderData.currentWindow.reset(); } +SP CHyprRenderer::createTexture(const SP buffer, bool keepDataCopy) { + if (!buffer) + return createTexture(); + + auto attrs = buffer->dmabuf(); + + if (!attrs.success) { + // attempt shm + auto shm = buffer->shm(); + + if (!shm.success) { + Log::logger->log(Log::ERR, "Cannot create a texture: buffer has no dmabuf or shm"); + return createTexture(buffer->opaque); + } + + auto [pixelData, fmt, bufLen] = buffer->beginDataPtr(0); + + return createTexture(fmt, pixelData, bufLen, shm.size, keepDataCopy, buffer->opaque); + } + + auto tex = createTexture(attrs, buffer->opaque); + + if (!tex) { + Log::logger->log(Log::ERR, "Cannot create a texture: failed to create an Image"); + return createTexture(buffer->opaque); + } + + return tex; +} + void CHyprRenderer::renderLayer(PHLLS pLayer, PHLMONITOR pMonitor, const Time::steady_tp& time, bool popups, bool lockscreen) { if (!pLayer) return; @@ -902,10 +946,10 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA static auto PXPMODE = CConfigValue("render:xp_mode"); static auto PSESSIONLOCKXRAY = CConfigValue("misc:session_lock_xray"); - if (!pMonitor) + if UNLIKELY (!pMonitor) return; - if (g_pSessionLockManager->isSessionLocked() && !*PSESSIONLOCKXRAY) { + if UNLIKELY (g_pSessionLockManager->isSessionLocked() && !*PSESSIONLOCKXRAY) { // We stop to render workspaces as soon as the lockscreen was sent the "locked" or "finished" (aka denied) event. // In addition we make sure to stop rendering workspaces after misc:lockdead_screen_delay has passed. if (g_pSessionLockManager->shallConsiderLockMissing() || g_pSessionLockManager->clientLocked() || g_pSessionLockManager->clientDenied()) @@ -919,10 +963,10 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA SRenderModifData RENDERMODIFDATA; if (translate != Vector2D{0, 0}) RENDERMODIFDATA.modifs.emplace_back(std::make_pair<>(SRenderModifData::eRenderModifType::RMOD_TYPE_TRANSLATE, translate)); - if (scale != 1.f) + if UNLIKELY (scale != 1.f) RENDERMODIFDATA.modifs.emplace_back(std::make_pair<>(SRenderModifData::eRenderModifType::RMOD_TYPE_SCALE, scale)); - if (!RENDERMODIFDATA.modifs.empty()) + if UNLIKELY (!RENDERMODIFDATA.modifs.empty()) g_pHyprRenderer->m_renderPass.add(makeUnique(CRendererHintsPassElement::SData{RENDERMODIFDATA})); CScopeGuard x([&RENDERMODIFDATA] { @@ -931,7 +975,7 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA } }); - if (!pWorkspace) { + if UNLIKELY (!pWorkspace) { // allow rendering without a workspace. In this case, just render layers. renderBackground(pMonitor); @@ -940,7 +984,7 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA renderLayer(ls.lock(), pMonitor, time); } - EMIT_HOOK_EVENT("render", RENDER_POST_WALLPAPER); + Event::bus()->m_events.render.stage.emit(RENDER_POST_WALLPAPER); for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]) { renderLayer(ls.lock(), pMonitor, time); @@ -957,14 +1001,14 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA return; } - if (!*PXPMODE) { + if LIKELY (!*PXPMODE) { renderBackground(pMonitor); for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]) { renderLayer(ls.lock(), pMonitor, time); } - EMIT_HOOK_EVENT("render", RENDER_POST_WALLPAPER); + Event::bus()->m_events.render.stage.emit(RENDER_POST_WALLPAPER); for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]) { renderLayer(ls.lock(), pMonitor, time); @@ -974,13 +1018,13 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA // pre window pass g_pHyprOpenGL->preWindowPass(); - if (pWorkspace->m_hasFullscreenWindow) + if UNLIKELY /* subjective? */ (pWorkspace->m_hasFullscreenWindow) renderWorkspaceWindowsFullscreen(pMonitor, pWorkspace, time); else renderWorkspaceWindows(pMonitor, pWorkspace, time); // and then special - if (pMonitor->m_specialFade->value() != 0.F) { + if UNLIKELY (pMonitor->m_specialFade->value() != 0.F) { const auto SPECIALANIMPROGRS = pMonitor->m_specialFade->getCurveValue(); const bool ANIMOUT = !pMonitor->m_activeSpecialWorkspace; @@ -1029,7 +1073,7 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA renderWindow(w, pMonitor, time, true, RENDER_PASS_ALL); } - EMIT_HOOK_EVENT("render", RENDER_POST_WINDOWS); + Event::bus()->m_events.render.stage.emit(RENDER_POST_WINDOWS); // Render surfaces above windows for monitor for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) { @@ -1260,6 +1304,79 @@ void CHyprRenderer::calculateUVForSurface(PHLWINDOW pWindow, SP surface, bool modifySDR, float sdrMinLuminance, int sdrMaxLuminance) { + const auto sdrEOTF = NTransferFunction::fromConfig(); + NColorManagement::eTransferFunction srcTF; + + auto& m_renderData = g_pHyprOpenGL->m_renderData; + if (m_renderData.surface.valid()) { + if (m_renderData.surface->m_colorManagement.valid()) { + if (sdrEOTF == NTransferFunction::TF_FORCED_GAMMA22 && imageDescription->value().transferFunction == NColorManagement::eTransferFunction::CM_TRANSFER_FUNCTION_SRGB) + srcTF = NColorManagement::eTransferFunction::CM_TRANSFER_FUNCTION_GAMMA22; + else + srcTF = imageDescription->value().transferFunction; + } else if (sdrEOTF == NTransferFunction::TF_SRGB) + srcTF = NColorManagement::eTransferFunction::CM_TRANSFER_FUNCTION_SRGB; + else if (sdrEOTF == NTransferFunction::TF_GAMMA22 || sdrEOTF == NTransferFunction::TF_FORCED_GAMMA22) + srcTF = NColorManagement::eTransferFunction::CM_TRANSFER_FUNCTION_GAMMA22; + else + srcTF = imageDescription->value().transferFunction; + } else + srcTF = imageDescription->value().transferFunction; + + const bool needsSDRmod = modifySDR && isSDR2HDR(imageDescription->value(), targetImageDescription->value()); + const bool needsHDRmod = !needsSDRmod && isHDR2SDR(imageDescription->value(), targetImageDescription->value()); + const float maxLuminance = needsHDRmod ? + imageDescription->value().getTFMaxLuminance(-1) : + (imageDescription->value().luminances.max > 0 ? imageDescription->value().luminances.max : imageDescription->value().luminances.reference); + const auto dstMaxLuminance = targetImageDescription->value().luminances.max > 0 ? targetImageDescription->value().luminances.max : 10000; + + auto matrix = imageDescription->getPrimaries()->convertMatrix(targetImageDescription->getPrimaries()); + auto toXYZ = targetImageDescription->getPrimaries()->value().toXYZ(); + + const bool needsMod = (imageDescription->value().transferFunction == CM_TRANSFER_FUNCTION_SRGB || imageDescription->value().transferFunction == CM_TRANSFER_FUNCTION_GAMMA22) && + targetImageDescription->value().transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ && + ((m_renderData.pMonitor->m_sdrSaturation > 0 && m_renderData.pMonitor->m_sdrSaturation != 1.0f) || + (m_renderData.pMonitor->m_sdrBrightness > 0 && m_renderData.pMonitor->m_sdrBrightness != 1.0f)); + + return { + .sourceTF = srcTF, + .targetTF = targetImageDescription->value().transferFunction, + .srcTFRange = {.min = imageDescription->value().getTFMinLuminance(needsSDRmod ? sdrMinLuminance : -1), + .max = imageDescription->value().getTFMaxLuminance(needsSDRmod ? sdrMaxLuminance : -1)}, + .dstTFRange = {.min = targetImageDescription->value().getTFMinLuminance(needsSDRmod ? sdrMinLuminance : -1), + .max = targetImageDescription->value().getTFMaxLuminance(needsSDRmod ? sdrMaxLuminance : -1)}, + .srcRefLuminance = imageDescription->value().luminances.reference, + .dstRefLuminance = targetImageDescription->value().luminances.reference, + .convertMatrix = matrix.mat(), + + .needsTonemap = maxLuminance >= dstMaxLuminance * 1.01, + .maxLuminance = maxLuminance * targetImageDescription->value().luminances.reference / imageDescription->value().luminances.reference, + .dstMaxLuminance = dstMaxLuminance, + .dstPrimaries2XYZ = toXYZ.mat(), + .needsSDRmod = needsMod, + .sdrSaturation = needsSDRmod && m_renderData.pMonitor->m_sdrSaturation > 0 ? m_renderData.pMonitor->m_sdrSaturation : 1.0f, + .sdrBrightnessMultiplier = needsSDRmod && m_renderData.pMonitor->m_sdrBrightness > 0 ? m_renderData.pMonitor->m_sdrBrightness : 1.0f, + }; +} + void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { static std::chrono::high_resolution_clock::time_point renderStart = std::chrono::high_resolution_clock::now(); static std::chrono::high_resolution_clock::time_point renderStartOverlay = std::chrono::high_resolution_clock::now(); @@ -1268,6 +1385,7 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { static auto PDEBUGOVERLAY = CConfigValue("debug:overlay"); static auto PDAMAGETRACKINGMODE = CConfigValue("debug:damage_tracking"); static auto PDAMAGEBLINK = CConfigValue("debug:damage_blink"); + static auto PSOLDAMAGE = CConfigValue("debug:render_solitary_wo_damage"); static auto PVFR = CConfigValue("misc:vfr"); static int damageBlinkCleanup = 0; // because double-buffered @@ -1304,7 +1422,8 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { if (pMonitor->m_scheduledRecalc) { pMonitor->m_scheduledRecalc = false; - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitor->m_id); + if (pMonitor->m_activeWorkspace) // might be missing (mirror) + pMonitor->m_activeWorkspace->m_space->recalculate(); } if (!pMonitor->m_output->needsFrame && pMonitor->m_forceFullFrames == 0) @@ -1314,10 +1433,12 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { bool shouldTear = pMonitor->updateTearing(); if (pMonitor->attemptDirectScanout()) { + pMonitor->m_directScanoutIsActive = true; return; - } else if (!pMonitor->m_lastScanout.expired()) { + } else if (!pMonitor->m_lastScanout.expired() || pMonitor->m_directScanoutIsActive) { Log::logger->log(Log::DEBUG, "Left a direct scanout."); pMonitor->m_lastScanout.reset(); + pMonitor->m_directScanoutIsActive = false; // reset DRM format, but only if needed since it might modeset if (pMonitor->m_output->state->state().drmFormat != pMonitor->m_prevDrmFormat) @@ -1326,7 +1447,7 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { pMonitor->m_drmFormat = pMonitor->m_prevDrmFormat; } - EMIT_HOOK_EVENT("preRender", pMonitor); + Event::bus()->m_events.render.pre.emit(pMonitor); const auto NOW = Time::steadyNow(); @@ -1341,7 +1462,7 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { return; } - EMIT_HOOK_EVENT("render", RENDER_PRE); + Event::bus()->m_events.render.stage.emit(RENDER_PRE); pMonitor->m_renderingActive = true; @@ -1394,50 +1515,49 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { pMonitor->m_forceFullFrames = 0; } - EMIT_HOOK_EVENT("render", RENDER_BEGIN); + Event::bus()->m_events.render.stage.emit(RENDER_BEGIN); bool renderCursor = true; - if (!finalDamage.empty()) { - if (pMonitor->m_solitaryClient.expired()) { - if (pMonitor->isMirror()) { - g_pHyprOpenGL->blend(false); - g_pHyprOpenGL->renderMirrored(); - g_pHyprOpenGL->blend(true); - EMIT_HOOK_EVENT("render", RENDER_POST_MIRROR); - renderCursor = false; - } else { - CBox renderBox = {0, 0, sc(pMonitor->m_pixelSize.x), sc(pMonitor->m_pixelSize.y)}; - renderWorkspace(pMonitor, pMonitor->m_activeWorkspace, NOW, renderBox); + if (pMonitor->m_solitaryClient && (!finalDamage.empty() || *PSOLDAMAGE)) + renderWindow(pMonitor->m_solitaryClient.lock(), pMonitor, NOW, false, RENDER_PASS_MAIN /* solitary = no popups */); + else if (!finalDamage.empty()) { + if (pMonitor->isMirror()) { + g_pHyprOpenGL->blend(false); + g_pHyprOpenGL->renderMirrored(); + g_pHyprOpenGL->blend(true); + Event::bus()->m_events.render.stage.emit(RENDER_POST_MIRROR); + renderCursor = false; + } else { + CBox renderBox = {0, 0, sc(pMonitor->m_pixelSize.x), sc(pMonitor->m_pixelSize.y)}; + renderWorkspace(pMonitor, pMonitor->m_activeWorkspace, NOW, renderBox); - renderLockscreen(pMonitor, NOW, renderBox); + renderLockscreen(pMonitor, NOW, renderBox); - if (pMonitor == Desktop::focusState()->monitor()) { - g_pHyprNotificationOverlay->draw(pMonitor); - g_pHyprError->draw(); - } - - // for drawing the debug overlay - if (pMonitor == g_pCompositor->m_monitors.front() && *PDEBUGOVERLAY == 1) { - renderStartOverlay = std::chrono::high_resolution_clock::now(); - g_pDebugOverlay->draw(); - endRenderOverlay = std::chrono::high_resolution_clock::now(); - } - - if (*PDAMAGEBLINK && damageBlinkCleanup == 0) { - CRectPassElement::SRectData data; - data.box = {0, 0, pMonitor->m_transformedSize.x, pMonitor->m_transformedSize.y}; - data.color = CHyprColor(1.0, 0.0, 1.0, 100.0 / 255.0); - m_renderPass.add(makeUnique(data)); - damageBlinkCleanup = 1; - } else if (*PDAMAGEBLINK) { - damageBlinkCleanup++; - if (damageBlinkCleanup > 3) - damageBlinkCleanup = 0; - } + if (pMonitor == Desktop::focusState()->monitor()) { + g_pHyprNotificationOverlay->draw(pMonitor); + g_pHyprError->draw(); } - } else - renderWindow(pMonitor->m_solitaryClient.lock(), pMonitor, NOW, false, RENDER_PASS_MAIN /* solitary = no popups */); + + // for drawing the debug overlay + if (pMonitor == g_pCompositor->m_monitors.front() && *PDEBUGOVERLAY == 1) { + renderStartOverlay = std::chrono::high_resolution_clock::now(); + g_pDebugOverlay->draw(); + endRenderOverlay = std::chrono::high_resolution_clock::now(); + } + + if (*PDAMAGEBLINK && damageBlinkCleanup == 0) { + CRectPassElement::SRectData data; + data.box = {0, 0, pMonitor->m_transformedSize.x, pMonitor->m_transformedSize.y}; + data.color = CHyprColor(1.0, 0.0, 1.0, 100.0 / 255.0); + m_renderPass.add(makeUnique(data)); + damageBlinkCleanup = 1; + } else if (*PDAMAGEBLINK) { + damageBlinkCleanup++; + if (damageBlinkCleanup > 3) + damageBlinkCleanup = 0; + } + } } else if (!pMonitor->isMirror()) { sendFrameEventsToWorkspace(pMonitor, pMonitor->m_activeWorkspace, NOW); if (pMonitor->m_activeSpecialWorkspace) @@ -1459,7 +1579,7 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { m_renderPass.add(makeUnique(data)); } - EMIT_HOOK_EVENT("render", RENDER_LAST_MOMENT); + Event::bus()->m_events.render.stage.emit(RENDER_LAST_MOMENT); endRender(); @@ -1481,7 +1601,7 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { pMonitor->m_renderingActive = false; - EMIT_HOOK_EVENT("render", RENDER_POST); + Event::bus()->m_events.render.stage.emit(RENDER_POST); pMonitor->m_output->state->addDamage(frameDamage); pMonitor->m_output->state->setPresentationMode(shouldTear ? Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_IMMEDIATE : @@ -1552,8 +1672,8 @@ static hdr_output_metadata createHDRMetadata(SImageDescription settings, S .white_point = {.x = to16Bit(colorimetry.white.x), .y = to16Bit(colorimetry.white.y)}, .max_display_mastering_luminance = toNits(luminances.max), .min_display_mastering_luminance = toNits(luminances.min * 10000), - .max_cll = toNits(settings.maxCLL), - .max_fall = toNits(settings.maxFALL), + .max_cll = toNits(settings.maxCLL > 0 ? settings.maxCLL : monitor->maxCLL()), + .max_fall = toNits(settings.maxFALL > 0 ? settings.maxFALL : monitor->maxFALL()), }, }; } @@ -1567,7 +1687,7 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { const bool configuredHDR = (pMonitor->m_cmType == NCMType::CM_HDR_EDID || pMonitor->m_cmType == NCMType::CM_HDR); bool wantHDR = configuredHDR; - const auto FS_WINDOW = pMonitor->inFullscreenMode() ? pMonitor->m_activeWorkspace->getFullscreenWindow() : nullptr; + const auto FS_WINDOW = pMonitor->getFullscreenWindow(); if (pMonitor->supportsHDR()) { // HDR metadata determined by @@ -1646,7 +1766,7 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { if (*PCT) pMonitor->m_output->state->setContentType(NContentType::toDRM(FS_WINDOW ? FS_WINDOW->getContentType() : CONTENT_TYPE_NONE)); - if (FS_WINDOW != pMonitor->m_previousFSWindow) { + if (FS_WINDOW != pMonitor->m_previousFSWindow || (!FS_WINDOW && pMonitor->m_noShaderCTM)) { if (*PNONSHADER == CM_NS_IGNORE || !FS_WINDOW || !pMonitor->needsCM() || !pMonitor->canNoShaderCM() || (*PNONSHADER == CM_NS_ONDEMAND && pMonitor->m_lastScanout.expired() && *PPASS != 1)) { if (pMonitor->m_noShaderCTM) { @@ -1904,7 +2024,7 @@ void CHyprRenderer::arrangeLayersForMonitor(const MONITORID& monitor) { // damage the monitor if can damageMonitor(PMONITOR); - g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitor); + g_layoutManager->invalidateMonitorGeometries(PMONITOR); } void CHyprRenderer::damageSurface(SP pSurface, double x, double y, double scale) { @@ -1914,22 +2034,33 @@ void CHyprRenderer::damageSurface(SP pSurface, double x, dou if (g_pCompositor->m_unsafeState) return; - const auto WLSURF = Desktop::View::CWLSurface::fromResource(pSurface); - CRegion damageBox = WLSURF ? WLSURF->computeDamage() : CRegion{}; + const auto WLSURF = Desktop::View::CWLSurface::fromResource(pSurface); if (!WLSURF) { Log::logger->log(Log::ERR, "BUG THIS: No CWLSurface for surface in damageSurface!!!"); return; } - if (scale != 1.0) - damageBox.scale(scale); + // hack: schedule frame events + if (!WLSURF->resource()->m_current.callbacks.empty() && pSurface->m_hlSurface) { + const auto BOX = pSurface->m_hlSurface->getSurfaceBoxGlobal(); + if (BOX && !BOX->empty()) { + for (auto const& m : g_pCompositor->m_monitors) { + if (!m->m_output) + continue; - // schedule frame events - g_pCompositor->scheduleFrameForMonitor(g_pCompositor->getMonitorFromVector(Vector2D(x, y)), Aquamarine::IOutput::AQ_SCHEDULE_DAMAGE); + if (BOX->overlaps(m->logicalBox())) + g_pCompositor->scheduleFrameForMonitor(m, Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME); + } + } + } + CRegion damageBox = WLSURF->computeDamage(); if (damageBox.empty()) return; + if (scale != 1.0) + damageBox.scale(scale); + damageBox.translate({x, y}); CRegion damageBoxForEach; @@ -2049,7 +2180,7 @@ void CHyprRenderer::renderDragIcon(PHLMONITOR pMonitor, const Time::steady_tp& t } void CHyprRenderer::setCursorSurface(SP surf, int hotspotX, int hotspotY, bool force) { - m_cursorHasSurface = surf; + m_cursorHasSurface = surf && surf->resource(); m_lastCursorData.name = ""; m_lastCursorData.surf = surf; @@ -2140,30 +2271,19 @@ void CHyprRenderer::ensureCursorRenderingMode() { if (HIDE == m_cursorHidden) return; - if (HIDE) { + if (HIDE) Log::logger->log(Log::DEBUG, "Hiding the cursor (hl-mandated)"); - - for (auto const& m : g_pCompositor->m_monitors) { - if (!g_pPointerManager->softwareLockedFor(m)) - continue; - - damageMonitor(m); // TODO: maybe just damage the cursor area? - } - - setCursorHidden(true); - - } else { + else Log::logger->log(Log::DEBUG, "Showing the cursor (hl-mandated)"); - for (auto const& m : g_pCompositor->m_monitors) { - if (!g_pPointerManager->softwareLockedFor(m)) - continue; + for (auto const& m : g_pCompositor->m_monitors) { + if (!g_pPointerManager->softwareLockedFor(m)) + continue; - damageMonitor(m); // TODO: maybe just damage the cursor area? - } - - setCursorHidden(false); + g_pPointerManager->damageCursor(m, m->shouldSkipScheduleFrameOnMouseEvent()); } + + setCursorHidden(HIDE); } void CHyprRenderer::setCursorHidden(bool hide) { @@ -2197,10 +2317,8 @@ std::tuple CHyprRenderer::getRenderTimes(PHLMONITOR pMonito float maxRenderTime = 0; float minRenderTime = 9999; for (auto const& rt : POVERLAY->m_lastRenderTimes) { - if (rt > maxRenderTime) - maxRenderTime = rt; - if (rt < minRenderTime) - minRenderTime = rt; + maxRenderTime = std::max(rt, maxRenderTime); + minRenderTime = std::min(rt, minRenderTime); avgRenderTime += rt; } avgRenderTime /= POVERLAY->m_lastRenderTimes.empty() ? 1 : POVERLAY->m_lastRenderTimes.size(); @@ -2239,13 +2357,13 @@ void CHyprRenderer::initiateManualCrash() { **PDT = 0; } -SP CHyprRenderer::getOrCreateRenderbuffer(SP buffer, uint32_t fmt) { +SP CHyprRenderer::getOrCreateRenderbuffer(SP buffer, uint32_t fmt) { auto it = std::ranges::find_if(m_renderbuffers, [&](const auto& other) { return other->m_hlBuffer == buffer; }); if (it != m_renderbuffers.end()) return *it; - auto buf = makeShared(buffer, fmt); + auto buf = makeShared(buffer, fmt); if (!buf->good()) return nullptr; @@ -2269,7 +2387,7 @@ void CHyprRenderer::unsetEGL() { eglMakeCurrent(g_pHyprOpenGL->m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } -bool CHyprRenderer::beginRender(PHLMONITOR pMonitor, CRegion& damage, eRenderMode mode, SP buffer, CFramebuffer* fb, bool simple) { +bool CHyprRenderer::beginRender(PHLMONITOR pMonitor, CRegion& damage, eRenderMode mode, SP buffer, SP fb, bool simple) { makeEGLCurrent(); @@ -2370,7 +2488,7 @@ void CHyprRenderer::endRender(const std::function& renderingDoneCallback } UP eglSync = CEGLSync::create(); - if (eglSync && eglSync->isValid()) { + if LIKELY (eglSync && eglSync->isValid()) { for (auto const& buf : m_usedAsyncBuffers) { for (const auto& releaser : buf->m_syncReleasers) { releaser->addSyncFileFd(eglSync->fd()); @@ -2401,11 +2519,11 @@ void CHyprRenderer::endRender(const std::function& renderingDoneCallback } } -void CHyprRenderer::onRenderbufferDestroy(CRenderbuffer* rb) { +void CHyprRenderer::onRenderbufferDestroy(CGLRenderbuffer* rb) { std::erase_if(m_renderbuffers, [&](const auto& rbo) { return rbo.get() == rb; }); } -SP CHyprRenderer::getCurrentRBO() { +SP CHyprRenderer::getCurrentRBO() { return m_currentRenderbuffer; } @@ -2458,7 +2576,10 @@ void CHyprRenderer::makeSnapshot(PHLWINDOW pWindow) { makeEGLCurrent(); - const auto PFRAMEBUFFER = &g_pHyprOpenGL->m_windowFramebuffers[ref]; + if (!g_pHyprOpenGL->m_windowFramebuffers.contains(ref)) + g_pHyprOpenGL->m_windowFramebuffers[ref] = g_pHyprRenderer->createFB(); + + const auto PFRAMEBUFFER = g_pHyprOpenGL->m_windowFramebuffers[ref]; PFRAMEBUFFER->alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, DRM_FORMAT_ABGR8888); @@ -2491,7 +2612,10 @@ void CHyprRenderer::makeSnapshot(PHLLS pLayer) { makeEGLCurrent(); - const auto PFRAMEBUFFER = &g_pHyprOpenGL->m_layerFramebuffers[pLayer]; + if (!g_pHyprOpenGL->m_layerFramebuffers.contains(pLayer)) + g_pHyprOpenGL->m_layerFramebuffers[pLayer] = g_pHyprRenderer->createFB(); + + const auto PFRAMEBUFFER = g_pHyprOpenGL->m_layerFramebuffers[pLayer]; PFRAMEBUFFER->alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, DRM_FORMAT_ABGR8888); @@ -2525,7 +2649,10 @@ void CHyprRenderer::makeSnapshot(WP popup) { makeEGLCurrent(); - const auto PFRAMEBUFFER = &g_pHyprOpenGL->m_popupFramebuffers[popup]; + if (!g_pHyprOpenGL->m_popupFramebuffers.contains(popup)) + g_pHyprOpenGL->m_popupFramebuffers[popup] = g_pHyprRenderer->createFB(); + + const auto PFRAMEBUFFER = g_pHyprOpenGL->m_popupFramebuffers[popup]; PFRAMEBUFFER->alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, DRM_FORMAT_ABGR8888); @@ -2574,7 +2701,7 @@ void CHyprRenderer::renderSnapshot(PHLWINDOW pWindow) { if (!g_pHyprOpenGL->m_windowFramebuffers.contains(ref)) return; - const auto FBDATA = &g_pHyprOpenGL->m_windowFramebuffers.at(ref); + const auto FBDATA = g_pHyprOpenGL->m_windowFramebuffers.at(ref); if (!FBDATA->getTexture()) return; @@ -2630,7 +2757,7 @@ void CHyprRenderer::renderSnapshot(PHLLS pLayer) { if (!g_pHyprOpenGL->m_layerFramebuffers.contains(pLayer)) return; - const auto FBDATA = &g_pHyprOpenGL->m_layerFramebuffers.at(pLayer); + const auto FBDATA = g_pHyprOpenGL->m_layerFramebuffers.at(pLayer); if (!FBDATA->getTexture()) return; @@ -2674,7 +2801,7 @@ void CHyprRenderer::renderSnapshot(WP popup) { static CConfigValue PBLURIGNOREA = CConfigValue("decoration:blur:popups_ignorealpha"); - const auto FBDATA = &g_pHyprOpenGL->m_popupFramebuffers.at(popup); + const auto FBDATA = g_pHyprOpenGL->m_popupFramebuffers.at(popup); if (!FBDATA->getTexture()) return; @@ -2703,6 +2830,16 @@ void CHyprRenderer::renderSnapshot(WP popup) { m_renderPass.add(makeUnique(std::move(data))); } +NColorManagement::PImageDescription CHyprRenderer::workBufferImageDescription() { + const auto& m_renderData = g_pHyprOpenGL->m_renderData; + // TODO + // const bool IS_MONITOR_ICC = m_renderData.pMonitor->m_imageDescription.valid() && m_renderData.pMonitor->m_imageDescription->value().icc.present; + // const auto sdrEOTF = NTransferFunction::fromConfig(IS_MONITOR_ICC); + // const auto CHOSEN_SDR_EOTF = sdrEOTF != NTransferFunction::TF_SRGB ? NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22 : NColorManagement::CM_TRANSFER_FUNCTION_SRGB; + + return m_renderData.pMonitor->m_imageDescription; //CImageDescription::from(NColorManagement::SImageDescription{.transferFunction = CHOSEN_SDR_EOTF}); +} + bool CHyprRenderer::shouldBlur(PHLLS ls) { if (m_bRenderingSnapshot) return false; @@ -2726,3 +2863,92 @@ bool CHyprRenderer::shouldBlur(WP p) { return *PBLURPOPUPS && *PBLUR; } + +bool CHyprRenderer::reloadShaders(const std::string& path) { + return g_pHyprOpenGL->initShaders(path); +} + +SP CHyprRenderer::createStencilTexture(const int width, const int height) { + makeEGLCurrent(); + auto tex = makeShared(); + tex->allocate({width, height}); + + return tex; +} + +SP CHyprRenderer::createTexture(bool opaque) { + makeEGLCurrent(); + return makeShared(opaque); +} + +SP CHyprRenderer::createTexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size, bool keepDataCopy, bool opaque) { + makeEGLCurrent(); + return makeShared(drmFormat, pixels, stride, size, keepDataCopy, opaque); +} + +SP CHyprRenderer::createTexture(const Aquamarine::SDMABUFAttrs& attrs, bool opaque) { + makeEGLCurrent(); + const auto image = g_pHyprOpenGL->createEGLImage(attrs); + if (!image) + return nullptr; + return makeShared(attrs, image, opaque); +} + +SP CHyprRenderer::createTexture(const int width, const int height, unsigned char* const data) { + makeEGLCurrent(); + SP tex = makeShared(); + + tex->allocate({width, height}); + + tex->m_size = {width, height}; + // copy the data to an OpenGL texture we have + const GLint glFormat = GL_RGBA; + const GLint glType = GL_UNSIGNED_BYTE; + + tex->bind(); + tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR); + tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); + tex->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE); + tex->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED); + + glTexImage2D(GL_TEXTURE_2D, 0, glFormat, tex->m_size.x, tex->m_size.y, 0, glFormat, glType, data); + tex->unbind(); + + return tex; +} + +SP CHyprRenderer::createTexture(cairo_surface_t* cairo) { + makeEGLCurrent(); + const auto CAIROFORMAT = cairo_image_surface_get_format(cairo); + auto tex = makeShared(); + + tex->allocate({cairo_image_surface_get_width(cairo), cairo_image_surface_get_height(cairo)}); + + const GLint glIFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB32F : GL_RGBA; + const GLint glFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB : GL_RGBA; + const GLint glType = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_FLOAT : GL_UNSIGNED_BYTE; + + const auto DATA = cairo_image_surface_get_data(cairo); + tex->bind(); + tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR); + tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + if (CAIROFORMAT != CAIRO_FORMAT_RGB96F) { + tex->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE); + tex->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED); + } + + glTexImage2D(GL_TEXTURE_2D, 0, glIFormat, tex->m_size.x, tex->m_size.y, 0, glFormat, glType, DATA); + + return tex; +} + +SP CHyprRenderer::createTexture(std::span lut3D, size_t N) { + makeEGLCurrent(); + return makeShared(lut3D, N); +} + +SP CHyprRenderer::createFB(const std::string& name) { + makeEGLCurrent(); + return makeShared(name); +} \ No newline at end of file diff --git a/src/render/Renderer.hpp b/src/render/Renderer.hpp index f2377b3b..bd14c219 100644 --- a/src/render/Renderer.hpp +++ b/src/render/Renderer.hpp @@ -1,7 +1,10 @@ #pragma once #include "../defines.hpp" +#include +#include #include +#include #include "../helpers/Monitor.hpp" #include "../desktop/view/LayerSurface.hpp" #include "OpenGL.hpp" @@ -10,6 +13,8 @@ #include "../helpers/math/Math.hpp" #include "../helpers/time/Time.hpp" #include "../../protocols/cursor-shape-v1.hpp" +#include "render/Framebuffer.hpp" +#include "render/Texture.hpp" struct SMonitorRule; class CWorkspace; @@ -17,6 +22,20 @@ class CInputPopup; class IHLBuffer; class CEventLoopTimer; +const std::vector ASSET_PATHS = { +#ifdef DATAROOTDIR + DATAROOTDIR, +#endif + "/usr/share", + "/usr/local/share", +}; +class CToplevelExportProtocolManager; +class CInputManager; +struct SSessionLockSurface; +namespace Screenshare { + class CScreenshareFrame; +}; + enum eDamageTrackingModes : int8_t { DAMAGE_TRACKING_INVALID = -1, DAMAGE_TRACKING_NONE = 0, @@ -37,15 +56,34 @@ enum eRenderMode : uint8_t { RENDER_MODE_TO_BUFFER_READ_ONLY = 3, }; -class CToplevelExportProtocolManager; -class CInputManager; -struct SSessionLockSurface; - struct SRenderWorkspaceUntilData { PHLLS ls; PHLWINDOW w; }; +struct STFRange { + float min = 0; + float max = 80; +}; + +struct SCMSettings { + NColorManagement::eTransferFunction sourceTF = NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22; + NColorManagement::eTransferFunction targetTF = NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22; + STFRange srcTFRange; + STFRange dstTFRange; + float srcRefLuminance = 80; + float dstRefLuminance = 80; + std::array, 3> convertMatrix; + + bool needsTonemap = false; + float maxLuminance = 80; + float dstMaxLuminance = 80; + std::array, 3> dstPrimaries2XYZ; + bool needsSDRmod = false; + float sdrSaturation = 1.0; + float sdrBrightnessMultiplier = 1.0; +}; + class CHyprRenderer { public: CHyprRenderer(); @@ -71,8 +109,8 @@ class CHyprRenderer { void renderLockscreen(PHLMONITOR pMonitor, const Time::steady_tp& now, const CBox& geometry); void setCursorSurface(SP surf, int hotspotX, int hotspotY, bool force = false); void setCursorFromName(const std::string& name, bool force = false); - void onRenderbufferDestroy(CRenderbuffer* rb); - SP getCurrentRBO(); + void onRenderbufferDestroy(CGLRenderbuffer* rb); + SP getCurrentRBO(); bool isNvidia(); bool isIntel(); bool isSoftware(); @@ -87,9 +125,12 @@ class CHyprRenderer { void renderSnapshot(PHLLS); void renderSnapshot(WP); + // + NColorManagement::PImageDescription workBufferImageDescription(); + // if RENDER_MODE_NORMAL, provided damage will be written to. // otherwise, it will be the one used. - bool beginRender(PHLMONITOR pMonitor, CRegion& damage, eRenderMode mode = RENDER_MODE_NORMAL, SP buffer = {}, CFramebuffer* fb = nullptr, bool simple = false); + bool beginRender(PHLMONITOR pMonitor, CRegion& damage, eRenderMode mode = RENDER_MODE_NORMAL, SP buffer = {}, SP fb = nullptr, bool simple = false); void endRender(const std::function& renderingDoneCallback = {}); bool m_bBlockSurfaceFeedback = false; @@ -117,7 +158,21 @@ class CHyprRenderer { std::string name; } m_lastCursorData; - CRenderPass m_renderPass = {}; + CRenderPass m_renderPass = {}; + + SP createStencilTexture(const int width, const int height); + SP createTexture(bool opaque = false); + SP createTexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size, bool keepDataCopy = false, bool opaque = false); + SP createTexture(const Aquamarine::SDMABUFAttrs&, bool opaque = false); + SP createTexture(const int width, const int height, unsigned char* const); + SP createTexture(cairo_surface_t* cairo); + SP createTexture(const SP buffer, bool keepDataCopy = false); + SP createTexture(std::span lut3D, size_t N); + SP createFB(const std::string& name = ""); + + SCMSettings getCMSettings(const NColorManagement::PImageDescription imageDescription, const NColorManagement::PImageDescription targetImageDescription, + SP surface = nullptr, bool modifySDR = false, float sdrMinLuminance = -1.0f, int sdrMaxLuminance = -1); + bool reloadShaders(const std::string& path = ""); private: void arrangeLayerArray(PHLMONITOR, const std::vector&, bool, CBox*); @@ -144,7 +199,7 @@ class CHyprRenderer { bool m_cursorHidden = false; bool m_cursorHiddenByCondition = false; bool m_cursorHasSurface = false; - SP m_currentRenderbuffer = nullptr; + SP m_currentRenderbuffer = nullptr; SP m_currentBuffer = nullptr; eRenderMode m_renderMode = RENDER_MODE_NORMAL; bool m_nvidia = false; @@ -159,13 +214,14 @@ class CHyprRenderer { bool hiddenOnKeyboard = false; } m_cursorHiddenConditions; - SP getOrCreateRenderbuffer(SP buffer, uint32_t fmt); - std::vector> m_renderbuffers; + SP getOrCreateRenderbuffer(SP buffer, uint32_t fmt); + std::vector> m_renderbuffers; std::vector m_renderUnfocused; SP m_renderUnfocusedTimer; friend class CHyprOpenGLImpl; friend class CToplevelExportFrame; + friend class Screenshare::CScreenshareFrame; friend class CInputManager; friend class CPointerManager; friend class CMonitor; diff --git a/src/render/Shader.cpp b/src/render/Shader.cpp index 5081d4c4..ead841a5 100644 --- a/src/render/Shader.cpp +++ b/src/render/Shader.cpp @@ -1,4 +1,5 @@ #include "Shader.hpp" +#include "../config/ConfigManager.hpp" #include "render/OpenGL.hpp" #define EPSILON(x, y) (std::abs((x) - (y)) < 1e-5f) @@ -14,51 +15,235 @@ static bool compareFloat(auto a, auto b) { return true; } -SShader::SShader() { - uniformLocations.fill(-1); +CShader::CShader() { + m_uniformLocations.fill(-1); } -SShader::~SShader() { +CShader::~CShader() { destroy(); } -void SShader::createVao() { - GLuint shaderVao = 0, shaderVbo = 0, shaderVboUv = 0; +void CShader::logShaderError(const GLuint& shader, bool program, bool silent) { + GLint maxLength = 0; + if (program) + glGetProgramiv(shader, GL_INFO_LOG_LENGTH, &maxLength); + else + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength); + + std::vector errorLog(maxLength); + if (program) + glGetProgramInfoLog(shader, maxLength, &maxLength, errorLog.data()); + else + glGetShaderInfoLog(shader, maxLength, &maxLength, errorLog.data()); + std::string errorStr(errorLog.begin(), errorLog.end()); + + const auto FULLERROR = (program ? "Screen shader parser: Error linking program:" : "Screen shader parser: Error compiling shader: ") + errorStr; + + Log::logger->log(Log::ERR, "Failed to link shader: {}", FULLERROR); + + if (!silent) + g_pConfigManager->addParseError(FULLERROR); +} + +GLuint CShader::compileShader(const GLuint& type, std::string src, bool dynamic, bool silent) { + auto shader = glCreateShader(type); + + auto shaderSource = src.c_str(); + + glShaderSource(shader, 1, &shaderSource, nullptr); + glCompileShader(shader); + + GLint ok; + glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); + + if (dynamic) { + if (ok == GL_FALSE) { + logShaderError(shader, false, silent); + return 0; + } + } else { + if (ok != GL_TRUE) + logShaderError(shader, false); + RASSERT(ok != GL_FALSE, "compileShader() failed! GL_COMPILE_STATUS not OK!"); + } + + return shader; +} + +bool CShader::createProgram(const std::string& vert, const std::string& frag, bool dynamic, bool silent) { + auto vertCompiled = compileShader(GL_VERTEX_SHADER, vert, dynamic, silent); + if (dynamic) { + if (vertCompiled == 0) + return false; + } else + RASSERT(vertCompiled, "Compiling shader failed. VERTEX nullptr! Shader source:\n\n{}", vert); + + auto fragCompiled = compileShader(GL_FRAGMENT_SHADER, frag, dynamic, silent); + if (dynamic) { + if (fragCompiled == 0) + return false; + } else + RASSERT(fragCompiled, "Compiling shader failed. FRAGMENT nullptr! Shader source:\n\n{}", frag); + + auto prog = glCreateProgram(); + glAttachShader(prog, vertCompiled); + glAttachShader(prog, fragCompiled); + glLinkProgram(prog); + + glDetachShader(prog, vertCompiled); + glDetachShader(prog, fragCompiled); + glDeleteShader(vertCompiled); + glDeleteShader(fragCompiled); + + GLint ok; + glGetProgramiv(prog, GL_LINK_STATUS, &ok); + if (dynamic) { + if (ok == GL_FALSE) { + logShaderError(prog, true, silent); + return false; + } + } else { + if (ok != GL_TRUE) + logShaderError(prog, true); + RASSERT(ok != GL_FALSE, "createProgram() failed! GL_LINK_STATUS not OK!"); + } + + m_program = prog; + + getUniformLocations(); + createVao(); + return true; +} + +// its fine to call glGet on shaders that dont have the uniform +// this however hardcodes the name now. #TODO maybe dont +void CShader::getUniformLocations() { + auto getUniform = [this](const GLchar* name) { return glGetUniformLocation(m_program, name); }; + auto getAttrib = [this](const GLchar* name) { return glGetAttribLocation(m_program, name); }; + + m_uniformLocations[SHADER_PROJ] = getUniform("proj"); + m_uniformLocations[SHADER_COLOR] = getUniform("color"); + m_uniformLocations[SHADER_ALPHA_MATTE] = getUniform("texMatte"); + m_uniformLocations[SHADER_TEX_TYPE] = getUniform("texType"); + + // shader has #include "CM.glsl" + m_uniformLocations[SHADER_SOURCE_TF] = getUniform("sourceTF"); + m_uniformLocations[SHADER_TARGET_TF] = getUniform("targetTF"); + m_uniformLocations[SHADER_SRC_TF_RANGE] = getUniform("srcTFRange"); + m_uniformLocations[SHADER_DST_TF_RANGE] = getUniform("dstTFRange"); + m_uniformLocations[SHADER_TARGET_PRIMARIES_XYZ] = getUniform("targetPrimariesXYZ"); + m_uniformLocations[SHADER_MAX_LUMINANCE] = getUniform("maxLuminance"); + m_uniformLocations[SHADER_SRC_REF_LUMINANCE] = getUniform("srcRefLuminance"); + m_uniformLocations[SHADER_DST_MAX_LUMINANCE] = getUniform("dstMaxLuminance"); + m_uniformLocations[SHADER_DST_REF_LUMINANCE] = getUniform("dstRefLuminance"); + m_uniformLocations[SHADER_SDR_SATURATION] = getUniform("sdrSaturation"); + m_uniformLocations[SHADER_SDR_BRIGHTNESS] = getUniform("sdrBrightnessMultiplier"); + m_uniformLocations[SHADER_CONVERT_MATRIX] = getUniform("convertMatrix"); + m_uniformLocations[SHADER_LUT_3D] = getUniform("iccLut3D"); + m_uniformLocations[SHADER_LUT_SIZE] = getUniform("iccLutSize"); + // + m_uniformLocations[SHADER_TEX] = getUniform("tex"); + m_uniformLocations[SHADER_BLURRED_BG] = getUniform("blurredBG"); + m_uniformLocations[SHADER_UV_SIZE] = getUniform("uvSize"); + m_uniformLocations[SHADER_UV_OFFSET] = getUniform("uvOffset"); + m_uniformLocations[SHADER_ALPHA] = getUniform("alpha"); + m_uniformLocations[SHADER_POS_ATTRIB] = getAttrib("pos"); + m_uniformLocations[SHADER_TEX_ATTRIB] = getAttrib("texcoord"); + m_uniformLocations[SHADER_MATTE_TEX_ATTRIB] = getAttrib("texcoordMatte"); + m_uniformLocations[SHADER_DISCARD_OPAQUE] = getUniform("discardOpaque"); + m_uniformLocations[SHADER_DISCARD_ALPHA] = getUniform("discardAlpha"); + m_uniformLocations[SHADER_DISCARD_ALPHA_VALUE] = getUniform("discardAlphaValue"); + /* set in createVao + m_uniformLocations[SHADER_SHADER_VAO] + m_uniformLocations[SHADER_SHADER_VBO_POS] + m_uniformLocations[SHADER_SHADER_VBO_UV] + */ + m_uniformLocations[SHADER_TOP_LEFT] = getUniform("topLeft"); + m_uniformLocations[SHADER_BOTTOM_RIGHT] = getUniform("bottomRight"); + + // compat for screenshaders + auto fullSize = getUniform("fullSize"); + if (fullSize == -1) + fullSize = getUniform("screen_size"); + if (fullSize == -1) + fullSize = getUniform("screenSize"); + m_uniformLocations[SHADER_FULL_SIZE] = fullSize; + + m_uniformLocations[SHADER_FULL_SIZE_UNTRANSFORMED] = getUniform("fullSizeUntransformed"); + m_uniformLocations[SHADER_RADIUS] = getUniform("radius"); + m_uniformLocations[SHADER_RADIUS_OUTER] = getUniform("radiusOuter"); + m_uniformLocations[SHADER_ROUNDING_POWER] = getUniform("roundingPower"); + m_uniformLocations[SHADER_THICK] = getUniform("thick"); + m_uniformLocations[SHADER_HALFPIXEL] = getUniform("halfpixel"); + m_uniformLocations[SHADER_RANGE] = getUniform("range"); + m_uniformLocations[SHADER_SHADOW_POWER] = getUniform("shadowPower"); + m_uniformLocations[SHADER_USE_ALPHA_MATTE] = getUniform("useAlphaMatte"); + m_uniformLocations[SHADER_APPLY_TINT] = getUniform("applyTint"); + m_uniformLocations[SHADER_TINT] = getUniform("tint"); + m_uniformLocations[SHADER_GRADIENT] = getUniform("gradient"); + m_uniformLocations[SHADER_GRADIENT_LENGTH] = getUniform("gradientLength"); + m_uniformLocations[SHADER_GRADIENT2] = getUniform("gradient2"); + m_uniformLocations[SHADER_GRADIENT2_LENGTH] = getUniform("gradient2Length"); + m_uniformLocations[SHADER_ANGLE] = getUniform("angle"); + m_uniformLocations[SHADER_ANGLE2] = getUniform("angle2"); + m_uniformLocations[SHADER_GRADIENT_LERP] = getUniform("gradientLerp"); + m_uniformLocations[SHADER_TIME] = getUniform("time"); + m_uniformLocations[SHADER_DISTORT] = getUniform("distort"); + m_uniformLocations[SHADER_WL_OUTPUT] = getUniform("wl_output"); + m_uniformLocations[SHADER_CONTRAST] = getUniform("contrast"); + m_uniformLocations[SHADER_PASSES] = getUniform("passes"); + m_uniformLocations[SHADER_VIBRANCY] = getUniform("vibrancy"); + m_uniformLocations[SHADER_VIBRANCY_DARKNESS] = getUniform("vibrancy_darkness"); + m_uniformLocations[SHADER_BRIGHTNESS] = getUniform("brightness"); + m_uniformLocations[SHADER_NOISE] = getUniform("noise"); + m_uniformLocations[SHADER_POINTER] = getUniform("pointer_position"); + m_uniformLocations[SHADER_POINTER_SHAPE] = getUniform("pointer_shape"); + m_uniformLocations[SHADER_POINTER_SWITCH_TIME] = getUniform("pointer_switch_time"); + m_uniformLocations[SHADER_POINTER_SHAPE_PREVIOUS] = getUniform("pointer_shape_previous"); + m_uniformLocations[SHADER_POINTER_PRESSED_POSITIONS] = getUniform("pointer_pressed_positions"); + m_uniformLocations[SHADER_POINTER_HIDDEN] = getUniform("pointer_hidden"); + m_uniformLocations[SHADER_POINTER_KILLING] = getUniform("pointer_killing"); + m_uniformLocations[SHADER_POINTER_PRESSED_TIMES] = getUniform("pointer_pressed_times"); + m_uniformLocations[SHADER_POINTER_PRESSED_KILLED] = getUniform("pointer_pressed_killed"); + m_uniformLocations[SHADER_POINTER_PRESSED_TOUCHED] = getUniform("pointer_pressed_touched"); + m_uniformLocations[SHADER_POINTER_INACTIVE_TIMEOUT] = getUniform("pointer_inactive_timeout"); + m_uniformLocations[SHADER_POINTER_LAST_ACTIVE] = getUniform("pointer_last_active"); + m_uniformLocations[SHADER_POINTER_SIZE] = getUniform("pointer_size"); +} + +void CShader::createVao() { + GLuint shaderVao = 0, shaderVbo = 0; glGenVertexArrays(1, &shaderVao); glBindVertexArray(shaderVao); - if (uniformLocations[SHADER_POS_ATTRIB] != -1) { + if (m_uniformLocations[SHADER_POS_ATTRIB] != -1) { glGenBuffers(1, &shaderVbo); glBindBuffer(GL_ARRAY_BUFFER, shaderVbo); - glBufferData(GL_ARRAY_BUFFER, sizeof(fullVerts), fullVerts, GL_STATIC_DRAW); - glEnableVertexAttribArray(uniformLocations[SHADER_POS_ATTRIB]); - glVertexAttribPointer(uniformLocations[SHADER_POS_ATTRIB], 2, GL_FLOAT, GL_FALSE, 0, nullptr); + glBufferData(GL_ARRAY_BUFFER, sizeof(fullVerts), fullVerts.data(), GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(m_uniformLocations[SHADER_POS_ATTRIB]); + glVertexAttribPointer(m_uniformLocations[SHADER_POS_ATTRIB], 2, GL_FLOAT, GL_FALSE, sizeof(SVertex), (void*)offsetof(SVertex, x)); } // UV VBO (dynamic, may be updated per frame) - if (uniformLocations[SHADER_TEX_ATTRIB] != -1) { - glGenBuffers(1, &shaderVboUv); - glBindBuffer(GL_ARRAY_BUFFER, shaderVboUv); - glBufferData(GL_ARRAY_BUFFER, sizeof(fullVerts), fullVerts, GL_DYNAMIC_DRAW); // Initial dummy UVs - glEnableVertexAttribArray(uniformLocations[SHADER_TEX_ATTRIB]); - glVertexAttribPointer(uniformLocations[SHADER_TEX_ATTRIB], 2, GL_FLOAT, GL_FALSE, 0, nullptr); + if (m_uniformLocations[SHADER_TEX_ATTRIB] != -1 && shaderVbo != 0) { + glBindBuffer(GL_ARRAY_BUFFER, shaderVbo); + glEnableVertexAttribArray(m_uniformLocations[SHADER_TEX_ATTRIB]); + glVertexAttribPointer(m_uniformLocations[SHADER_TEX_ATTRIB], 2, GL_FLOAT, GL_FALSE, sizeof(SVertex), (void*)offsetof(SVertex, u)); } glBindVertexArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); - uniformLocations[SHADER_SHADER_VAO] = shaderVao; - uniformLocations[SHADER_SHADER_VBO_POS] = shaderVbo; - uniformLocations[SHADER_SHADER_VBO_UV] = shaderVboUv; + m_uniformLocations[SHADER_SHADER_VAO] = shaderVao; + m_uniformLocations[SHADER_SHADER_VBO] = shaderVbo; - RASSERT(uniformLocations[SHADER_SHADER_VAO] >= 0, "SHADER_SHADER_VAO could not be created"); - RASSERT(uniformLocations[SHADER_SHADER_VBO_POS] >= 0, "SHADER_SHADER_VBO_POS could not be created"); - RASSERT(uniformLocations[SHADER_SHADER_VBO_UV] >= 0, "SHADER_SHADER_VBO_UV could not be created"); + RASSERT(m_uniformLocations[SHADER_SHADER_VAO] >= 0, "SHADER_SHADER_VAO could not be created"); + RASSERT(m_uniformLocations[SHADER_SHADER_VBO] >= 0, "SHADER_SHADER_VBO_POS could not be created"); } -void SShader::setUniformInt(eShaderUniform location, GLint v0) { - if (uniformLocations.at(location) == -1) +void CShader::setUniformInt(eShaderUniform location, GLint v0) { + if (m_uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -67,11 +252,12 @@ void SShader::setUniformInt(eShaderUniform location, GLint v0) { return; cached = v0; - glUniform1i(uniformLocations[location], v0); + + GLCALL(glUniform1i(m_uniformLocations[location], v0)); } -void SShader::setUniformFloat(eShaderUniform location, GLfloat v0) { - if (uniformLocations.at(location) == -1) +void CShader::setUniformFloat(eShaderUniform location, GLfloat v0) { + if (m_uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -83,11 +269,11 @@ void SShader::setUniformFloat(eShaderUniform location, GLfloat v0) { } cached = v0; - glUniform1f(uniformLocations[location], v0); + GLCALL(glUniform1f(m_uniformLocations[location], v0)); } -void SShader::setUniformFloat2(eShaderUniform location, GLfloat v0, GLfloat v1) { - if (uniformLocations.at(location) == -1) +void CShader::setUniformFloat2(eShaderUniform location, GLfloat v0, GLfloat v1) { + if (m_uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -99,11 +285,11 @@ void SShader::setUniformFloat2(eShaderUniform location, GLfloat v0, GLfloat v1) } cached = std::array{v0, v1}; - glUniform2f(uniformLocations[location], v0, v1); + GLCALL(glUniform2f(m_uniformLocations[location], v0, v1)); } -void SShader::setUniformFloat3(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2) { - if (uniformLocations.at(location) == -1) +void CShader::setUniformFloat3(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2) { + if (m_uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -115,11 +301,11 @@ void SShader::setUniformFloat3(eShaderUniform location, GLfloat v0, GLfloat v1, } cached = std::array{v0, v1, v2}; - glUniform3f(uniformLocations[location], v0, v1, v2); + GLCALL(glUniform3f(m_uniformLocations[location], v0, v1, v2)); } -void SShader::setUniformFloat4(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) { - if (uniformLocations.at(location) == -1) +void CShader::setUniformFloat4(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) { + if (m_uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -131,11 +317,11 @@ void SShader::setUniformFloat4(eShaderUniform location, GLfloat v0, GLfloat v1, } cached = std::array{v0, v1, v2, v3}; - glUniform4f(uniformLocations[location], v0, v1, v2, v3); + GLCALL(glUniform4f(m_uniformLocations[location], v0, v1, v2, v3)); } -void SShader::setUniformMatrix3fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value) { - if (uniformLocations.at(location) == -1) +void CShader::setUniformMatrix3fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value) { + if (m_uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -147,11 +333,11 @@ void SShader::setUniformMatrix3fv(eShaderUniform location, GLsizei count, GLbool } cached = SUniformMatrix3Data{.count = count, .transpose = transpose, .value = value}; - glUniformMatrix3fv(uniformLocations[location], count, transpose, value.data()); + GLCALL(glUniformMatrix3fv(m_uniformLocations[location], count, transpose, value.data())); } -void SShader::setUniformMatrix4x2fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value) { - if (uniformLocations.at(location) == -1) +void CShader::setUniformMatrix4x2fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value) { + if (m_uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -163,11 +349,11 @@ void SShader::setUniformMatrix4x2fv(eShaderUniform location, GLsizei count, GLbo } cached = SUniformMatrix4Data{.count = count, .transpose = transpose, .value = value}; - glUniformMatrix4x2fv(uniformLocations[location], count, transpose, value.data()); + GLCALL(glUniformMatrix4x2fv(m_uniformLocations[location], count, transpose, value.data())); } -void SShader::setUniformfv(eShaderUniform location, GLsizei count, const std::vector& value, GLsizei vec_size) { - if (uniformLocations.at(location) == -1) +void CShader::setUniformfv(eShaderUniform location, GLsizei count, const std::vector& value, GLsizei vec_size) { + if (m_uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -180,36 +366,35 @@ void SShader::setUniformfv(eShaderUniform location, GLsizei count, const std::ve cached = SUniformVData{.count = count, .value = value}; switch (vec_size) { - case 1: glUniform1fv(uniformLocations[location], count, value.data()); break; - case 2: glUniform2fv(uniformLocations[location], count, value.data()); break; - case 4: glUniform4fv(uniformLocations[location], count, value.data()); break; + case 1: GLCALL(glUniform1fv(m_uniformLocations[location], count, value.data())); break; + case 2: GLCALL(glUniform2fv(m_uniformLocations[location], count, value.data())); break; + case 4: GLCALL(glUniform4fv(m_uniformLocations[location], count, value.data())); break; default: UNREACHABLE(); } } -void SShader::setUniform1fv(eShaderUniform location, GLsizei count, const std::vector& value) { +void CShader::setUniform1fv(eShaderUniform location, GLsizei count, const std::vector& value) { setUniformfv(location, count, value, 1); } -void SShader::setUniform2fv(eShaderUniform location, GLsizei count, const std::vector& value) { +void CShader::setUniform2fv(eShaderUniform location, GLsizei count, const std::vector& value) { setUniformfv(location, count, value, 2); } -void SShader::setUniform4fv(eShaderUniform location, GLsizei count, const std::vector& value) { +void CShader::setUniform4fv(eShaderUniform location, GLsizei count, const std::vector& value) { setUniformfv(location, count, value, 4); } -void SShader::destroy() { +void CShader::destroy() { uniformStatus.fill(std::monostate()); - if (program == 0) + if (m_program == 0) return; - GLuint shaderVao, shaderVbo, shaderVboUv; + GLuint shaderVao, shaderVbo; - shaderVao = uniformLocations[SHADER_SHADER_VAO] == -1 ? 0 : uniformLocations[SHADER_SHADER_VAO]; - shaderVbo = uniformLocations[SHADER_SHADER_VBO_POS] == -1 ? 0 : uniformLocations[SHADER_SHADER_VBO_POS]; - shaderVboUv = uniformLocations[SHADER_SHADER_VBO_UV] == -1 ? 0 : uniformLocations[SHADER_SHADER_VBO_UV]; + shaderVao = m_uniformLocations[SHADER_SHADER_VAO] == -1 ? 0 : m_uniformLocations[SHADER_SHADER_VAO]; + shaderVbo = m_uniformLocations[SHADER_SHADER_VBO] == -1 ? 0 : m_uniformLocations[SHADER_SHADER_VBO]; if (shaderVao) glDeleteVertexArrays(1, &shaderVao); @@ -217,9 +402,22 @@ void SShader::destroy() { if (shaderVbo) glDeleteBuffers(1, &shaderVbo); - if (shaderVboUv) - glDeleteBuffers(1, &shaderVboUv); - - glDeleteProgram(program); - program = 0; + glDeleteProgram(m_program); + m_program = 0; +} + +GLint CShader::getUniformLocation(eShaderUniform location) const { + return m_uniformLocations[location]; +} + +GLuint CShader::program() const { + return m_program; +} + +int CShader::getInitialTime() const { + return m_initialTime; +} + +void CShader::setInitialTime(int time) { + m_initialTime = time; } diff --git a/src/render/Shader.hpp b/src/render/Shader.hpp index 50ff5889..9b097c44 100644 --- a/src/render/Shader.hpp +++ b/src/render/Shader.hpp @@ -9,12 +9,11 @@ enum eShaderUniform : uint8_t { SHADER_COLOR, SHADER_ALPHA_MATTE, SHADER_TEX_TYPE, - SHADER_SKIP_CM, SHADER_SOURCE_TF, SHADER_TARGET_TF, SHADER_SRC_TF_RANGE, SHADER_DST_TF_RANGE, - SHADER_TARGET_PRIMARIES, + SHADER_TARGET_PRIMARIES_XYZ, SHADER_MAX_LUMINANCE, SHADER_SRC_REF_LUMINANCE, SHADER_DST_MAX_LUMINANCE, @@ -31,8 +30,7 @@ enum eShaderUniform : uint8_t { SHADER_DISCARD_ALPHA, SHADER_DISCARD_ALPHA_VALUE, SHADER_SHADER_VAO, - SHADER_SHADER_VBO_POS, - SHADER_SHADER_VBO_UV, + SHADER_SHADER_VBO, SHADER_TOP_LEFT, SHADER_BOTTOM_RIGHT, SHADER_FULL_SIZE, @@ -76,19 +74,41 @@ enum eShaderUniform : uint8_t { SHADER_POINTER_INACTIVE_TIMEOUT, SHADER_POINTER_LAST_ACTIVE, SHADER_POINTER_SIZE, + SHADER_LUT_3D, + SHADER_LUT_SIZE, + SHADER_BLURRED_BG, + SHADER_UV_SIZE, + SHADER_UV_OFFSET, SHADER_LAST, }; -struct SShader { - SShader(); - ~SShader(); +class CShader { + public: + CShader(); + ~CShader(); - GLuint program = 0; + bool createProgram(const std::string& vert, const std::string& frag, bool dynamic = false, bool silent = false); + void setUniformInt(eShaderUniform location, GLint v0); + void setUniformFloat(eShaderUniform location, GLfloat v0); + void setUniformFloat2(eShaderUniform location, GLfloat v0, GLfloat v1); + void setUniformFloat3(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2); + void setUniformFloat4(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); + void setUniformMatrix3fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value); + void setUniformMatrix4x2fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value); + void setUniform1fv(eShaderUniform location, GLsizei count, const std::vector& value); + void setUniform2fv(eShaderUniform location, GLsizei count, const std::vector& value); + void setUniform4fv(eShaderUniform location, GLsizei count, const std::vector& value); + void destroy(); + GLuint program() const; + GLint getUniformLocation(eShaderUniform location) const; + int getInitialTime() const; + void setInitialTime(int time); - std::array uniformLocations; - - float initialTime = 0; + private: + GLuint m_program = 0; + float m_initialTime = 0; + std::array m_uniformLocations; struct SUniformMatrix3Data { GLsizei count = 0; @@ -114,19 +134,9 @@ struct SShader { uniformStatus; // - void createVao(); - void setUniformInt(eShaderUniform location, GLint v0); - void setUniformFloat(eShaderUniform location, GLfloat v0); - void setUniformFloat2(eShaderUniform location, GLfloat v0, GLfloat v1); - void setUniformFloat3(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2); - void setUniformFloat4(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); - void setUniformMatrix3fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value); - void setUniformMatrix4x2fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value); - void setUniform1fv(eShaderUniform location, GLsizei count, const std::vector& value); - void setUniform2fv(eShaderUniform location, GLsizei count, const std::vector& value); - void setUniform4fv(eShaderUniform location, GLsizei count, const std::vector& value); - void destroy(); - - private: - void setUniformfv(eShaderUniform location, GLsizei count, const std::vector& value, GLsizei vec_size); + void logShaderError(const GLuint&, bool program = false, bool silent = false); + GLuint compileShader(const GLuint&, std::string, bool dynamic = false, bool silent = false); + void getUniformLocations(); + void createVao(); + void setUniformfv(eShaderUniform location, GLsizei count, const std::vector& value, GLsizei vec_size); }; diff --git a/src/render/ShaderLoader.cpp b/src/render/ShaderLoader.cpp new file mode 100644 index 00000000..0d2d0ee4 --- /dev/null +++ b/src/render/ShaderLoader.cpp @@ -0,0 +1,176 @@ +#include "ShaderLoader.hpp" +#include +#include +#include +#include +#include +#include "../debug/log/Logger.hpp" +#include "shaders/Shaders.hpp" +#include "../helpers/fs/FsUtils.hpp" +#include "Renderer.hpp" +#include +#include +#include + +using namespace Render; + +CShaderLoader::CShaderLoader(const std::vector includes, const std::array& frags, const std::string shaderPath) : m_shaderPath(shaderPath) { + m_callbacks = glsl_include_callbacks_t{ + .include_local = + [](void* ctx, const char* header_name, const char* includer_name, size_t include_depth) { + auto shaderLoader = sc(ctx); + auto res = new glsl_include_result_t; + if (shaderLoader->m_overrideDefines.length() && std::string{header_name} == "defines.h") { + res->header_name = header_name; + res->header_data = shaderLoader->m_overrideDefines.c_str(); + res->header_length = shaderLoader->m_overrideDefines.length(); + } else if (shaderLoader->includes().contains(header_name)) { + res->header_name = header_name; + res->header_data = shaderLoader->includes().at(header_name).c_str(); + res->header_length = shaderLoader->includes().at(header_name).length(); + } else { + res->header_name = nullptr; + res->header_data = nullptr; + res->header_length = 0; + } + + shaderLoader->m_includeResults.push_back(res); + return res; + }, + .free_include_result = + [](void* ctx, glsl_include_result_t* result) { + auto shaderLoader = sc(ctx); + std::erase(shaderLoader->m_includeResults, result); + delete result; + return 0; + }, + }; + + for (const auto& inc : includes) { + include(inc); + } + + std::ranges::transform(frags, m_fragFiles.begin(), [&](const auto& filename) { return loadShader(filename); }); +} + +CShaderLoader::~CShaderLoader() { + // glslFreeIncludeResult should leave it empty by this point + for (const auto& res : m_includeResults) { + delete res; + } +} + +void CShaderLoader::include(const std::string& filename) { + m_includes.insert({filename, loadShader(filename)}); +} + +std::string CShaderLoader::getDefines(ShaderFeatureFlags features) { + std::string res = ""; + std::map defines = { + {"USE_RGBA", features & SH_FEAT_RGBA ? "1" : "0"}, {"USE_DISCARD", features & SH_FEAT_DISCARD ? "1" : "0"}, {"USE_TINT", features & SH_FEAT_TINT ? "1" : "0"}, + {"USE_ROUNDING", features & SH_FEAT_ROUNDING ? "1" : "0"}, {"USE_CM", features & SH_FEAT_CM ? "1" : "0"}, {"USE_TONEMAP", features & SH_FEAT_TONEMAP ? "1" : "0"}, + {"USE_SDR_MOD", features & SH_FEAT_SDR_MOD ? "1" : "0"}, {"USE_BLUR", features & SH_FEAT_BLUR ? "1" : "0"}, {"USE_ICC", features & SH_FEAT_ICC ? "1" : "0"}, + }; + for (const auto& [name, value] : defines) { + res += std::format("#define {} {}\n", name, value); + } + return res; +} + +std::string CShaderLoader::processSource(const std::string& source, glslang_stage_t stage) { + const glslang_input_t input = { + .language = GLSLANG_SOURCE_GLSL, + .stage = stage, + .client = GLSLANG_CLIENT_NONE, + .target_language = GLSLANG_TARGET_NONE, + .code = source.c_str(), + .default_version = 100, + .default_profile = GLSLANG_NO_PROFILE, + .force_default_version_and_profile = false, + .forward_compatible = false, + .messages = GLSLANG_MSG_DEFAULT_BIT, + .resource = glslang_default_resource(), + .callbacks = m_callbacks, + .callbacks_ctx = this, + }; + + glslang_shader_t* shader = glslang_shader_create(&input); + + if (!glslang_shader_preprocess(shader, &input)) { + Log::logger->log(Log::ERR, "GLSL preprocessing failed"); + Log::logger->log(Log::ERR, "{}", glslang_shader_get_info_log(shader)); + Log::logger->log(Log::ERR, "{}", glslang_shader_get_info_debug_log(shader)); + Log::logger->log(Log::ERR, "{}", input.code); + glslang_shader_delete(shader); + return source; + } + + std::stringstream stream(glslang_shader_get_preprocessed_code(shader)); + std::string code = ""; + std::string line; + + while (std::getline(stream, line)) { + if (!line.starts_with("#line ")) + code += line + "\n"; + } + + return code; +} + +std::string CShaderLoader::process(const std::string& filename) { + auto source = loadShader(filename); + return processSource(source, filename.ends_with(".vert") ? GLSLANG_STAGE_VERTEX : GLSLANG_STAGE_FRAGMENT); +} + +std::string CShaderLoader::process(const std::string& filename, const std::map& defines) { + m_overrideDefines = ""; + for (const auto& [name, value] : defines) { + m_overrideDefines += std::format("#define {} {}\n", name, value); + } + const auto& res = process(filename); + m_overrideDefines = ""; + return res; +} + +std::string CShaderLoader::getVariantSource(ePreparedFragmentShader frag, ShaderFeatureFlags features) { + static const auto PCM = CConfigValue("render:cm_enabled"); + if (!*PCM) + features &= ~(SH_FEAT_CM | SH_FEAT_TONEMAP | SH_FEAT_SDR_MOD); + + if (!m_fragVariants[frag].contains(features)) { + ASSERT(m_fragFiles[frag].length()); + m_overrideDefines = getDefines(features); + m_fragVariants[frag][features] = processSource(m_fragFiles[frag]); + m_overrideDefines = ""; + } + + return m_fragVariants[frag][features]; +} + +const std::map& CShaderLoader::includes() { + return m_includes; +} + +// TODO notify user if bundled shader is newer than ~/.config override +std::string CShaderLoader::loadShader(const std::string& filename) { + if (m_shaderPath.length()) { + std::filesystem::path path = m_shaderPath; + const auto src = NFsUtils::readFileAsString(path / filename); + if (src.has_value()) + return src.value(); + } + const auto home = Hyprutils::Path::getHome(); + if (home.has_value()) { + const auto src = NFsUtils::readFileAsString(home.value() + "/hypr/shaders/" + filename); + if (src.has_value()) + return src.value(); + } + for (auto& e : ASSET_PATHS) { + const auto src = NFsUtils::readFileAsString(std::string{e} + "/hypr/shaders/" + filename); + if (src.has_value()) + return src.value(); + } + if (SHADERS.contains(filename)) + return SHADERS.at(filename); + throw std::runtime_error(std::format("Couldn't load shader {}", filename)); +} diff --git a/src/render/ShaderLoader.hpp b/src/render/ShaderLoader.hpp new file mode 100644 index 00000000..e522e9fa --- /dev/null +++ b/src/render/ShaderLoader.hpp @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "../helpers/memory/Memory.hpp" + +namespace Render { + enum ePreparedFragmentShaderFeature : uint16_t { + SH_FEAT_UNKNOWN = 0, // all features just in case + + SH_FEAT_RGBA = (1 << 0), // RGBA/RGBX texture sampling + SH_FEAT_DISCARD = (1 << 1), // RGBA/RGBX texture sampling + SH_FEAT_TINT = (1 << 2), // uniforms: tint; condition: applyTint + SH_FEAT_ROUNDING = (1 << 3), // uniforms: radius, roundingPower, topLeft, fullSize; condition: radius > 0 + SH_FEAT_CM = (1 << 4), // uniforms: srcTFRange, dstTFRange, srcRefLuminance, convertMatrix; condition: !skipCM + SH_FEAT_TONEMAP = (1 << 5), // uniforms: maxLuminance, dstMaxLuminance, dstRefLuminance; condition: maxLuminance < dstMaxLuminance * 1.01 + SH_FEAT_SDR_MOD = (1 << 6), // uniforms: sdrSaturation, sdrBrightnessMultiplier; condition: SDR <-> HDR && (sdrSaturation != 1 || sdrBrightnessMultiplier != 1) + SH_FEAT_BLUR = (1 << 7), // condition: render:use_shader_blur_blend + SH_FEAT_ICC = (1 << 8), // + + // uniforms: targetPrimariesXYZ; condition: SH_FEAT_TONEMAP || SH_FEAT_SDR_MOD + }; + + using ShaderFeatureFlags = uint16_t; + + enum ePreparedFragmentShader : uint8_t { + SH_FRAG_QUAD = 0, + SH_FRAG_PASSTHRURGBA, + SH_FRAG_MATTE, + SH_FRAG_EXT, + SH_FRAG_BLUR1, + SH_FRAG_BLUR2, + SH_FRAG_BLURPREPARE, + SH_FRAG_BLURFINISH, + SH_FRAG_SHADOW, + SH_FRAG_SURFACE, + SH_FRAG_BORDER1, + SH_FRAG_GLITCH, + + SH_FRAG_LAST, + }; + + class CShaderLoader { + public: + CShaderLoader(const std::vector includes, const std::array& frags, const std::string shaderPath = ""); + ~CShaderLoader(); + + void include(const std::string& filename); + std::string process(const std::string& filename); + std::string process(const std::string& filename, const std::map& defines); + + std::string getVariantSource(ePreparedFragmentShader frag, ShaderFeatureFlags features); + + const std::map& includes(); + + std::vector m_includeResults; + + private: + std::string loadShader(const std::string& filename); + std::string getDefines(ShaderFeatureFlags features); + std::string processSource(const std::string& source, glslang_stage_t stage = GLSLANG_STAGE_FRAGMENT); + + // + std::string m_shaderPath; + std::array m_fragFiles; + std::array, SH_FRAG_LAST> m_fragVariants; + std::map m_includes; + + std::string m_overrideDefines; + glsl_include_callbacks_t m_callbacks; + }; + + inline UP g_pShaderLoader; +} diff --git a/src/render/Texture.cpp b/src/render/Texture.cpp index f1704afa..28ae4b41 100644 --- a/src/render/Texture.cpp +++ b/src/render/Texture.cpp @@ -1,207 +1,24 @@ #include "Texture.hpp" -#include "Renderer.hpp" -#include "../Compositor.hpp" -#include "../protocols/types/Buffer.hpp" -#include "../helpers/Format.hpp" #include -CTexture::CTexture() = default; - -CTexture::~CTexture() { - if (!g_pCompositor || g_pCompositor->m_isShuttingDown || !g_pHyprRenderer) - return; - - g_pHyprRenderer->makeEGLCurrent(); - destroyTexture(); -} - -CTexture::CTexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size_, bool keepDataCopy) : m_drmFormat(drmFormat), m_keepDataCopy(keepDataCopy) { - createFromShm(drmFormat, pixels, stride, size_); -} - -CTexture::CTexture(const Aquamarine::SDMABUFAttrs& attrs, void* image) { - createFromDma(attrs, image); -} - -CTexture::CTexture(const SP buffer, bool keepDataCopy) : m_keepDataCopy(keepDataCopy) { - if (!buffer) - return; - - m_opaque = buffer->opaque; - - auto attrs = buffer->dmabuf(); - - if (!attrs.success) { - // attempt shm - auto shm = buffer->shm(); - - if (!shm.success) { - Log::logger->log(Log::ERR, "Cannot create a texture: buffer has no dmabuf or shm"); - return; - } - - auto [pixelData, fmt, bufLen] = buffer->beginDataPtr(0); - - m_drmFormat = fmt; - - createFromShm(fmt, pixelData, bufLen, shm.size); - return; - } - - auto image = g_pHyprOpenGL->createEGLImage(buffer->dmabuf()); - - if (!image) { - Log::logger->log(Log::ERR, "Cannot create a texture: failed to create an EGLImage"); - return; - } - - createFromDma(attrs, image); -} - -void CTexture::createFromShm(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size_) { - g_pHyprRenderer->makeEGLCurrent(); - - const auto format = NFormatUtils::getPixelFormatFromDRM(drmFormat); - ASSERT(format); - - m_type = format->withAlpha ? TEXTURE_RGBA : TEXTURE_RGBX; - m_size = size_; - m_isSynchronous = true; - m_target = GL_TEXTURE_2D; - allocate(); - bind(); - setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - if (format->flipRB) { - setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE); - setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED); - } - - GLCALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / format->bytesPerBlock)); - GLCALL(glTexImage2D(GL_TEXTURE_2D, 0, format->glInternalFormat ? format->glInternalFormat : format->glFormat, size_.x, size_.y, 0, format->glFormat, format->glType, pixels)); - GLCALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0)); - unbind(); - - if (m_keepDataCopy) { - m_dataCopy.resize(stride * size_.y); - memcpy(m_dataCopy.data(), pixels, stride * size_.y); +ITexture::ITexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size, bool keepDataCopy, bool opaque) : + m_size(size), m_opaque(opaque), m_drmFormat(drmFormat), m_keepDataCopy(keepDataCopy) { + if (m_keepDataCopy && stride && pixels) { + m_dataCopy.resize(stride * size.y); + memcpy(m_dataCopy.data(), pixels, stride * size.y); } } -void CTexture::createFromDma(const Aquamarine::SDMABUFAttrs& attrs, void* image) { - if (!g_pHyprOpenGL->m_proc.glEGLImageTargetTexture2DOES) { - Log::logger->log(Log::ERR, "Cannot create a dmabuf texture: no glEGLImageTargetTexture2DOES"); - return; - } +ITexture::ITexture(std::span lut3D, size_t N) : m_type(TEXTURE_3D_LUT), m_size(lut3D.size() / 3, 1), m_isSynchronous(true) {} - m_opaque = NFormatUtils::isFormatOpaque(attrs.format); - m_target = GL_TEXTURE_2D; - m_type = TEXTURE_RGBA; - m_size = attrs.size; - m_type = NFormatUtils::isFormatOpaque(attrs.format) ? TEXTURE_RGBX : TEXTURE_RGBA; - allocate(); - m_eglImage = image; - - bind(); - setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - GLCALL(g_pHyprOpenGL->m_proc.glEGLImageTargetTexture2DOES(m_target, image)); - unbind(); +bool ITexture::ok() { + return false; } -void CTexture::update(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const CRegion& damage) { - if (damage.empty()) - return; - - g_pHyprRenderer->makeEGLCurrent(); - - const auto format = NFormatUtils::getPixelFormatFromDRM(drmFormat); - ASSERT(format); - - bind(); - - if (format->flipRB) { - setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE); - setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED); - } - - damage.copy().intersect(CBox{{}, m_size}).forEachRect([&format, &stride, &pixels](const auto& rect) { - 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)); - - int width = rect.x2 - rect.x1; - int height = rect.y2 - rect.y1; - GLCALL(glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x1, rect.y1, width, height, format->glFormat, format->glType, pixels)); - }); - - GLCALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0)); - GLCALL(glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0)); - GLCALL(glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, 0)); - - unbind(); - - if (m_keepDataCopy) { - m_dataCopy.resize(stride * m_size.y); - memcpy(m_dataCopy.data(), pixels, stride * m_size.y); - } +bool ITexture::isDMA() { + return false; } -void CTexture::destroyTexture() { - if (m_texID) { - GLCALL(glDeleteTextures(1, &m_texID)); - m_texID = 0; - } - - if (m_eglImage) - g_pHyprOpenGL->m_proc.eglDestroyImageKHR(g_pHyprOpenGL->m_eglDisplay, m_eglImage); - m_eglImage = nullptr; - m_cachedStates.fill(std::nullopt); -} - -void CTexture::allocate() { - if (!m_texID) - GLCALL(glGenTextures(1, &m_texID)); -} - -const std::vector& CTexture::dataCopy() { +const std::vector& ITexture::dataCopy() { return m_dataCopy; } - -void CTexture::bind() { - GLCALL(glBindTexture(m_target, m_texID)); -} - -void CTexture::unbind() { - GLCALL(glBindTexture(m_target, 0)); -} - -constexpr std::optional CTexture::getCacheStateIndex(GLenum pname) { - switch (pname) { - case GL_TEXTURE_WRAP_S: return TEXTURE_PAR_WRAP_S; - case GL_TEXTURE_WRAP_T: return TEXTURE_PAR_WRAP_T; - case GL_TEXTURE_MAG_FILTER: return TEXTURE_PAR_MAG_FILTER; - case GL_TEXTURE_MIN_FILTER: return TEXTURE_PAR_MIN_FILTER; - case GL_TEXTURE_SWIZZLE_R: return TEXTURE_PAR_SWIZZLE_R; - case GL_TEXTURE_SWIZZLE_B: return TEXTURE_PAR_SWIZZLE_B; - default: return std::nullopt; - } -} - -void CTexture::setTexParameter(GLenum pname, GLint param) { - const auto cacheIndex = getCacheStateIndex(pname); - - if (!cacheIndex) { - GLCALL(glTexParameteri(m_target, pname, param)); - return; - } - - const auto idx = cacheIndex.value(); - - if (m_cachedStates[idx] == param) - return; - - m_cachedStates[idx] = param; - GLCALL(glTexParameteri(m_target, pname, param)); -} diff --git a/src/render/Texture.hpp b/src/render/Texture.hpp index b9811230..38c3ff01 100644 --- a/src/render/Texture.hpp +++ b/src/render/Texture.hpp @@ -3,6 +3,7 @@ #include "../defines.hpp" #include #include +#include class IHLBuffer; HYPRUTILS_FORWARD(Math, CRegion); @@ -11,59 +12,47 @@ enum eTextureType : int8_t { TEXTURE_INVALID = -1, // Invalid TEXTURE_RGBA = 0, // 4 channels TEXTURE_RGBX, // discard A + TEXTURE_3D_LUT, // 3D LUT TEXTURE_EXTERNAL, // EGLImage }; -class CTexture { +class ITexture { public: - CTexture(); + ITexture(ITexture&) = delete; + ITexture(ITexture&&) = delete; + ITexture(const ITexture&&) = delete; + ITexture(const ITexture&) = delete; - CTexture(CTexture&) = delete; - CTexture(CTexture&&) = delete; - CTexture(const CTexture&&) = delete; - CTexture(const CTexture&) = delete; + virtual ~ITexture() = default; - CTexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size, bool keepDataCopy = false); + virtual void setTexParameter(GLenum pname, GLint param) = 0; + virtual void allocate(const Vector2D& size, uint32_t drmFormat = 0) = 0; + virtual void update(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const CRegion& damage) = 0; + virtual void bind() {}; + virtual void unbind() {}; + virtual bool ok(); + virtual bool isDMA(); - CTexture(const SP buffer, bool keepDataCopy = false); - // this ctor takes ownership of the eglImage. - CTexture(const Aquamarine::SDMABUFAttrs&, void* image); - ~CTexture(); - - void destroyTexture(); - void allocate(); - void update(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const CRegion& damage); const std::vector& dataCopy(); - void bind(); - void unbind(); - void setTexParameter(GLenum pname, GLint param); - eTextureType m_type = TEXTURE_RGBA; - GLenum m_target = GL_TEXTURE_2D; - GLuint m_texID = 0; - Vector2D m_size = {}; - void* m_eglImage = nullptr; - eTransform m_transform = HYPRUTILS_TRANSFORM_NORMAL; - bool m_opaque = false; + eTextureType m_type = TEXTURE_RGBA; + Vector2D m_size = {}; + eTransform m_transform = HYPRUTILS_TRANSFORM_NORMAL; + bool m_opaque = false; + uint32_t m_drmFormat = 0; // for shm bool m_isSynchronous = false; - private: - enum eTextureParam : uint8_t { - TEXTURE_PAR_WRAP_S = 0, - TEXTURE_PAR_WRAP_T, - TEXTURE_PAR_MAG_FILTER, - TEXTURE_PAR_MIN_FILTER, - TEXTURE_PAR_SWIZZLE_R, - TEXTURE_PAR_SWIZZLE_B, - TEXTURE_PAR_LAST, - }; + // TODO move to GLTexture + GLuint m_texID = 0; + GLenum magFilter = GL_LINEAR; // useNearestNeighbor overwrites these + GLenum minFilter = GL_LINEAR; - void createFromShm(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size); - void createFromDma(const Aquamarine::SDMABUFAttrs&, void* image); - inline constexpr std::optional getCacheStateIndex(GLenum pname); + protected: + ITexture() = default; + ITexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size, bool keepDataCopy = false, bool opaque = false); + ITexture(std::span lut3D, size_t N); - bool m_keepDataCopy = false; - std::vector m_dataCopy; - std::array, TEXTURE_PAR_LAST> m_cachedStates; + bool m_keepDataCopy = false; + std::vector m_dataCopy; }; diff --git a/src/render/Transformer.hpp b/src/render/Transformer.hpp index 048b1898..8f401859 100644 --- a/src/render/Transformer.hpp +++ b/src/render/Transformer.hpp @@ -14,7 +14,7 @@ class IWindowTransformer { // called by Hyprland. For more data about what is being rendered, inspect render data. // returns the out fb. - virtual CFramebuffer* transform(CFramebuffer* in) = 0; + virtual IFramebuffer* transform(IFramebuffer* in) = 0; // called by Hyprland before a window main pass is started. virtual void preWindowRender(CSurfacePassElement::SRenderData* pRenderData); diff --git a/src/render/decorations/CHyprBorderDecoration.cpp b/src/render/decorations/CHyprBorderDecoration.cpp index a082f073..3e4f04a9 100644 --- a/src/render/decorations/CHyprBorderDecoration.cpp +++ b/src/render/decorations/CHyprBorderDecoration.cpp @@ -4,7 +4,6 @@ #include "../../managers/eventLoop/EventLoopManager.hpp" #include "../pass/BorderPassElement.hpp" #include "../Renderer.hpp" -#include "../../managers/HookSystemManager.hpp" CHyprBorderDecoration::CHyprBorderDecoration(PHLWINDOW pWindow) : IHyprWindowDecoration(pWindow), m_window(pWindow) { ; @@ -115,26 +114,15 @@ void CHyprBorderDecoration::updateWindow(PHLWINDOW) { } void CHyprBorderDecoration::damageEntire() { - if (!validMapped(m_window)) + if (!validMapped(m_window) || m_window->m_fullscreenState.internal == FSMODE_FULLSCREEN) return; - auto surfaceBox = m_window->getWindowMainSurfaceBox(); - const auto ROUNDING = m_window->rounding(); - const auto ROUNDINGSIZE = ROUNDING - M_SQRT1_2 * ROUNDING + 2; - const auto BORDERSIZE = m_window->getRealBorderSize() + 1; + const auto GLOBAL_BOX = assignedBoxGlobal(); + const auto ROUNDING = m_window->rounding(); + const auto BORDERSIZE = m_window->getRealBorderSize() + 1; - const auto PWINDOWWORKSPACE = m_window->m_workspace; - if (PWINDOWWORKSPACE && PWINDOWWORKSPACE->m_renderOffset->isBeingAnimated() && !m_window->m_pinned) - surfaceBox.translate(PWINDOWWORKSPACE->m_renderOffset->value()); - surfaceBox.translate(m_window->m_floatingOffset); - - CBox surfaceBoxExpandedBorder = surfaceBox; - surfaceBoxExpandedBorder.expand(BORDERSIZE); - CBox surfaceBoxShrunkRounding = surfaceBox; - surfaceBoxShrunkRounding.expand(-ROUNDINGSIZE); - - CRegion borderRegion(surfaceBoxExpandedBorder); - borderRegion.subtract(surfaceBoxShrunkRounding); + CRegion borderRegion(GLOBAL_BOX); + borderRegion.subtract(GLOBAL_BOX.copy().expand(-(BORDERSIZE + ROUNDING))); for (auto const& m : g_pCompositor->m_monitors) { if (!g_pHyprRenderer->shouldRenderWindow(m_window.lock(), m)) { diff --git a/src/render/decorations/CHyprDropShadowDecoration.cpp b/src/render/decorations/CHyprDropShadowDecoration.cpp index dd82abc5..5e1b6e8a 100644 --- a/src/render/decorations/CHyprDropShadowDecoration.cpp +++ b/src/render/decorations/CHyprDropShadowDecoration.cpp @@ -155,9 +155,9 @@ void CHyprDropShadowDecoration::render(PHLMONITOR pMonitor, float const& a) { g_pHyprOpenGL->m_renderData.currentWindow = m_window; // we'll take the liberty of using this as it should not be used rn - CFramebuffer& alphaFB = g_pHyprOpenGL->m_renderData.pCurrentMonData->mirrorFB; - CFramebuffer& alphaSwapFB = g_pHyprOpenGL->m_renderData.pCurrentMonData->mirrorSwapFB; - auto* LASTFB = g_pHyprOpenGL->m_renderData.currentFB; + auto alphaFB = g_pHyprOpenGL->m_renderData.pCurrentMonData->mirrorFB; + auto alphaSwapFB = g_pHyprOpenGL->m_renderData.pCurrentMonData->mirrorSwapFB; + auto LASTFB = g_pHyprOpenGL->m_renderData.currentFB; fullBox.scale(pMonitor->m_scale).round(); @@ -188,7 +188,7 @@ void CHyprDropShadowDecoration::render(PHLMONITOR pMonitor, float const& a) { g_pHyprOpenGL->m_renderData.damage.subtract(windowBox.copy().expand(-ROUNDING * pMonitor->m_scale)).intersect(saveDamage); g_pHyprOpenGL->m_renderData.renderModif.applyToRegion(g_pHyprOpenGL->m_renderData.damage); - alphaFB.bind(); + alphaFB->bind(); // build the matte // 10-bit formats have dogshit alpha channels, so we have to use the matte to its fullest. @@ -202,7 +202,7 @@ void CHyprDropShadowDecoration::render(PHLMONITOR pMonitor, float const& a) { g_pHyprOpenGL->renderRect(windowBox, CHyprColor(0, 0, 0, 1.0), {.round = (ROUNDING + 1 /* This fixes small pixel gaps. */) * pMonitor->m_scale, .roundingPower = ROUNDINGPOWER}); - alphaSwapFB.bind(); + alphaSwapFB->bind(); // alpha swap just has the shadow color. It will be the "texture" to render. g_pHyprOpenGL->renderRect(fullBox, PWINDOW->m_realShadowColor->value().stripA(), {.round = 0}); @@ -213,7 +213,7 @@ void CHyprDropShadowDecoration::render(PHLMONITOR pMonitor, float const& a) { g_pHyprOpenGL->pushMonitorTransformEnabled(true); g_pHyprOpenGL->setRenderModifEnabled(false); - g_pHyprOpenGL->renderTextureMatte(alphaSwapFB.getTexture(), monbox, alphaFB); + g_pHyprOpenGL->renderTextureMatte(alphaSwapFB->getTexture(), monbox, alphaFB); g_pHyprOpenGL->setRenderModifEnabled(true); g_pHyprOpenGL->popMonitorTransformEnabled(); diff --git a/src/render/decorations/CHyprGroupBarDecoration.cpp b/src/render/decorations/CHyprGroupBarDecoration.cpp index 93a17341..6ce69261 100644 --- a/src/render/decorations/CHyprGroupBarDecoration.cpp +++ b/src/render/decorations/CHyprGroupBarDecoration.cpp @@ -2,19 +2,21 @@ #include "../../Compositor.hpp" #include "../../config/ConfigValue.hpp" #include "../../desktop/state/FocusState.hpp" -#include "managers/LayoutManager.hpp" +#include "../../desktop/view/Group.hpp" #include #include #include "../pass/TexPassElement.hpp" #include "../pass/RectPassElement.hpp" #include "../Renderer.hpp" #include "../../managers/input/InputManager.hpp" +#include "../../layout/LayoutManager.hpp" +#include "../../layout/supplementary/DragController.hpp" // shared things to conserve VRAM -static SP m_tGradientActive = makeShared(); -static SP m_tGradientInactive = makeShared(); -static SP m_tGradientLockedActive = makeShared(); -static SP m_tGradientLockedInactive = makeShared(); +static SP m_tGradientActive; +static SP m_tGradientInactive; +static SP m_tGradientLockedActive; +static SP m_tGradientLockedInactive; constexpr int BAR_TEXT_PAD = 2; @@ -22,7 +24,16 @@ CHyprGroupBarDecoration::CHyprGroupBarDecoration(PHLWINDOW pWindow) : IHyprWindo static auto PGRADIENTS = CConfigValue("group:groupbar:enabled"); static auto PENABLED = CConfigValue("group:groupbar:gradients"); - if (m_tGradientActive->m_texID == 0 && *PENABLED && *PGRADIENTS) + if (!m_tGradientActive) + m_tGradientActive = g_pHyprRenderer->createTexture(); + if (!m_tGradientInactive) + m_tGradientInactive = g_pHyprRenderer->createTexture(); + if (!m_tGradientLockedActive) + m_tGradientLockedActive = g_pHyprRenderer->createTexture(); + if (!m_tGradientLockedInactive) + m_tGradientLockedInactive = g_pHyprRenderer->createTexture(); + + if (!m_tGradientActive->ok() && *PENABLED && *PGRADIENTS) refreshGroupBarGradients(); } @@ -65,19 +76,14 @@ eDecorationType CHyprGroupBarDecoration::getDecorationType() { // void CHyprGroupBarDecoration::updateWindow(PHLWINDOW pWindow) { - if (m_window->m_groupData.pNextWindow.expired()) { + if (!m_window->m_group) { m_window->removeWindowDeco(this); return; } m_dwGroupMembers.clear(); - PHLWINDOW head = pWindow->getGroupHead(); - m_dwGroupMembers.emplace_back(head); - - PHLWINDOW curr = head->m_groupData.pNextWindow.lock(); - while (curr != head) { - m_dwGroupMembers.emplace_back(curr); - curr = curr->m_groupData.pNextWindow.lock(); + for (const auto& w : m_window->m_group->windows()) { + m_dwGroupMembers.emplace_back(w); } damageEntire(); @@ -127,6 +133,7 @@ void CHyprGroupBarDecoration::draw(PHLMONITOR pMonitor, float const& a) { static auto PINNERGAP = CConfigValue("group:groupbar:gaps_in"); static auto PKEEPUPPERGAP = CConfigValue("group:groupbar:keep_upper_gap"); static auto PTEXTOFFSET = CConfigValue("group:groupbar:text_offset"); + static auto PTEXTPADDING = CConfigValue("group:groupbar:text_padding"); static auto PBLUR = CConfigValue("group:groupbar:blur"); auto* const GROUPCOLACTIVE = sc((PGROUPCOLACTIVE.ptr())->getData()); auto* const GROUPCOLINACTIVE = sc((PGROUPCOLINACTIVE.ptr())->getData()); @@ -157,7 +164,7 @@ void CHyprGroupBarDecoration::draw(PHLMONITOR pMonitor, float const& a) { rect.scale(pMonitor->m_scale).round(); - const bool GROUPLOCKED = m_window->getGroupHead()->m_groupData.locked || g_pKeybindManager->m_groupsLocked; + const bool GROUPLOCKED = m_window->m_group->locked() || g_pKeybindManager->m_groupsLocked; const auto* const PCOLACTIVE = GROUPLOCKED ? GROUPCOLACTIVELOCKED : GROUPCOLACTIVE; const auto* const PCOLINACTIVE = GROUPLOCKED ? GROUPCOLINACTIVELOCKED : GROUPCOLINACTIVE; @@ -198,11 +205,12 @@ void CHyprGroupBarDecoration::draw(PHLMONITOR pMonitor, float const& a) { if (*PGRADIENTS) { const auto GRADIENTTEX = (m_dwGroupMembers[WINDOWINDEX] == Desktop::focusState()->window() ? (GROUPLOCKED ? m_tGradientLockedActive : m_tGradientActive) : (GROUPLOCKED ? m_tGradientLockedInactive : m_tGradientInactive)); - if (GRADIENTTEX->m_texID) { + if (GRADIENTTEX->ok()) { CTexPassElement::SRenderData data; data.tex = GRADIENTTEX; data.blur = blur; data.box = rect; + data.a = a; if (*PGRADIENTROUNDING) { data.round = *PGRADIENTROUNDING; data.roundingPower = *PGRADIENTROUNDINGPOWER; @@ -228,13 +236,14 @@ void CHyprGroupBarDecoration::draw(PHLMONITOR pMonitor, float const& a) { CTitleTex* pTitleTex = textureFromTitle(m_dwGroupMembers[WINDOWINDEX]->m_title); if (!pTitleTex) - pTitleTex = m_titleTexs.titleTexs - .emplace_back(makeUnique(m_dwGroupMembers[WINDOWINDEX].lock(), - Vector2D{m_barWidth * pMonitor->m_scale, (*PTITLEFONTSIZE + 2L * BAR_TEXT_PAD) * pMonitor->m_scale}, - pMonitor->m_scale)) - .get(); + pTitleTex = + m_titleTexs.titleTexs + .emplace_back(makeUnique( + m_dwGroupMembers[WINDOWINDEX].lock(), + Vector2D{(m_barWidth - (*PTEXTPADDING * 2)) * pMonitor->m_scale, (*PTITLEFONTSIZE + 2L * BAR_TEXT_PAD) * pMonitor->m_scale}, pMonitor->m_scale)) + .get(); - SP titleTex; + SP titleTex; if (m_dwGroupMembers[WINDOWINDEX] == Desktop::focusState()->window()) titleTex = GROUPLOCKED ? pTitleTex->m_texLockedActive : pTitleTex->m_texActive; else @@ -243,7 +252,7 @@ void CHyprGroupBarDecoration::draw(PHLMONITOR pMonitor, float const& a) { rect.y += std::ceil(((rect.height - titleTex->m_size.y) / 2.0) - (*PTEXTOFFSET * pMonitor->m_scale)); rect.height = titleTex->m_size.y; rect.width = titleTex->m_size.x; - rect.x += std::round(((m_barWidth * pMonitor->m_scale) / 2.0) - (titleTex->m_size.x / 2.0)); + rect.x += std::round((((m_barWidth + *PTEXTPADDING) * pMonitor->m_scale) / 2.0) - ((titleTex->m_size.x + *PTEXTPADDING) / 2.0)); rect.round(); CTexPassElement::SRenderData data; @@ -307,7 +316,7 @@ CTitleTex::CTitleTex(PHLWINDOW pWindow, const Vector2D& bufferSize, const float #undef RENDER_TEXT } -static void renderGradientTo(SP tex, CGradientValueData* grad) { +static void renderGradientTo(SP tex, CGradientValueData* grad) { if (!Desktop::focusState()->monitor()) return; @@ -339,15 +348,7 @@ static void renderGradientTo(SP tex, CGradientValueData* grad) { cairo_surface_flush(CAIROSURFACE); // copy the data to an OpenGL texture we have - const auto DATA = cairo_image_surface_get_data(CAIROSURFACE); - tex->allocate(); - tex->bind(); - tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_NEAREST); - tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST); - tex->setTexParameter(GL_TEXTURE_SWIZZLE_R, GL_BLUE); - tex->setTexParameter(GL_TEXTURE_SWIZZLE_B, GL_RED); - - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bufferSize.x, bufferSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA); + tex = g_pHyprRenderer->createTexture(CAIROSURFACE); // delete cairo cairo_destroy(CAIRO); @@ -367,13 +368,11 @@ void refreshGroupBarGradients() { auto* const GROUPCOLACTIVELOCKED = sc((PGROUPCOLACTIVELOCKED.ptr())->getData()); auto* const GROUPCOLINACTIVELOCKED = sc((PGROUPCOLINACTIVELOCKED.ptr())->getData()); - g_pHyprRenderer->makeEGLCurrent(); - - if (m_tGradientActive->m_texID != 0) { - m_tGradientActive->destroyTexture(); - m_tGradientInactive->destroyTexture(); - m_tGradientLockedActive->destroyTexture(); - m_tGradientLockedInactive->destroyTexture(); + if (m_tGradientActive && m_tGradientActive->ok()) { + m_tGradientActive.reset(); + m_tGradientInactive.reset(); + m_tGradientLockedActive.reset(); + m_tGradientLockedInactive.reset(); } if (!*PENABLED || !*PGRADIENTS) @@ -389,7 +388,7 @@ bool CHyprGroupBarDecoration::onBeginWindowDragOnDeco(const Vector2D& pos) { static auto PSTACKED = CConfigValue("group:groupbar:stacked"); static auto POUTERGAP = CConfigValue("group:groupbar:gaps_out"); static auto PINNERGAP = CConfigValue("group:groupbar:gaps_in"); - if (m_window.lock() == m_window->m_groupData.pNextWindow.lock()) + if (m_window->m_group->size() == 1) return false; const float BARRELATIVEX = pos.x - assignedBoxGlobal().x; @@ -402,95 +401,33 @@ bool CHyprGroupBarDecoration::onBeginWindowDragOnDeco(const Vector2D& pos) { if (*PSTACKED && (BARRELATIVEY - (m_barHeight + *POUTERGAP) * WINDOWINDEX < *POUTERGAP)) return false; - PHLWINDOW pWindow = m_window->getGroupWindowByIndex(WINDOWINDEX); + PHLWINDOW pWindow = m_window->m_group->fromIndex(WINDOWINDEX); - // hack - g_pLayoutManager->getCurrentLayout()->onWindowRemoved(pWindow); - if (!pWindow->m_isFloating) { - const bool GROUPSLOCKEDPREV = g_pKeybindManager->m_groupsLocked; - g_pKeybindManager->m_groupsLocked = true; - g_pLayoutManager->getCurrentLayout()->onWindowCreated(pWindow); - g_pKeybindManager->m_groupsLocked = GROUPSLOCKEDPREV; - } + const auto& GROUP = m_window->m_group; - g_pInputManager->m_currentlyDraggedWindow = pWindow; + // remove the window from the group + GROUP->remove(pWindow); + + // start a move drag on it + g_layoutManager->dragController()->dragBegin(pWindow->layoutTarget(), MBIND_MOVE); if (!g_pCompositor->isWindowActive(pWindow)) - Desktop::focusState()->rawWindowFocus(pWindow); + Desktop::focusState()->rawWindowFocus(pWindow, Desktop::FOCUS_REASON_CLICK); return true; } bool CHyprGroupBarDecoration::onEndWindowDragOnDeco(const Vector2D& pos, PHLWINDOW pDraggedWindow) { - 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"); static auto PMERGEGROUPSONGROUPBAR = CConfigValue("group:merge_groups_on_groupbar"); - static auto POUTERGAP = CConfigValue("group:groupbar:gaps_out"); - static auto PINNERGAP = CConfigValue("group:groupbar:gaps_in"); - const bool FLOATEDINTOTILED = !m_window->m_isFloating && !pDraggedWindow->m_draggingTiled; + const bool FLOATEDINTOTILED = !m_window->m_isFloating && !g_layoutManager->dragController()->draggingTiled(); - g_pInputManager->m_wasDraggingWindow = false; - - if (!pDraggedWindow->canBeGroupedInto(m_window.lock()) || (*PDRAGINTOGROUP != 1 && *PDRAGINTOGROUP != 2) || (FLOATEDINTOTILED && !*PMERGEFLOATEDINTOTILEDONGROUPBAR) || - (!*PMERGEGROUPSONGROUPBAR && pDraggedWindow->m_groupData.pNextWindow.lock() && m_window->m_groupData.pNextWindow.lock())) { - g_pInputManager->m_wasDraggingWindow = true; + if (!pDraggedWindow->canBeGroupedInto(m_window->m_group) || (*PDRAGINTOGROUP != 1 && *PDRAGINTOGROUP != 2) || (FLOATEDINTOTILED && !*PMERGEFLOATEDINTOTILEDONGROUPBAR) || + (!*PMERGEGROUPSONGROUPBAR && pDraggedWindow->m_group)) return false; - } - const float BARRELATIVE = *PSTACKED ? pos.y - assignedBoxGlobal().y - (m_barHeight + *POUTERGAP) / 2 : pos.x - assignedBoxGlobal().x - m_barWidth / 2; - const float BARSIZE = *PSTACKED ? m_barHeight + *POUTERGAP : m_barWidth + *PINNERGAP; - const int WINDOWINDEX = BARRELATIVE < 0 ? -1 : BARRELATIVE / BARSIZE; - - PHLWINDOW pWindowInsertAfter = m_window->getGroupWindowByIndex(WINDOWINDEX); - PHLWINDOW pWindowInsertEnd = pWindowInsertAfter->m_groupData.pNextWindow.lock(); - PHLWINDOW pDraggedHead = pDraggedWindow->m_groupData.pNextWindow.lock() ? pDraggedWindow->getGroupHead() : pDraggedWindow; - - if (!pDraggedWindow->m_groupData.pNextWindow.expired()) { - - // stores group data - std::vector members; - PHLWINDOW curr = pDraggedHead; - const bool WASLOCKED = pDraggedHead->m_groupData.locked; - do { - members.push_back(curr); - curr = curr->m_groupData.pNextWindow.lock(); - } while (curr != members[0]); - - // removes all windows - for (const PHLWINDOW& w : members) { - w->m_groupData.pNextWindow.reset(); - w->m_groupData.head = false; - w->m_groupData.locked = false; - g_pLayoutManager->getCurrentLayout()->onWindowRemoved(w); - } - - // restores the group - for (auto it = members.begin(); it != members.end(); ++it) { - (*it)->m_isFloating = pWindowInsertAfter->m_isFloating; // match the floating state of group members - *(*it)->m_realSize = pWindowInsertAfter->m_realSize->goal(); // match the size of group members - *(*it)->m_realPosition = pWindowInsertAfter->m_realPosition->goal(); // match the position of group members - if (std::next(it) != members.end()) - (*it)->m_groupData.pNextWindow = *std::next(it); - else - (*it)->m_groupData.pNextWindow = members[0]; - } - members[0]->m_groupData.head = true; - members[0]->m_groupData.locked = WASLOCKED; - } else - g_pLayoutManager->getCurrentLayout()->onWindowRemoved(pDraggedWindow); - - pDraggedWindow->m_isFloating = pWindowInsertAfter->m_isFloating; // match the floating state of the window - - pWindowInsertAfter->insertWindowToGroup(pDraggedWindow); - - if (WINDOWINDEX == -1) - std::swap(pDraggedHead->m_groupData.head, pWindowInsertEnd->m_groupData.head); - - m_window->setGroupCurrent(pDraggedWindow); - pDraggedWindow->applyGroupRules(); - pDraggedWindow->updateWindowDecos(); - g_pLayoutManager->getCurrentLayout()->recalculateWindow(pDraggedWindow); + m_window->m_group->add(pDraggedWindow); if (!pDraggedWindow->getDecorationByType(DECORATION_GROUPBAR)) pDraggedWindow->addWindowDeco(makeUnique(pDraggedWindow)); @@ -517,7 +454,7 @@ bool CHyprGroupBarDecoration::onMouseButtonOnDeco(const Vector2D& pos, const IPo if (e.state == WL_POINTER_BUTTON_STATE_PRESSED) pressedCursorPos = pos; else if (e.state == WL_POINTER_BUTTON_STATE_RELEASED && pressedCursorPos == pos) - g_pXWaylandManager->sendCloseWindow(m_window->getGroupWindowByIndex(WINDOWINDEX)); + g_pXWaylandManager->sendCloseWindow(m_window->m_group->fromIndex(WINDOWINDEX)); return true; } @@ -530,17 +467,17 @@ bool CHyprGroupBarDecoration::onMouseButtonOnDeco(const Vector2D& pos, const IPo const auto STACKPAD = *PSTACKED && (BARRELATIVEY - (m_barHeight + *POUTERGAP) * WINDOWINDEX < *POUTERGAP); if (TABPAD || STACKPAD) { if (!g_pCompositor->isWindowActive(m_window.lock())) - Desktop::focusState()->rawWindowFocus(m_window.lock()); + Desktop::focusState()->rawWindowFocus(m_window.lock(), Desktop::FOCUS_REASON_CLICK); return true; } - PHLWINDOW pWindow = m_window->getGroupWindowByIndex(WINDOWINDEX); + PHLWINDOW pWindow = m_window->m_group->fromIndex(WINDOWINDEX); if (pWindow != m_window) - pWindow->setGroupCurrent(pWindow); + pWindow->m_group->setCurrent(pWindow); if (!g_pCompositor->isWindowActive(pWindow) && *PFOLLOWMOUSE != 3) - Desktop::focusState()->rawWindowFocus(pWindow); + Desktop::focusState()->rawWindowFocus(pWindow, Desktop::FOCUS_REASON_CLICK); if (pWindow->m_isFloating) g_pCompositor->changeWindowZOrder(pWindow, true); @@ -551,13 +488,13 @@ bool CHyprGroupBarDecoration::onMouseButtonOnDeco(const Vector2D& pos, const IPo bool CHyprGroupBarDecoration::onScrollOnDeco(const Vector2D& pos, const IPointer::SAxisEvent e) { static auto PGROUPBARSCROLLING = CConfigValue("group:groupbar:scrolling"); - if (!*PGROUPBARSCROLLING || m_window->m_groupData.pNextWindow.expired()) + if (!*PGROUPBARSCROLLING || !m_window->m_group) return false; if (e.delta > 0) - m_window->setGroupCurrent(m_window->m_groupData.pNextWindow.lock()); + m_window->m_group->moveCurrent(true); else - m_window->setGroupCurrent(m_window->getGroupPrevious()); + m_window->m_group->moveCurrent(false); return true; } diff --git a/src/render/decorations/CHyprGroupBarDecoration.hpp b/src/render/decorations/CHyprGroupBarDecoration.hpp index 3e5d3c2d..5c3f4ae5 100644 --- a/src/render/decorations/CHyprGroupBarDecoration.hpp +++ b/src/render/decorations/CHyprGroupBarDecoration.hpp @@ -12,10 +12,10 @@ class CTitleTex { CTitleTex(PHLWINDOW pWindow, const Vector2D& bufferSize, const float monitorScale); ~CTitleTex() = default; - SP m_texActive; - SP m_texInactive; - SP m_texLockedActive; - SP m_texLockedInactive; + SP m_texActive; + SP m_texInactive; + SP m_texLockedActive; + SP m_texLockedInactive; std::string m_content; PHLWINDOWREF m_windowOwner; diff --git a/src/render/decorations/DecorationPositioner.cpp b/src/render/decorations/DecorationPositioner.cpp index aa849bab..470b5bb7 100644 --- a/src/render/decorations/DecorationPositioner.cpp +++ b/src/render/decorations/DecorationPositioner.cpp @@ -1,18 +1,11 @@ #include "DecorationPositioner.hpp" #include "../../desktop/view/Window.hpp" -#include "../../managers/HookSystemManager.hpp" -#include "../../managers/LayoutManager.hpp" +#include "../../layout/target/Target.hpp" +#include "../../event/EventBus.hpp" CDecorationPositioner::CDecorationPositioner() { - static auto P = g_pHookSystem->hookDynamic("closeWindow", [this](void* call, SCallbackInfo& info, std::any data) { - auto PWINDOW = std::any_cast(data); - this->onWindowUnmap(PWINDOW); - }); - - static auto P2 = g_pHookSystem->hookDynamic("openWindow", [this](void* call, SCallbackInfo& info, std::any data) { - auto PWINDOW = std::any_cast(data); - this->onWindowMap(PWINDOW); - }); + static auto P = Event::bus()->m_events.window.close.listen([this](PHLWINDOW window) { onWindowUnmap(window); }); + static auto P2 = Event::bus()->m_events.window.open.listen([this](PHLWINDOW window) { onWindowMap(window); }); } Vector2D CDecorationPositioner::getEdgeDefinedPoint(uint32_t edges, PHLWINDOWREF pWindow) { @@ -214,29 +207,23 @@ void CDecorationPositioner::onWindowUpdate(PHLWINDOW pWindow) { continue; } - auto desiredSize = 0; - if (LEFT) - desiredSize = wd->positioningInfo.desiredExtents.topLeft.x; - else if (RIGHT) - desiredSize = wd->positioningInfo.desiredExtents.bottomRight.x; - else if (TOP) - desiredSize = wd->positioningInfo.desiredExtents.topLeft.y; - else - desiredSize = wd->positioningInfo.desiredExtents.bottomRight.y; + const auto desiredExtents = wd->positioningInfo.desiredExtents; const auto EDGEPOINT = getEdgeDefinedPoint(wd->positioningInfo.edges, pWindow); Vector2D pos, size; if (EDGESNO == 4) { - pos = wb.pos() - EDGEPOINT - Vector2D{stickyOffsetXL + desiredSize, stickyOffsetYT + desiredSize}; - size = wb.size() + Vector2D{stickyOffsetXL + stickyOffsetXR + desiredSize * 2, stickyOffsetYB + stickyOffsetYT + desiredSize * 2}; + stickyOffsetXL += desiredExtents.topLeft.x; + stickyOffsetXR += desiredExtents.bottomRight.x; + stickyOffsetYT += desiredExtents.topLeft.y; + stickyOffsetYB += desiredExtents.bottomRight.y; - stickyOffsetXL += desiredSize; - stickyOffsetXR += desiredSize; - stickyOffsetYT += desiredSize; - stickyOffsetYB += desiredSize; + pos = wb.pos() - EDGEPOINT - Vector2D{stickyOffsetXL, stickyOffsetYT}; + size = wb.size() + Vector2D{stickyOffsetXL + stickyOffsetXR, stickyOffsetYB + stickyOffsetYT}; } else if (LEFT) { + const auto desiredSize = desiredExtents.topLeft.x; + pos = wb.pos() - EDGEPOINT - Vector2D{stickyOffsetXL, -stickyOffsetYT}; pos.x -= desiredSize; size = {sc(desiredSize), wb.size().y + stickyOffsetYB + stickyOffsetYT}; @@ -244,12 +231,16 @@ void CDecorationPositioner::onWindowUpdate(PHLWINDOW pWindow) { if (SOLID) stickyOffsetXL += desiredSize; } else if (RIGHT) { + const auto desiredSize = desiredExtents.bottomRight.x; + pos = wb.pos() + Vector2D{wb.size().x, 0.0} - EDGEPOINT + Vector2D{stickyOffsetXR, -stickyOffsetYT}; size = {sc(desiredSize), wb.size().y + stickyOffsetYB + stickyOffsetYT}; if (SOLID) stickyOffsetXR += desiredSize; } else if (TOP) { + const auto desiredSize = desiredExtents.topLeft.y; + pos = wb.pos() - EDGEPOINT - Vector2D{stickyOffsetXL, stickyOffsetYT}; pos.y -= desiredSize; size = {wb.size().x + stickyOffsetXL + stickyOffsetXR, sc(desiredSize)}; @@ -257,6 +248,8 @@ void CDecorationPositioner::onWindowUpdate(PHLWINDOW pWindow) { if (SOLID) stickyOffsetYT += desiredSize; } else { + const auto desiredSize = desiredExtents.bottomRight.y; + pos = wb.pos() + Vector2D{0.0, wb.size().y} - EDGEPOINT - Vector2D{stickyOffsetXL, stickyOffsetYB}; size = {wb.size().x + stickyOffsetXL + stickyOffsetXR, sc(desiredSize)}; @@ -278,7 +271,7 @@ void CDecorationPositioner::onWindowUpdate(PHLWINDOW pWindow) { if (WINDOWDATA->extents != SBoxExtents{{stickyOffsetXL + reservedXL, stickyOffsetYT + reservedYT}, {stickyOffsetXR + reservedXR, stickyOffsetYB + reservedYB}}) { WINDOWDATA->extents = {{stickyOffsetXL + reservedXL, stickyOffsetYT + reservedYT}, {stickyOffsetXR + reservedXR, stickyOffsetYB + reservedYB}}; - g_pLayoutManager->getCurrentLayout()->recalculateWindow(pWindow); + pWindow->layoutTarget()->recalc(); } } diff --git a/src/render/gl/GLFramebuffer.cpp b/src/render/gl/GLFramebuffer.cpp new file mode 100644 index 00000000..d821f766 --- /dev/null +++ b/src/render/gl/GLFramebuffer.cpp @@ -0,0 +1,170 @@ +#include "GLFramebuffer.hpp" +#include "../OpenGL.hpp" +#include "../Renderer.hpp" +#include "macros.hpp" +#include "render/Framebuffer.hpp" + +CGLFramebuffer::CGLFramebuffer() : IFramebuffer() {} +CGLFramebuffer::CGLFramebuffer(const std::string& name) : IFramebuffer(name) {} + +bool CGLFramebuffer::internalAlloc(int w, int h, uint32_t drmFormat) { + g_pHyprRenderer->makeEGLCurrent(); + + bool firstAlloc = false; + + if (!m_tex) { + m_tex = g_pHyprRenderer->createTexture(); + m_tex->allocate({w, h}); + m_tex->bind(); + m_tex->setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + m_tex->setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + m_tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR); + m_tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); + firstAlloc = true; + } + + if (!m_fbAllocated) { + glGenFramebuffers(1, &m_fb); + m_fbAllocated = true; + firstAlloc = true; + } + + if (firstAlloc) { + const auto format = NFormatUtils::getPixelFormatFromDRM(drmFormat); + m_tex->bind(); + glTexImage2D(GL_TEXTURE_2D, 0, format->glInternalFormat ? format->glInternalFormat : format->glFormat, w, h, 0, format->glFormat, format->glType, nullptr); + glBindFramebuffer(GL_FRAMEBUFFER, m_fb); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_tex->m_texID, 0); + + if (m_stencilTex && m_stencilTex->ok()) { + m_stencilTex->bind(); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, w, h, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_stencilTex->m_texID, 0); + + glDisable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + } + + auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + RASSERT((status == GL_FRAMEBUFFER_COMPLETE), "Framebuffer incomplete, couldn't create! (FB status: {}, GL Error: 0x{:x})", status, sc(glGetError())); + + if (m_stencilTex && m_stencilTex->ok()) + m_stencilTex->unbind(); + + Log::logger->log(Log::DEBUG, "Framebuffer created, status {}", status); + } + + glBindTexture(GL_TEXTURE_2D, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + return true; +} + +void CGLFramebuffer::addStencil(SP tex) { + if (m_stencilTex == tex) + return; + + RASSERT(!m_fbAllocated, "Should add stencil tex prior to FB allocation") + m_stencilTex = tex; +} + +void CGLFramebuffer::bind() { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fb); + + if (g_pHyprOpenGL) + g_pHyprOpenGL->setViewport(0, 0, g_pHyprOpenGL->m_renderData.pMonitor->m_pixelSize.x, g_pHyprOpenGL->m_renderData.pMonitor->m_pixelSize.y); + else + glViewport(0, 0, m_size.x, m_size.y); +} + +void CGLFramebuffer::unbind() { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); +} + +void CGLFramebuffer::release() { + if (m_fbAllocated) { + glBindFramebuffer(GL_FRAMEBUFFER, m_fb); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + glDeleteFramebuffers(1, &m_fb); + m_fbAllocated = false; + m_fb = 0; + } + + if (m_tex) + m_tex.reset(); + + m_size = Vector2D(); +} + +bool CGLFramebuffer::readPixels(CHLBufferReference buffer, uint32_t offsetX, uint32_t offsetY, uint32_t width, uint32_t height) { + auto shm = buffer->shm(); + auto [pixelData, fmt, bufLen] = buffer->beginDataPtr(0); // no need for end, cuz it's shm + + const auto PFORMAT = NFormatUtils::getPixelFormatFromDRM(shm.format); + if (!PFORMAT) { + LOGM(Log::ERR, "Can't copy: failed to find a pixel format"); + return false; + } + + g_pHyprRenderer->makeEGLCurrent(); + glBindFramebuffer(GL_READ_FRAMEBUFFER, getFBID()); + bind(); + + glPixelStorei(GL_PACK_ALIGNMENT, 1); + + uint32_t packStride = NFormatUtils::minStride(PFORMAT, m_size.x); + int glFormat = PFORMAT->glFormat; + + if (glFormat == GL_RGBA) + glFormat = GL_BGRA_EXT; + + if (glFormat != GL_BGRA_EXT && glFormat != GL_RGB) { + if (PFORMAT->swizzle.has_value()) { + std::array RGBA = SWIZZLE_RGBA; + std::array BGRA = SWIZZLE_BGRA; + if (PFORMAT->swizzle == RGBA) + glFormat = GL_RGBA; + else if (PFORMAT->swizzle == BGRA) + glFormat = GL_BGRA_EXT; + else { + LOGM(Log::ERR, "Copied frame via shm might be broken or color flipped"); + glFormat = GL_RGBA; + } + } + } + + // This could be optimized by using a pixel buffer object to make this async, + // but really clients should just use a dma buffer anyways. + if (packStride == sc(shm.stride)) { + glReadPixels(offsetX, offsetY, width > 0 ? width : m_size.x, height > 0 ? height : m_size.y, glFormat, PFORMAT->glType, pixelData); + } else { + const auto h = height > 0 ? height : m_size.y; + for (size_t i = 0; i < h; ++i) { + uint32_t y = i; + glReadPixels(offsetX, offsetY + y, width > 0 ? width : m_size.x, 1, glFormat, PFORMAT->glType, pixelData + i * shm.stride); + } + } + + unbind(); + glPixelStorei(GL_PACK_ALIGNMENT, 4); + + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + return true; +} + +CGLFramebuffer::~CGLFramebuffer() { + release(); +} + +GLuint CGLFramebuffer::getFBID() { + return m_fbAllocated ? m_fb : 0; +} + +void CGLFramebuffer::invalidate(const std::vector& attachments) { + if (!isAllocated()) + return; + + glInvalidateFramebuffer(GL_FRAMEBUFFER, attachments.size(), attachments.data()); +} diff --git a/src/render/gl/GLFramebuffer.hpp b/src/render/gl/GLFramebuffer.hpp new file mode 100644 index 00000000..c171444e --- /dev/null +++ b/src/render/gl/GLFramebuffer.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "../../defines.hpp" +#include "../Texture.hpp" +#include "../Framebuffer.hpp" +#include + +class CGLFramebuffer : public IFramebuffer { + public: + CGLFramebuffer(); + CGLFramebuffer(const std::string& name); + ~CGLFramebuffer(); + + void addStencil(SP tex) override; + void release() override; + bool readPixels(CHLBufferReference buffer, uint32_t offsetX = 0, uint32_t offsetY = 0, uint32_t width = 0, uint32_t height = 0) override; + + void bind() override; + void unbind(); + GLuint getFBID(); + void invalidate(const std::vector& attachments); + + protected: + bool internalAlloc(int w, int h, uint32_t format = DRM_FORMAT_ARGB8888) override; + + private: + GLuint m_fb = -1; + + friend class CGLRenderbuffer; +}; diff --git a/src/render/gl/GLRenderbuffer.cpp b/src/render/gl/GLRenderbuffer.cpp new file mode 100644 index 00000000..8299d0e4 --- /dev/null +++ b/src/render/gl/GLRenderbuffer.cpp @@ -0,0 +1,71 @@ +#include "GLRenderbuffer.hpp" +#include "../Renderer.hpp" +#include "../OpenGL.hpp" +#include "../../Compositor.hpp" +#include "../Framebuffer.hpp" +#include "GLFramebuffer.hpp" +#include "render/Renderbuffer.hpp" +#include +#include +#include + +#include + +CGLRenderbuffer::~CGLRenderbuffer() { + if (!g_pCompositor || g_pCompositor->m_isShuttingDown || !g_pHyprRenderer) + return; + + g_pHyprRenderer->makeEGLCurrent(); + + unbind(); + m_framebuffer->release(); + + if (m_rbo) + glDeleteRenderbuffers(1, &m_rbo); + + if (m_image != EGL_NO_IMAGE_KHR) + g_pHyprOpenGL->m_proc.eglDestroyImageKHR(g_pHyprOpenGL->m_eglDisplay, m_image); +} + +CGLRenderbuffer::CGLRenderbuffer(SP buffer, uint32_t format) : IRenderbuffer(buffer, format) { + auto dma = buffer->dmabuf(); + + m_image = g_pHyprOpenGL->createEGLImage(dma); + if (m_image == EGL_NO_IMAGE_KHR) { + Log::logger->log(Log::ERR, "rb: createEGLImage failed"); + return; + } + + glGenRenderbuffers(1, &m_rbo); + glBindRenderbuffer(GL_RENDERBUFFER, m_rbo); + g_pHyprOpenGL->m_proc.glEGLImageTargetRenderbufferStorageOES(GL_RENDERBUFFER, m_image); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + + m_framebuffer = makeShared(); + glGenFramebuffers(1, &GLFB(m_framebuffer)->m_fb); + GLFB(m_framebuffer)->m_fbAllocated = true; + m_framebuffer->m_size = buffer->size; + m_framebuffer->m_drmFormat = dma.format; + m_framebuffer->bind(); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_rbo); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + Log::logger->log(Log::ERR, "rbo: glCheckFramebufferStatus failed"); + return; + } + + GLFB(m_framebuffer)->unbind(); + + m_listeners.destroyBuffer = buffer->events.destroy.listen([this] { g_pHyprRenderer->onRenderbufferDestroy(this); }); + + m_good = true; +} + +void CGLRenderbuffer::bind() { + g_pHyprRenderer->makeEGLCurrent(); + m_framebuffer->bind(); +} + +void CGLRenderbuffer::unbind() { + GLFB(m_framebuffer)->unbind(); +} diff --git a/src/render/gl/GLRenderbuffer.hpp b/src/render/gl/GLRenderbuffer.hpp new file mode 100644 index 00000000..8367f702 --- /dev/null +++ b/src/render/gl/GLRenderbuffer.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "../../helpers/memory/Memory.hpp" +#include "../Renderbuffer.hpp" +#include + +class CMonitor; + +class CGLRenderbuffer : public IRenderbuffer { + public: + CGLRenderbuffer(SP buffer, uint32_t format); + ~CGLRenderbuffer(); + + void bind() override; + void unbind() override; + + private: + void* m_image = nullptr; + GLuint m_rbo = 0; +}; diff --git a/src/render/gl/GLTexture.cpp b/src/render/gl/GLTexture.cpp new file mode 100644 index 00000000..6a1fb172 --- /dev/null +++ b/src/render/gl/GLTexture.cpp @@ -0,0 +1,223 @@ +#include "GLTexture.hpp" +#include "../Renderer.hpp" +#include "../../Compositor.hpp" +#include "../../helpers/Format.hpp" +#include "render/Texture.hpp" +#include + +CGLTexture::CGLTexture(bool opaque) { + m_opaque = opaque; +} + +CGLTexture::~CGLTexture() { + if (!g_pCompositor || g_pCompositor->m_isShuttingDown || !g_pHyprRenderer) + return; + + g_pHyprRenderer->makeEGLCurrent(); + if (m_texID) { + GLCALL(glDeleteTextures(1, &m_texID)); + m_texID = 0; + } + + if (m_eglImage) + g_pHyprOpenGL->m_proc.eglDestroyImageKHR(g_pHyprOpenGL->m_eglDisplay, m_eglImage); + m_eglImage = nullptr; + m_cachedStates.fill(std::nullopt); +} + +CGLTexture::CGLTexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size_, bool keepDataCopy, bool opaque) : + ITexture(drmFormat, pixels, stride, size_, keepDataCopy, opaque) { + + g_pHyprRenderer->makeEGLCurrent(); + + const auto format = NFormatUtils::getPixelFormatFromDRM(drmFormat); + ASSERT(format); + + m_type = format->withAlpha ? TEXTURE_RGBA : TEXTURE_RGBX; + m_size = size_; + m_isSynchronous = true; + m_target = GL_TEXTURE_2D; + allocate(size_); + bind(); + setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + if (format->swizzle.has_value()) + swizzle(format->swizzle.value()); + + bool alignmentChanged = false; + if (format->bytesPerBlock != 4) { + const GLint alignment = (stride % 4 == 0) ? 4 : 1; + GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, alignment)); + alignmentChanged = true; + } + + GLCALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / format->bytesPerBlock)); + GLCALL(glTexImage2D(GL_TEXTURE_2D, 0, format->glInternalFormat ? format->glInternalFormat : format->glFormat, size_.x, size_.y, 0, format->glFormat, format->glType, pixels)); + GLCALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0)); + if (alignmentChanged) + GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 4)); + + unbind(); +} + +CGLTexture::CGLTexture(const Aquamarine::SDMABUFAttrs& attrs, void* image, bool opaque) { + m_opaque = opaque; + if (!g_pHyprOpenGL->m_proc.glEGLImageTargetTexture2DOES) { + Log::logger->log(Log::ERR, "Cannot create a dmabuf texture: no glEGLImageTargetTexture2DOES"); + return; + } + + m_opaque = NFormatUtils::isFormatOpaque(attrs.format); + + // #TODO external only formats should be external aswell. + // also needs a seperate color shader. + /*if (NFormatUtils::isFormatYUV(attrs.format)) { + m_target = GL_TEXTURE_EXTERNAL_OES; + m_type = TEXTURE_EXTERNAL; + } else {*/ + m_target = GL_TEXTURE_2D; + m_type = NFormatUtils::isFormatOpaque(attrs.format) ? TEXTURE_RGBX : TEXTURE_RGBA; + //} + + allocate(attrs.size); + m_eglImage = image; + + bind(); + setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + GLCALL(g_pHyprOpenGL->m_proc.glEGLImageTargetTexture2DOES(m_target, image)); + unbind(); +} + +CGLTexture::CGLTexture(std::span lut3D, size_t N) : ITexture(lut3D, N), m_target(GL_TEXTURE_3D) { + allocate({}); + bind(); + + GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); + setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); + setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR); + setTexParameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + setTexParameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + setTexParameter(GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + + // Expand RGB->RGBA on upload (alpha=1) + std::vector rgba; + rgba.resize(N * N * N * 4); + for (size_t i = 0, j = 0; i < N * N * N; ++i, j += 3) { + rgba[i * 4 + 0] = lut3D[j + 0]; + rgba[i * 4 + 1] = lut3D[j + 1]; + rgba[i * 4 + 2] = lut3D[j + 2]; + rgba[i * 4 + 3] = 1.F; + } + + GLCALL(glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA16F, N, N, N, 0, GL_RGBA, GL_FLOAT, rgba.data())); + + unbind(); +} + +void CGLTexture::update(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const CRegion& damage) { + if (damage.empty()) + return; + + g_pHyprRenderer->makeEGLCurrent(); + + const auto format = NFormatUtils::getPixelFormatFromDRM(drmFormat); + ASSERT(format); + + bind(); + + if (format->swizzle.has_value()) + swizzle(format->swizzle.value()); + + bool alignmentChanged = false; + if (format->bytesPerBlock != 4) { + const GLint alignment = (stride % 4 == 0) ? 4 : 1; + GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, alignment)); + alignmentChanged = true; + } + + GLCALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, stride / format->bytesPerBlock)); + + damage.copy().intersect(CBox{{}, m_size}).forEachRect([&format, &pixels](const auto& rect) { + GLCALL(glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, rect.x1)); + GLCALL(glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, rect.y1)); + + int width = rect.x2 - rect.x1; + int height = rect.y2 - rect.y1; + GLCALL(glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x1, rect.y1, width, height, format->glFormat, format->glType, pixels)); + }); + + if (alignmentChanged) + GLCALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 4)); + + GLCALL(glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT, 0)); + GLCALL(glPixelStorei(GL_UNPACK_SKIP_PIXELS_EXT, 0)); + GLCALL(glPixelStorei(GL_UNPACK_SKIP_ROWS_EXT, 0)); + + unbind(); + + if (m_keepDataCopy) { + m_dataCopy.resize(stride * m_size.y); + memcpy(m_dataCopy.data(), pixels, stride * m_size.y); + } +} + +void CGLTexture::allocate(const Vector2D& size, uint32_t drmFormat) { + if (!m_texID) + GLCALL(glGenTextures(1, &m_texID)); + m_size = size; + m_drmFormat = drmFormat; +} + +void CGLTexture::bind() { + GLCALL(glBindTexture(m_target, m_texID)); +} + +void CGLTexture::unbind() { + GLCALL(glBindTexture(m_target, 0)); +} + +bool CGLTexture::ok() { + return m_texID > 0; +} + +bool CGLTexture::isDMA() { + return m_eglImage; +} + +constexpr std::optional CGLTexture::getCacheStateIndex(GLenum pname) { + switch (pname) { + case GL_TEXTURE_WRAP_S: return TEXTURE_PAR_WRAP_S; + case GL_TEXTURE_WRAP_T: return TEXTURE_PAR_WRAP_T; + case GL_TEXTURE_MAG_FILTER: return TEXTURE_PAR_MAG_FILTER; + case GL_TEXTURE_MIN_FILTER: return TEXTURE_PAR_MIN_FILTER; + case GL_TEXTURE_SWIZZLE_R: return TEXTURE_PAR_SWIZZLE_R; + case GL_TEXTURE_SWIZZLE_B: return TEXTURE_PAR_SWIZZLE_B; + default: return std::nullopt; + } +} + +void CGLTexture::setTexParameter(GLenum pname, GLint param) { + const auto cacheIndex = getCacheStateIndex(pname); + + if (!cacheIndex) { + GLCALL(glTexParameteri(m_target, pname, param)); + return; + } + + const auto idx = cacheIndex.value(); + + if (m_cachedStates[idx] == param) + return; + + m_cachedStates[idx] = param; + GLCALL(glTexParameteri(m_target, pname, param)); +} + +void CGLTexture::swizzle(const std::array& colors) { + setTexParameter(GL_TEXTURE_SWIZZLE_R, colors.at(0)); + setTexParameter(GL_TEXTURE_SWIZZLE_G, colors.at(1)); + setTexParameter(GL_TEXTURE_SWIZZLE_B, colors.at(2)); + setTexParameter(GL_TEXTURE_SWIZZLE_A, colors.at(3)); +} diff --git a/src/render/gl/GLTexture.hpp b/src/render/gl/GLTexture.hpp new file mode 100644 index 00000000..34510e90 --- /dev/null +++ b/src/render/gl/GLTexture.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include "../Texture.hpp" +#include +#include + +class CGLTexture : public ITexture { + public: + using ITexture::ITexture; + + CGLTexture(CGLTexture&) = delete; + CGLTexture(CGLTexture&&) = delete; + CGLTexture(const CGLTexture&&) = delete; + CGLTexture(const CGLTexture&) = delete; + + CGLTexture(bool opaque = false); + CGLTexture(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size, bool keepDataCopy = false, bool opaque = false); + CGLTexture(const Aquamarine::SDMABUFAttrs&, void* image, bool opaque = false); + CGLTexture(std::span lut3D, size_t N); + ~CGLTexture(); + + void allocate(const Vector2D& size, uint32_t drmFormat = 0) override; + void update(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const CRegion& damage) override; + void bind() override; + void unbind() override; + void setTexParameter(GLenum pname, GLint param) override; + bool ok() override; + bool isDMA() override; + + private: + void* m_eglImage = nullptr; + + enum eTextureParam : uint8_t { + TEXTURE_PAR_WRAP_S = 0, + TEXTURE_PAR_WRAP_T, + TEXTURE_PAR_MAG_FILTER, + TEXTURE_PAR_MIN_FILTER, + TEXTURE_PAR_SWIZZLE_R, + TEXTURE_PAR_SWIZZLE_B, + TEXTURE_PAR_LAST, + }; + + GLenum m_target = GL_TEXTURE_2D; + + void swizzle(const std::array& colors); + constexpr std::optional getCacheStateIndex(GLenum pname); + + std::array, TEXTURE_PAR_LAST> m_cachedStates; +}; diff --git a/src/render/pass/FramebufferElement.cpp b/src/render/pass/FramebufferElement.cpp index 77a29fba..bc7c686a 100644 --- a/src/render/pass/FramebufferElement.cpp +++ b/src/render/pass/FramebufferElement.cpp @@ -6,7 +6,7 @@ CFramebufferElement::CFramebufferElement(const CFramebufferElement::SFramebuffer } void CFramebufferElement::draw(const CRegion& damage) { - CFramebuffer* fb = nullptr; + SP fb = nullptr; if (m_data.main) { switch (m_data.framebufferID) { @@ -22,12 +22,12 @@ void CFramebufferElement::draw(const CRegion& damage) { } else { switch (m_data.framebufferID) { - case FB_MONITOR_RENDER_EXTRA_OFFLOAD: fb = &g_pHyprOpenGL->m_renderData.pCurrentMonData->offloadFB; break; - case FB_MONITOR_RENDER_EXTRA_MIRROR: fb = &g_pHyprOpenGL->m_renderData.pCurrentMonData->mirrorFB; break; - case FB_MONITOR_RENDER_EXTRA_MIRROR_SWAP: fb = &g_pHyprOpenGL->m_renderData.pCurrentMonData->mirrorSwapFB; break; - case FB_MONITOR_RENDER_EXTRA_OFF_MAIN: fb = &g_pHyprOpenGL->m_renderData.pCurrentMonData->offMainFB; break; - case FB_MONITOR_RENDER_EXTRA_MONITOR_MIRROR: fb = &g_pHyprOpenGL->m_renderData.pCurrentMonData->monitorMirrorFB; break; - case FB_MONITOR_RENDER_EXTRA_BLUR: fb = &g_pHyprOpenGL->m_renderData.pCurrentMonData->blurFB; break; + case FB_MONITOR_RENDER_EXTRA_OFFLOAD: fb = g_pHyprOpenGL->m_renderData.pCurrentMonData->offloadFB; break; + case FB_MONITOR_RENDER_EXTRA_MIRROR: fb = g_pHyprOpenGL->m_renderData.pCurrentMonData->mirrorFB; break; + case FB_MONITOR_RENDER_EXTRA_MIRROR_SWAP: fb = g_pHyprOpenGL->m_renderData.pCurrentMonData->mirrorSwapFB; break; + case FB_MONITOR_RENDER_EXTRA_OFF_MAIN: fb = g_pHyprOpenGL->m_renderData.pCurrentMonData->offMainFB; break; + case FB_MONITOR_RENDER_EXTRA_MONITOR_MIRROR: fb = g_pHyprOpenGL->m_renderData.pCurrentMonData->monitorMirrorFB; break; + case FB_MONITOR_RENDER_EXTRA_BLUR: fb = g_pHyprOpenGL->m_renderData.pCurrentMonData->blurFB; break; } if (!fb) { diff --git a/src/render/pass/Pass.cpp b/src/render/pass/Pass.cpp index 3910e6a7..a4436516 100644 --- a/src/render/pass/Pass.cpp +++ b/src/render/pass/Pass.cpp @@ -55,7 +55,16 @@ void CRenderPass::simplify() { auto opaque = el->element->opaqueRegion(); if (!opaque.empty()) { - opaque.scale(g_pHyprOpenGL->m_renderData.pMonitor->m_scale); + // scale and rounding is very particular so we have to use CBoxes scale and round functions + if (opaque.getRects().size() == 1) + opaque = opaque.getExtents().scale(g_pHyprOpenGL->m_renderData.pMonitor->m_scale).round(); + else { + CRegion scaledRegion; + opaque.forEachRect([&scaledRegion](const auto& RECT) { + scaledRegion.add(CBox(RECT.x1, RECT.y1, RECT.x2 - RECT.x1, RECT.y2 - RECT.y1).scale(g_pHyprOpenGL->m_renderData.pMonitor->m_scale).round()); + }); + opaque = scaledRegion; + } // if this intersects the liveBlur region, allow live blur to operate correctly. // do not occlude a border near it. @@ -162,7 +171,7 @@ CRegion CRenderPass::render(const CRegion& damage_) { } else g_pHyprOpenGL->m_renderData.finalDamage = m_damage; - if (std::ranges::any_of(m_passElements, [](const auto& el) { return el->element->disableSimplification(); })) { + if (g_pHyprOpenGL->m_renderData.noSimplify || std::ranges::any_of(m_passElements, [](const auto& el) { return el->element->disableSimplification(); })) { for (auto& el : m_passElements) { el->elementDamage = m_damage; } @@ -207,7 +216,7 @@ void CRenderPass::renderDebugData() { std::unordered_map offsets; // render focus stuff - auto renderHLSurface = [&offsets](SP texture, SP surface, const CHyprColor& color) { + auto renderHLSurface = [&offsets](SP texture, SP surface, const CHyprColor& color) { if (!surface || !texture) return; diff --git a/src/render/pass/Pass.hpp b/src/render/pass/Pass.hpp index 435b5301..b45af88b 100644 --- a/src/render/pass/Pass.hpp +++ b/src/render/pass/Pass.hpp @@ -4,7 +4,7 @@ #include "PassElement.hpp" class CGradientValueData; -class CTexture; +class ITexture; class CRenderPass { public: @@ -36,7 +36,7 @@ class CRenderPass { struct { bool present = false; - SP keyboardFocusText, pointerFocusText, lastWindowText; + SP keyboardFocusText, pointerFocusText, lastWindowText; } m_debugData; friend class CHyprOpenGLImpl; diff --git a/src/render/pass/SurfacePassElement.cpp b/src/render/pass/SurfacePassElement.cpp index d3027290..c5feb8f7 100644 --- a/src/render/pass/SurfacePassElement.cpp +++ b/src/render/pass/SurfacePassElement.cpp @@ -5,6 +5,7 @@ #include "../../protocols/core/Compositor.hpp" #include "../../protocols/DRMSyncobj.hpp" #include "../../managers/input/InputManager.hpp" +#include "../../layout/LayoutManager.hpp" #include "../Renderer.hpp" #include @@ -51,7 +52,7 @@ void CSurfacePassElement::draw(const CRegion& damage) { if (!TEXTURE->m_texID) return; - const auto INTERACTIVERESIZEINPROGRESS = m_data.pWindow && g_pInputManager->m_currentlyDraggedWindow && g_pInputManager->m_dragMode == MBIND_RESIZE; + const auto INTERACTIVERESIZEINPROGRESS = m_data.pWindow && g_layoutManager->dragController()->target() && g_layoutManager->dragController()->mode() == MBIND_RESIZE; TRACY_GPU_ZONE("RenderSurface"); auto PSURFACE = Desktop::View::CWLSurface::fromResource(m_data.surface); @@ -103,7 +104,7 @@ void CSurfacePassElement::draw(const CRegion& damage) { } const bool WINDOWOPAQUE = m_data.pWindow && m_data.pWindow->wlSurface()->resource() == m_data.surface ? m_data.pWindow->opaque() : false; - const bool CANDISABLEBLEND = ALPHA >= 1.f && OVERALL_ALPHA >= 1.f && rounding == 0 && WINDOWOPAQUE; + const bool CANDISABLEBLEND = ALPHA >= 1.f && OVERALL_ALPHA >= 1.f && rounding <= 0 && WINDOWOPAQUE; if (CANDISABLEBLEND) g_pHyprOpenGL->blend(false); @@ -163,7 +164,7 @@ void CSurfacePassElement::draw(const CRegion& damage) { CBox CSurfacePassElement::getTexBox() { const double outputX = -m_data.pMonitor->m_position.x, outputY = -m_data.pMonitor->m_position.y; - const auto INTERACTIVERESIZEINPROGRESS = m_data.pWindow && g_pInputManager->m_currentlyDraggedWindow && g_pInputManager->m_dragMode == MBIND_RESIZE; + const auto INTERACTIVERESIZEINPROGRESS = m_data.pWindow && g_layoutManager->dragController()->target() && g_layoutManager->dragController()->mode() == MBIND_RESIZE; auto PSURFACE = Desktop::View::CWLSurface::fromResource(m_data.surface); CBox windowBox; diff --git a/src/render/pass/SurfacePassElement.hpp b/src/render/pass/SurfacePassElement.hpp index f4dbb45a..058744de 100644 --- a/src/render/pass/SurfacePassElement.hpp +++ b/src/render/pass/SurfacePassElement.hpp @@ -4,7 +4,7 @@ #include "../../helpers/time/Time.hpp" class CWLSurfaceResource; -class CTexture; +class ITexture; class CSyncTimeline; class CSurfacePassElement : public IPassElement { @@ -16,7 +16,7 @@ class CSurfacePassElement : public IPassElement { void* data = nullptr; SP surface = nullptr; - SP texture = nullptr; + SP texture = nullptr; bool mainSurface = true; double w = 0, h = 0; int rounding = 0; diff --git a/src/render/pass/TexPassElement.hpp b/src/render/pass/TexPassElement.hpp index a922843d..770e8b05 100644 --- a/src/render/pass/TexPassElement.hpp +++ b/src/render/pass/TexPassElement.hpp @@ -3,13 +3,13 @@ #include class CWLSurfaceResource; -class CTexture; +class ITexture; class CSyncTimeline; class CTexPassElement : public IPassElement { public: struct SRenderData { - SP tex; + SP tex; CBox box; float a = 1.F; float blurA = 1.F; diff --git a/src/render/pass/TextureMatteElement.cpp b/src/render/pass/TextureMatteElement.cpp index aeeeabc6..8023df8b 100644 --- a/src/render/pass/TextureMatteElement.cpp +++ b/src/render/pass/TextureMatteElement.cpp @@ -9,11 +9,11 @@ void CTextureMatteElement::draw(const CRegion& damage) { if (m_data.disableTransformAndModify) { g_pHyprOpenGL->pushMonitorTransformEnabled(true); g_pHyprOpenGL->setRenderModifEnabled(false); - g_pHyprOpenGL->renderTextureMatte(m_data.tex, m_data.box, *m_data.fb); + g_pHyprOpenGL->renderTextureMatte(m_data.tex, m_data.box, m_data.fb); g_pHyprOpenGL->setRenderModifEnabled(true); g_pHyprOpenGL->popMonitorTransformEnabled(); } else - g_pHyprOpenGL->renderTextureMatte(m_data.tex, m_data.box, *m_data.fb); + g_pHyprOpenGL->renderTextureMatte(m_data.tex, m_data.box, m_data.fb); } bool CTextureMatteElement::needsLiveBlur() { diff --git a/src/render/pass/TextureMatteElement.hpp b/src/render/pass/TextureMatteElement.hpp index 57d0e1e3..273c6474 100644 --- a/src/render/pass/TextureMatteElement.hpp +++ b/src/render/pass/TextureMatteElement.hpp @@ -2,14 +2,14 @@ #include "PassElement.hpp" #include "../Framebuffer.hpp" -class CTexture; +class ITexture; class CTextureMatteElement : public IPassElement { public: struct STextureMatteData { CBox box; - SP tex; - SP fb; + SP tex; + SP fb; bool disableTransformAndModify = false; }; diff --git a/src/render/shaders/glsl/CM.frag b/src/render/shaders/glsl/CM.frag deleted file mode 100644 index 031fe7f3..00000000 --- a/src/render/shaders/glsl/CM.frag +++ /dev/null @@ -1,54 +0,0 @@ -#version 300 es -//#extension GL_OES_EGL_image_external : require -#extension GL_ARB_shading_language_include : enable - -precision highp float; -in vec2 v_texcoord; -uniform sampler2D tex; -//uniform samplerExternalOES texture0; - -uniform int texType; // eTextureType: 0 - rgba, 1 - rgbx, 2 - ext -// uniform int skipCM; -uniform int sourceTF; // eTransferFunction -uniform int targetTF; // eTransferFunction -uniform mat4x2 targetPrimaries; - -uniform float alpha; - -uniform int discardOpaque; -uniform int discardAlpha; -uniform float discardAlphaValue; - -uniform int applyTint; -uniform vec3 tint; - -#include "rounding.glsl" -#include "CM.glsl" - -layout(location = 0) out vec4 fragColor; -void main() { - vec4 pixColor; - if (texType == 1) - pixColor = vec4(texture(tex, v_texcoord).rgb, 1.0); -// else if (texType == 2) -// pixColor = texture(texture0, v_texcoord); - else // assume rgba - pixColor = texture(tex, v_texcoord); - - if (discardOpaque == 1 && pixColor[3] * alpha == 1.0) - discard; - - if (discardAlpha == 1 && pixColor[3] <= discardAlphaValue) - discard; - - // this shader shouldn't be used when skipCM == 1 - pixColor = doColorManagement(pixColor, sourceTF, targetTF, targetPrimaries); - - if (applyTint == 1) - pixColor = vec4(pixColor.rgb * tint.rgb, pixColor[3]); - - if (radius > 0.0) - pixColor = rounding(pixColor); - - fragColor = pixColor * alpha; -} diff --git a/src/render/shaders/glsl/CM.glsl b/src/render/shaders/glsl/CM.glsl index 362d7cfb..323a3008 100644 --- a/src/render/shaders/glsl/CM.glsl +++ b/src/render/shaders/glsl/CM.glsl @@ -1,418 +1,27 @@ -uniform vec2 srcTFRange; -uniform vec2 dstTFRange; +#ifndef ALLOW_INCLUDES +#define ALLOW_INCLUDES +#extension GL_ARB_shading_language_include : enable +#endif +#include "cm_helpers.glsl" + +uniform vec2 srcTFRange; +uniform vec2 dstTFRange; -uniform float maxLuminance; uniform float srcRefLuminance; -uniform float dstMaxLuminance; -uniform float dstRefLuminance; +uniform mat3 convertMatrix; + +#if USE_ICC +uniform highp sampler3D iccLut3D; +uniform float iccLutSize; +#endif + +#if USE_SDR_MOD uniform float sdrSaturation; uniform float sdrBrightnessMultiplier; -uniform mat3 convertMatrix; +#endif -//enum eTransferFunction -#define CM_TRANSFER_FUNCTION_BT1886 1 -#define CM_TRANSFER_FUNCTION_GAMMA22 2 -#define CM_TRANSFER_FUNCTION_GAMMA28 3 -#define CM_TRANSFER_FUNCTION_ST240 4 -#define CM_TRANSFER_FUNCTION_EXT_LINEAR 5 -#define CM_TRANSFER_FUNCTION_LOG_100 6 -#define CM_TRANSFER_FUNCTION_LOG_316 7 -#define CM_TRANSFER_FUNCTION_XVYCC 8 -#define CM_TRANSFER_FUNCTION_SRGB 9 -#define CM_TRANSFER_FUNCTION_EXT_SRGB 10 -#define CM_TRANSFER_FUNCTION_ST2084_PQ 11 -#define CM_TRANSFER_FUNCTION_ST428 12 -#define CM_TRANSFER_FUNCTION_HLG 13 - -// sRGB constants -#define SRGB_POW 2.4 -#define SRGB_CUT 0.0031308 -#define SRGB_SCALE 12.92 -#define SRGB_ALPHA 1.055 - -#define BT1886_POW (1.0 / 0.45) -#define BT1886_CUT 0.018053968510807 -#define BT1886_SCALE 4.5 -#define BT1886_ALPHA (1.0 + 5.5 * BT1886_CUT) - -// See http://car.france3.mars.free.fr/HD/INA-%2026%20jan%2006/SMPTE%20normes%20et%20confs/s240m.pdf -#define ST240_POW (1.0 / 0.45) -#define ST240_CUT 0.0228 -#define ST240_SCALE 4.0 -#define ST240_ALPHA 1.1115 - -#define ST428_POW 2.6 -#define ST428_SCALE (52.37 / 48.0) - -// PQ constants -#define PQ_M1 0.1593017578125 -#define PQ_M2 78.84375 -#define PQ_INV_M1 (1.0 / PQ_M1) -#define PQ_INV_M2 (1.0 / PQ_M2) -#define PQ_C1 0.8359375 -#define PQ_C2 18.8515625 -#define PQ_C3 18.6875 - -// HLG constants -#define HLG_D_CUT (1.0 / 12.0) -#define HLG_E_CUT 0.5 -#define HLG_A 0.17883277 -#define HLG_B 0.28466892 -#define HLG_C 0.55991073 - -#define SDR_MIN_LUMINANCE 0.2 -#define SDR_MAX_LUMINANCE 80.0 -#define HDR_MIN_LUMINANCE 0.005 -#define HDR_MAX_LUMINANCE 10000.0 -#define HLG_MAX_LUMINANCE 1000.0 - -#define M_E 2.718281828459045 - -vec3 xy2xyz(vec2 xy) { - if (xy.y == 0.0) - return vec3(0.0, 0.0, 0.0); - - return vec3(xy.x / xy.y, 1.0, (1.0 - xy.x - xy.y) / xy.y); -} - -vec4 saturate(vec4 color, mat3 primaries, float saturation) { - if (saturation == 1.0) - return color; - vec3 brightness = vec3(primaries[1][0], primaries[1][1], primaries[1][2]); - float Y = dot(color.rgb, brightness); - return vec4(mix(vec3(Y), color.rgb, saturation), color[3]); -} - -// The primary source for these transfer functions is https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.1361-0-199802-W!!PDF-E.pdf -vec3 tfInvPQ(vec3 color) { - vec3 E = pow(clamp(color.rgb, vec3(0.0), vec3(1.0)), vec3(PQ_INV_M2)); - return pow( - (max(E - PQ_C1, vec3(0.0))) / (PQ_C2 - PQ_C3 * E), - vec3(PQ_INV_M1) - ); -} - -vec3 tfInvHLG(vec3 color) { - bvec3 isLow = lessThanEqual(color.rgb, vec3(HLG_E_CUT)); - vec3 lo = color.rgb * color.rgb / 3.0; - vec3 hi = (exp((color.rgb - HLG_C) / HLG_A) + HLG_B) / 12.0; - return mix(hi, lo, isLow); -} - -// Many transfer functions (including sRGB) follow the same pattern: a linear -// segment for small values and a power function for larger values. The -// following function implements this pattern from which sRGB, BT.1886, and -// others can be derived by plugging in the right constants. -vec3 tfInvLinPow(vec3 color, float gamma, float thres, float scale, float alpha) { - bvec3 isLow = lessThanEqual(color.rgb, vec3(thres * scale)); - vec3 lo = color.rgb / scale; - vec3 hi = pow((color.rgb + alpha - 1.0) / alpha, vec3(gamma)); - return mix(hi, lo, isLow); -} - -vec3 tfInvSRGB(vec3 color) { - return tfInvLinPow(color, SRGB_POW, SRGB_CUT, SRGB_SCALE, SRGB_ALPHA); -} - -vec3 tfInvExtSRGB(vec3 color) { - // EXT sRGB is the sRGB transfer function mirrored around 0. - return sign(color) * tfInvSRGB(abs(color)); -} - -vec3 tfInvBT1886(vec3 color) { - return tfInvLinPow(color, BT1886_POW, BT1886_CUT, BT1886_SCALE, BT1886_ALPHA); -} - -vec3 tfInvXVYCC(vec3 color) { - // The inverse transfer function for XVYCC is the BT1886 transfer function mirrored around 0, - // same as what EXT sRGB is to sRGB. - return sign(color) * tfInvBT1886(abs(color)); -} - -vec3 tfInvST240(vec3 color) { - return tfInvLinPow(color, ST240_POW, ST240_CUT, ST240_SCALE, ST240_ALPHA); -} - -// Forward transfer functions corresponding to the inverse functions above. -vec3 tfPQ(vec3 color) { - vec3 E = pow(clamp(color.rgb, vec3(0.0), vec3(1.0)), vec3(PQ_M1)); - return pow( - (vec3(PQ_C1) + PQ_C2 * E) / (vec3(1.0) + PQ_C3 * E), - vec3(PQ_M2) - ); -} - -vec3 tfHLG(vec3 color) { - bvec3 isLow = lessThanEqual(color.rgb, vec3(HLG_D_CUT)); - vec3 lo = sqrt(max(color.rgb, vec3(0.0)) * 3.0); - vec3 hi = HLG_A * log(max(12.0 * color.rgb - HLG_B, vec3(0.0001))) + HLG_C; - return mix(hi, lo, isLow); -} - -vec3 tfLinPow(vec3 color, float gamma, float thres, float scale, float alpha) { - bvec3 isLow = lessThanEqual(color.rgb, vec3(thres)); - vec3 lo = color.rgb * scale; - vec3 hi = pow(color.rgb, vec3(1.0 / gamma)) * alpha - (alpha - 1.0); - return mix(hi, lo, isLow); -} - -vec3 tfSRGB(vec3 color) { - return tfLinPow(color, SRGB_POW, SRGB_CUT, SRGB_SCALE, SRGB_ALPHA); -} - -vec3 tfExtSRGB(vec3 color) { - // EXT sRGB is the sRGB transfer function mirrored around 0. - return sign(color) * tfSRGB(abs(color)); -} - -vec3 tfBT1886(vec3 color) { - return tfLinPow(color, BT1886_POW, BT1886_CUT, BT1886_SCALE, BT1886_ALPHA); -} - -vec3 tfXVYCC(vec3 color) { - // The transfer function for XVYCC is the BT1886 transfer function mirrored around 0, - // same as what EXT sRGB is to sRGB. - return sign(color) * tfBT1886(abs(color)); -} - -vec3 tfST240(vec3 color) { - return tfLinPow(color, ST240_POW, ST240_CUT, ST240_SCALE, ST240_ALPHA); -} - -vec3 toLinearRGB(vec3 color, int tf) { - switch (tf) { - case CM_TRANSFER_FUNCTION_EXT_LINEAR: - return color; - case CM_TRANSFER_FUNCTION_ST2084_PQ: - return tfInvPQ(color); - case CM_TRANSFER_FUNCTION_GAMMA22: - return pow(max(color, vec3(0.0)), vec3(2.2)); - case CM_TRANSFER_FUNCTION_GAMMA28: - return pow(max(color, vec3(0.0)), vec3(2.8)); - case CM_TRANSFER_FUNCTION_HLG: - return tfInvHLG(color); - case CM_TRANSFER_FUNCTION_EXT_SRGB: - return tfInvExtSRGB(color); - case CM_TRANSFER_FUNCTION_BT1886: - return tfInvBT1886(color); - case CM_TRANSFER_FUNCTION_ST240: - return tfInvST240(color); - case CM_TRANSFER_FUNCTION_LOG_100: - return mix(exp((color - 1.0) * 2.0 * log(10.0)), vec3(0.0), lessThanEqual(color, vec3(0.0))); - case CM_TRANSFER_FUNCTION_LOG_316: - return mix(exp((color - 1.0) * 2.5 * log(10.0)), vec3(0.0), lessThanEqual(color, vec3(0.0))); - case CM_TRANSFER_FUNCTION_XVYCC: - return tfInvXVYCC(color); - case CM_TRANSFER_FUNCTION_ST428: - return pow(max(color, vec3(0.0)), vec3(ST428_POW)) * ST428_SCALE; - case CM_TRANSFER_FUNCTION_SRGB: - default: - return tfInvSRGB(color); - } -} - -vec4 toLinear(vec4 color, int tf) { - if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR) - return color; - - color.rgb /= max(color.a, 0.001); - color.rgb = toLinearRGB(color.rgb, tf); - color.rgb *= color.a; - return color; -} - -vec4 toNit(vec4 color, vec2 range) { - color.rgb = color.rgb * (range[1] - range[0]) + range[0]; - return color; -} - -vec3 fromLinearRGB(vec3 color, int tf) { - switch (tf) { - case CM_TRANSFER_FUNCTION_EXT_LINEAR: - return color; - case CM_TRANSFER_FUNCTION_ST2084_PQ: - return tfPQ(color); - case CM_TRANSFER_FUNCTION_GAMMA22: - return pow(max(color, vec3(0.0)), vec3(1.0 / 2.2)); - case CM_TRANSFER_FUNCTION_GAMMA28: - return pow(max(color, vec3(0.0)), vec3(1.0 / 2.8)); - case CM_TRANSFER_FUNCTION_HLG: - return tfHLG(color); - case CM_TRANSFER_FUNCTION_EXT_SRGB: - return tfExtSRGB(color); - case CM_TRANSFER_FUNCTION_BT1886: - return tfBT1886(color); - case CM_TRANSFER_FUNCTION_ST240: - return tfST240(color); - case CM_TRANSFER_FUNCTION_LOG_100: - return mix(1.0 + log(color) / log(10.0) / 2.0, vec3(0.0), lessThanEqual(color, vec3(0.01))); - case CM_TRANSFER_FUNCTION_LOG_316: - return mix(1.0 + log(color) / log(10.0) / 2.5, vec3(0.0), lessThanEqual(color, vec3(sqrt(10.0) / 1000.0))); - case CM_TRANSFER_FUNCTION_XVYCC: - return tfXVYCC(color); - case CM_TRANSFER_FUNCTION_ST428: - return pow(max(color, vec3(0.0)) / ST428_SCALE, vec3(1.0 / ST428_POW)); - case CM_TRANSFER_FUNCTION_SRGB: - default: - return tfSRGB(color); - } -} - -vec4 fromLinear(vec4 color, int tf) { - if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR) - return color; - - color.rgb /= max(color.a, 0.001); - color.rgb = fromLinearRGB(color.rgb, tf); - color.rgb *= color.a; - return color; -} - -vec4 fromLinearNit(vec4 color, int tf, vec2 range) { - if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR) - color.rgb = color.rgb / SDR_MAX_LUMINANCE; - else { - color.rgb /= max(color.a, 0.001); - color.rgb = (color.rgb - range[0]) / (range[1] - range[0]); - color.rgb = fromLinearRGB(color.rgb, tf); - color.rgb *= color.a; - } - return color; -} - -mat3 primaries2xyz(mat4x2 primaries) { - vec3 r = xy2xyz(primaries[0]); - vec3 g = xy2xyz(primaries[1]); - vec3 b = xy2xyz(primaries[2]); - vec3 w = xy2xyz(primaries[3]); - - mat3 invMat = inverse( - mat3( - r.x, r.y, r.z, - g.x, g.y, g.z, - b.x, b.y, b.z - ) - ); - - vec3 s = invMat * w; - - return mat3( - s.r * r.x, s.r * r.y, s.r * r.z, - s.g * g.x, s.g * g.y, s.g * g.z, - s.b * b.x, s.b * b.y, s.b * b.z - ); -} - - -mat3 adaptWhite(vec2 src, vec2 dst) { - if (src == dst) - return mat3( - 1.0, 0.0, 0.0, - 0.0, 1.0, 0.0, - 0.0, 0.0, 1.0 - ); - - // const vec2 D65 = vec2(0.3127, 0.3290); - const mat3 Bradford = mat3( - 0.8951, 0.2664, -0.1614, - -0.7502, 1.7135, 0.0367, - 0.0389, -0.0685, 1.0296 - ); - mat3 BradfordInv = inverse(Bradford); - vec3 srcXYZ = xy2xyz(src); - vec3 dstXYZ = xy2xyz(dst); - vec3 factors = (Bradford * dstXYZ) / (Bradford * srcXYZ); - - return BradfordInv * mat3( - factors.x, 0.0, 0.0, - 0.0, factors.y, 0.0, - 0.0, 0.0, factors.z - ) * Bradford; -} - -vec4 convertPrimaries(vec4 color, mat3 src, vec2 srcWhite, mat3 dst, vec2 dstWhite) { - mat3 convMat = inverse(dst) * adaptWhite(srcWhite, dstWhite) * src; - return vec4(convMat * color.rgb, color[3]); -} - -const mat3 BT2020toLMS = mat3( - 0.3592, 0.6976, -0.0358, - -0.1922, 1.1004, 0.0755, - 0.0070, 0.0749, 0.8434 -); -//const mat3 LMStoBT2020 = inverse(BT2020toLMS); -const mat3 LMStoBT2020 = mat3( - 2.0701800566956135096, -1.3264568761030210255, 0.20661600684785517081, - 0.36498825003265747974, 0.68046736285223514102, -0.045421753075853231409, - -0.049595542238932107896, -0.049421161186757487412, 1.1879959417328034394 -); - -// const mat3 ICtCpPQ = transpose(mat3( -// 2048.0, 2048.0, 0.0, -// 6610.0, -13613.0, 7003.0, -// 17933.0, -17390.0, -543.0 -// ) / 4096.0); -const mat3 ICtCpPQ = mat3( - 0.5, 1.61376953125, 4.378173828125, - 0.5, -3.323486328125, -4.24560546875, - 0.0, 1.709716796875, -0.132568359375 -); -//const mat3 ICtCpPQInv = inverse(ICtCpPQ); -const mat3 ICtCpPQInv = mat3( - 1.0, 1.0, 1.0, - 0.0086090370379327566, -0.0086090370379327566, 0.560031335710679118, - 0.11102962500302595656, -0.11102962500302595656, -0.32062717498731885185 -); - -// unused for now -// const mat3 ICtCpHLG = transpose(mat3( -// 2048.0, 2048.0, 0.0, -// 3625.0, -7465.0, 3840.0, -// 9500.0, -9212.0, -288.0 -// ) / 4096.0); -// const mat3 ICtCpHLGInv = inverse(ICtCpHLG); - -vec4 tonemap(vec4 color, mat3 dstXYZ) { - if (maxLuminance < dstMaxLuminance * 1.01) - return vec4(clamp(color.rgb, vec3(0.0), vec3(dstMaxLuminance)), color[3]); - - mat3 toLMS = BT2020toLMS * dstXYZ; - mat3 fromLMS = inverse(dstXYZ) * LMStoBT2020; - - vec3 lms = fromLinear(vec4((toLMS * color.rgb) / HDR_MAX_LUMINANCE, 1.0), CM_TRANSFER_FUNCTION_ST2084_PQ).rgb; - vec3 ICtCp = ICtCpPQ * lms; - - float E = pow(clamp(ICtCp[0], 0.0, 1.0), PQ_INV_M2); - float luminance = pow( - (max(E - PQ_C1, 0.0)) / (PQ_C2 - PQ_C3 * E), - PQ_INV_M1 - ) * HDR_MAX_LUMINANCE; - - float linearPart = min(luminance, dstRefLuminance); - float luminanceAboveRef = max(luminance - dstRefLuminance, 0.0); - float maxExcessLuminance = max(maxLuminance - dstRefLuminance, 1.0); - float shoulder = log((luminanceAboveRef / maxExcessLuminance + 1.0) * (M_E - 1.0)); - float mappedHigh = shoulder * (dstMaxLuminance - dstRefLuminance); - float newLum = clamp(linearPart + mappedHigh, 0.0, dstMaxLuminance); - - // scale src to dst reference - float refScale = dstRefLuminance / srcRefLuminance; - - return vec4(fromLMS * toLinear(vec4(ICtCpPQInv * ICtCp, 1.0), CM_TRANSFER_FUNCTION_ST2084_PQ).rgb * HDR_MAX_LUMINANCE * refScale, color[3]); -} - -vec4 doColorManagement(vec4 pixColor, int srcTF, int dstTF, mat4x2 dstPrimaries) { - pixColor.rgb /= max(pixColor.a, 0.001); - pixColor.rgb = toLinearRGB(pixColor.rgb, srcTF); - pixColor.rgb = convertMatrix * pixColor.rgb; - pixColor = toNit(pixColor, srcTFRange); - pixColor.rgb *= pixColor.a; - mat3 dstxyz = primaries2xyz(dstPrimaries); - pixColor = tonemap(pixColor, dstxyz); - pixColor = fromLinearNit(pixColor, dstTF, dstTFRange); - if ((srcTF == CM_TRANSFER_FUNCTION_SRGB || srcTF == CM_TRANSFER_FUNCTION_GAMMA22) && dstTF == CM_TRANSFER_FUNCTION_ST2084_PQ) { - pixColor = saturate(pixColor, dstxyz, sdrSaturation); - pixColor.rgb *= sdrBrightnessMultiplier; - } - return pixColor; -} +#if USE_TONEMAP +uniform float maxLuminance; +uniform float dstMaxLuminance; +uniform float dstRefLuminance; +#endif diff --git a/src/render/shaders/glsl/blur1.frag b/src/render/shaders/glsl/blur1.frag index 796fb42d..044df3cc 100644 --- a/src/render/shaders/glsl/blur1.frag +++ b/src/render/shaders/glsl/blur1.frag @@ -1,143 +1,21 @@ #version 300 es -precision highp float; -uniform sampler2D tex; +#define ALLOW_INCLUDES +#extension GL_ARB_shading_language_include : enable -uniform float radius; -uniform vec2 halfpixel; -uniform int passes; -uniform float vibrancy; -uniform float vibrancy_darkness; +precision highp float; +uniform sampler2D tex; -in vec2 v_texcoord; - -// see http://alienryderflex.com/hsp.html -const float Pr = 0.299; -const float Pg = 0.587; -const float Pb = 0.114; - -// Y is "v" ( brightness ). X is "s" ( saturation ) -// see https://www.desmos.com/3d/a88652b9a4 -// Determines if high brightness or high saturation is more important -const float a = 0.93; -const float b = 0.11; -const float c = 0.66; // Determines the smoothness of the transition of unboosted to boosted colors -// - -// http://www.flong.com/archive/texts/code/shapers_circ/ -float doubleCircleSigmoid(float x, float a) { - a = clamp(a, 0.0, 1.0); - - float y = .0; - if (x <= a) { - y = a - sqrt(a * a - x * x); - } else { - y = a + sqrt(pow(1. - a, 2.) - pow(x - 1., 2.)); - } - return y; -} - -vec3 rgb2hsl(vec3 col) { - float red = col.r; - float green = col.g; - float blue = col.b; - - float minc = min(col.r, min(col.g, col.b)); - float maxc = max(col.r, max(col.g, col.b)); - float delta = maxc - minc; - - float lum = (minc + maxc) * 0.5; - float sat = 0.0; - float hue = 0.0; - - if (lum > 0.0 && lum < 1.0) { - float mul = (lum < 0.5) ? (lum) : (1.0 - lum); - sat = delta / (mul * 2.0); - } - - if (delta > 0.0) { - vec3 maxcVec = vec3(maxc); - vec3 masks = vec3(equal(maxcVec, col)) * vec3(notEqual(maxcVec, vec3(green, blue, red))); - vec3 adds = vec3(0.0, 2.0, 4.0) + vec3(green - blue, blue - red, red - green) / delta; - - hue += dot(adds, masks); - hue /= 6.0; - - if (hue < 0.0) - hue += 1.0; - } - - return vec3(hue, sat, lum); -} - -vec3 hsl2rgb(vec3 col) { - const float onethird = 1.0 / 3.0; - const float twothird = 2.0 / 3.0; - const float rcpsixth = 6.0; - - float hue = col.x; - float sat = col.y; - float lum = col.z; - - vec3 xt = vec3(0.0); - - if (hue < onethird) { - xt.r = rcpsixth * (onethird - hue); - xt.g = rcpsixth * hue; - xt.b = 0.0; - } else if (hue < twothird) { - xt.r = 0.0; - xt.g = rcpsixth * (twothird - hue); - xt.b = rcpsixth * (hue - onethird); - } else - xt = vec3(rcpsixth * (hue - twothird), 0.0, rcpsixth * (1.0 - hue)); - - xt = min(xt, 1.0); - - float sat2 = 2.0 * sat; - float satinv = 1.0 - sat; - float luminv = 1.0 - lum; - float lum2m1 = (2.0 * lum) - 1.0; - vec3 ct = (sat2 * xt) + satinv; - - vec3 rgb; - if (lum >= 0.5) - rgb = (luminv * ct) + lum2m1; - else - rgb = lum * ct; - - return rgb; -} +uniform float radius; +uniform vec2 halfpixel; +uniform int passes; +uniform float vibrancy; +uniform float vibrancy_darkness; +in vec2 v_texcoord; layout(location = 0) out vec4 fragColor; + +#include "blur1.glsl" + void main() { - vec2 uv = v_texcoord * 2.0; - - vec4 sum = texture(tex, uv) * 4.0; - sum += texture(tex, uv - halfpixel.xy * radius); - sum += texture(tex, uv + halfpixel.xy * radius); - sum += texture(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius); - sum += texture(tex, uv - vec2(halfpixel.x, -halfpixel.y) * radius); - - vec4 color = sum / 8.0; - - if (vibrancy == 0.0) { - fragColor = color; - } else { - // Invert it so that it correctly maps to the config setting - float vibrancy_darkness1 = 1.0 - vibrancy_darkness; - - // Decrease the RGB components based on their perceived brightness, to prevent visually dark colors from overblowing the rest. - vec3 hsl = rgb2hsl(color.rgb); - // Calculate perceived brightness, as not boost visually dark colors like deep blue as much as equally saturated yellow - float perceivedBrightness = doubleCircleSigmoid(sqrt(color.r * color.r * Pr + color.g * color.g * Pg + color.b * color.b * Pb), 0.8 * vibrancy_darkness1); - - float b1 = b * vibrancy_darkness1; - float boostBase = hsl[1] > 0.0 ? smoothstep(b1 - c * 0.5, b1 + c * 0.5, 1.0 - (pow(1.0 - hsl[1] * cos(a), 2.0) + pow(1.0 - perceivedBrightness * sin(a), 2.0))) : 0.0; - - float saturation = clamp(hsl[1] + (boostBase * vibrancy) / float(passes), 0.0, 1.0); - - vec3 newColor = hsl2rgb(vec3(hsl[0], saturation, hsl[2])); - - fragColor = vec4(newColor, color[3]); - } + fragColor = blur1(v_texcoord, tex, radius, halfpixel, passes, vibrancy, vibrancy_darkness); } diff --git a/src/render/shaders/glsl/blur1.glsl b/src/render/shaders/glsl/blur1.glsl new file mode 100644 index 00000000..36e7d660 --- /dev/null +++ b/src/render/shaders/glsl/blur1.glsl @@ -0,0 +1,130 @@ +// see http://alienryderflex.com/hsp.html +const float Pr = 0.299; +const float Pg = 0.587; +const float Pb = 0.114; + +// Y is "v" ( brightness ). X is "s" ( saturation ) +// see https://www.desmos.com/3d/a88652b9a4 +// Determines if high brightness or high saturation is more important +const float a = 0.93; +const float b = 0.11; +const float c = 0.66; // Determines the smoothness of the transition of unboosted to boosted colors +// + +// http://www.flong.com/archive/texts/code/shapers_circ/ +float doubleCircleSigmoid(float x, float a) { + a = clamp(a, 0.0, 1.0); + + float y = .0; + if (x <= a) { + y = a - sqrt(a * a - x * x); + } else { + y = a + sqrt(pow(1. - a, 2.) - pow(x - 1., 2.)); + } + return y; +} + +vec3 rgb2hsl(vec3 col) { + float red = col.r; + float green = col.g; + float blue = col.b; + + float minc = min(col.r, min(col.g, col.b)); + float maxc = max(col.r, max(col.g, col.b)); + float delta = maxc - minc; + + float lum = (minc + maxc) * 0.5; + float sat = 0.0; + float hue = 0.0; + + if (lum > 0.0 && lum < 1.0) { + float mul = (lum < 0.5) ? (lum) : (1.0 - lum); + sat = delta / (mul * 2.0); + } + + if (delta > 0.0) { + vec3 maxcVec = vec3(maxc); + vec3 masks = vec3(equal(maxcVec, col)) * vec3(notEqual(maxcVec, vec3(green, blue, red))); + vec3 adds = vec3(0.0, 2.0, 4.0) + vec3(green - blue, blue - red, red - green) / delta; + + hue += dot(adds, masks); + hue /= 6.0; + + if (hue < 0.0) + hue += 1.0; + } + + return vec3(hue, sat, lum); +} + +vec3 hsl2rgb(vec3 col) { + const float onethird = 1.0 / 3.0; + const float twothird = 2.0 / 3.0; + const float rcpsixth = 6.0; + + float hue = col.x; + float sat = col.y; + float lum = col.z; + + vec3 xt = vec3(0.0); + + if (hue < onethird) { + xt.r = rcpsixth * (onethird - hue); + xt.g = rcpsixth * hue; + xt.b = 0.0; + } else if (hue < twothird) { + xt.r = 0.0; + xt.g = rcpsixth * (twothird - hue); + xt.b = rcpsixth * (hue - onethird); + } else + xt = vec3(rcpsixth * (hue - twothird), 0.0, rcpsixth * (1.0 - hue)); + + xt = min(xt, 1.0); + + float sat2 = 2.0 * sat; + float satinv = 1.0 - sat; + float luminv = 1.0 - lum; + float lum2m1 = (2.0 * lum) - 1.0; + vec3 ct = (sat2 * xt) + satinv; + + vec3 rgb; + if (lum >= 0.5) + rgb = (luminv * ct) + lum2m1; + else + rgb = lum * ct; + + return rgb; +} + +vec4 blur1(vec2 v_texcoord, sampler2D tex, float radius, vec2 halfpixel, int passes, float vibrancy, float vibrancy_darkness) { + vec2 uv = v_texcoord * 2.0; + + vec4 sum = texture(tex, uv) * 4.0; + sum += texture(tex, uv - halfpixel.xy * radius); + sum += texture(tex, uv + halfpixel.xy * radius); + sum += texture(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius); + sum += texture(tex, uv - vec2(halfpixel.x, -halfpixel.y) * radius); + + vec4 color = sum / 8.0; + + if (vibrancy == 0.0) { + return color; + } else { + // Invert it so that it correctly maps to the config setting + float vibrancy_darkness1 = 1.0 - vibrancy_darkness; + + // Decrease the RGB components based on their perceived brightness, to prevent visually dark colors from overblowing the rest. + vec3 hsl = rgb2hsl(color.rgb); + // Calculate perceived brightness, as not boost visually dark colors like deep blue as much as equally saturated yellow + float perceivedBrightness = doubleCircleSigmoid(sqrt(color.r * color.r * Pr + color.g * color.g * Pg + color.b * color.b * Pb), 0.8 * vibrancy_darkness1); + + float b1 = b * vibrancy_darkness1; + float boostBase = hsl[1] > 0.0 ? smoothstep(b1 - c * 0.5, b1 + c * 0.5, 1.0 - (pow(1.0 - hsl[1] * cos(a), 2.0) + pow(1.0 - perceivedBrightness * sin(a), 2.0))) : 0.0; + + float saturation = clamp(hsl[1] + (boostBase * vibrancy) / float(passes), 0.0, 1.0); + + vec3 newColor = hsl2rgb(vec3(hsl[0], saturation, hsl[2])); + + return vec4(newColor, color[3]); + } +} diff --git a/src/render/shaders/glsl/blur2.frag b/src/render/shaders/glsl/blur2.frag index bfe448d5..62caae56 100644 --- a/src/render/shaders/glsl/blur2.frag +++ b/src/render/shaders/glsl/blur2.frag @@ -1,25 +1,18 @@ #version 300 es -precision highp float; +#define ALLOW_INCLUDES +#extension GL_ARB_shading_language_include : enable + +precision highp float; uniform sampler2D tex; -uniform float radius; -uniform vec2 halfpixel; +uniform float radius; +uniform vec2 halfpixel; -in vec2 v_texcoord; +in vec2 v_texcoord; layout(location = 0) out vec4 fragColor; +#include "blur2.glsl" + void main() { - vec2 uv = v_texcoord / 2.0; - - vec4 sum = texture(tex, uv + vec2(-halfpixel.x * 2.0, 0.0) * radius); - - sum += texture(tex, uv + vec2(-halfpixel.x, halfpixel.y) * radius) * 2.0; - sum += texture(tex, uv + vec2(0.0, halfpixel.y * 2.0) * radius); - sum += texture(tex, uv + vec2(halfpixel.x, halfpixel.y) * radius) * 2.0; - sum += texture(tex, uv + vec2(halfpixel.x * 2.0, 0.0) * radius); - sum += texture(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius) * 2.0; - sum += texture(tex, uv + vec2(0.0, -halfpixel.y * 2.0) * radius); - sum += texture(tex, uv + vec2(-halfpixel.x, -halfpixel.y) * radius) * 2.0; - - fragColor = sum / 12.0; + fragColor = blur2(v_texcoord, tex, radius, halfpixel); } diff --git a/src/render/shaders/glsl/blur2.glsl b/src/render/shaders/glsl/blur2.glsl new file mode 100644 index 00000000..e73e90e3 --- /dev/null +++ b/src/render/shaders/glsl/blur2.glsl @@ -0,0 +1,15 @@ +vec4 blur2(vec2 v_texcoord, sampler2D tex, float radius, vec2 halfpixel) { + vec2 uv = v_texcoord / 2.0; + + vec4 sum = texture(tex, uv + vec2(-halfpixel.x * 2.0, 0.0) * radius); + + sum += texture(tex, uv + vec2(-halfpixel.x, halfpixel.y) * radius) * 2.0; + sum += texture(tex, uv + vec2(0.0, halfpixel.y * 2.0) * radius); + sum += texture(tex, uv + vec2(halfpixel.x, halfpixel.y) * radius) * 2.0; + sum += texture(tex, uv + vec2(halfpixel.x * 2.0, 0.0) * radius); + sum += texture(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius) * 2.0; + sum += texture(tex, uv + vec2(0.0, -halfpixel.y * 2.0) * radius); + sum += texture(tex, uv + vec2(-halfpixel.x, -halfpixel.y) * radius) * 2.0; + + return sum / 12.0; +} diff --git a/src/render/shaders/glsl/blurFinish.glsl b/src/render/shaders/glsl/blurFinish.glsl new file mode 100644 index 00000000..f3d225c3 --- /dev/null +++ b/src/render/shaders/glsl/blurFinish.glsl @@ -0,0 +1,17 @@ +float hash(vec2 p) { + vec3 p3 = fract(vec3(p.xyx) * 1689.1984); + p3 += dot(p3, p3.yzx + 33.33); + return fract((p3.x + p3.y) * p3.z); +} + +vec4 blurFinish(vec4 pixColor, vec2 v_texcoord, float noise, float brightness) { + // noise + float noiseHash = hash(v_texcoord); + float noiseAmount = noiseHash - 0.5; + pixColor.rgb += noiseAmount * noise; + + // brightness + pixColor.rgb *= min(1.0, brightness); + + return pixColor; +} diff --git a/src/render/shaders/glsl/blurfinish.frag b/src/render/shaders/glsl/blurfinish.frag index 6ab48337..0342646b 100644 --- a/src/render/shaders/glsl/blurfinish.frag +++ b/src/render/shaders/glsl/blurfinish.frag @@ -1,32 +1,19 @@ #version 300 es +#define ALLOW_INCLUDES #extension GL_ARB_shading_language_include : enable -precision highp float; -in vec2 v_texcoord; // is in 0-1 +precision highp float; +in vec2 v_texcoord; // is in 0-1 uniform sampler2D tex; -uniform float noise; -uniform float brightness; +uniform float noise; +uniform float brightness; -float hash(vec2 p) { - vec3 p3 = fract(vec3(p.xyx) * 1689.1984); - p3 += dot(p3, p3.yzx + 33.33); - return fract((p3.x + p3.y) * p3.z); -} +#include "blurFinish.glsl" layout(location = 0) out vec4 fragColor; void main() { vec4 pixColor = texture(tex, v_texcoord); - // noise - float noiseHash = hash(v_texcoord); - float noiseAmount = (mod(noiseHash, 1.0) - 0.5); - pixColor.rgb += noiseAmount * noise; - - // brightness - if (brightness < 1.0) { - pixColor.rgb *= brightness; - } - - fragColor = pixColor; + fragColor = blurFinish(pixColor, v_texcoord, noise, brightness); } diff --git a/src/render/shaders/glsl/blurprepare.frag b/src/render/shaders/glsl/blurprepare.frag index 6b9809f8..e96c54bb 100644 --- a/src/render/shaders/glsl/blurprepare.frag +++ b/src/render/shaders/glsl/blurprepare.frag @@ -1,48 +1,38 @@ #version 300 es +#define ALLOW_INCLUDES #extension GL_ARB_shading_language_include : enable -precision highp float; -in vec2 v_texcoord; // is in 0-1 +#include "defines.h" + +precision highp float; +in vec2 v_texcoord; // is in 0-1 uniform sampler2D tex; -uniform float contrast; -uniform float brightness; +uniform float contrast; +uniform float brightness; -uniform int skipCM; -uniform int sourceTF; // eTransferFunction -uniform int targetTF; // eTransferFunction +uniform int sourceTF; // eTransferFunction +uniform int targetTF; // eTransferFunction -#include "CM.glsl" +#if USE_CM +uniform vec2 srcTFRange; +uniform vec2 dstTFRange; -float gain(float x, float k) { - float a = 0.5 * pow(2.0 * ((x < 0.5) ? x : 1.0 - x), k); - return (x < 0.5) ? a : 1.0 - a; -} +uniform float srcRefLuminance; +uniform mat3 convertMatrix; + +uniform float sdrBrightnessMultiplier; +#include "cm_helpers.glsl" +#endif + +#include "blurprepare.glsl" layout(location = 0) out vec4 fragColor; void main() { - vec4 pixColor = texture(tex, v_texcoord); - - if (skipCM == 0) { - if (sourceTF == CM_TRANSFER_FUNCTION_ST2084_PQ) { - pixColor.rgb /= sdrBrightnessMultiplier; - } - pixColor.rgb = convertMatrix * toLinearRGB(pixColor.rgb, sourceTF); - pixColor = toNit(pixColor, vec2(srcTFRange[0], srcRefLuminance)); - pixColor = fromLinearNit(pixColor, targetTF, dstTFRange); - } - - // contrast - if (contrast != 1.0) { - pixColor.r = gain(pixColor.r, contrast); - pixColor.g = gain(pixColor.g, contrast); - pixColor.b = gain(pixColor.b, contrast); - } - - // brightness - if (brightness > 1.0) { - pixColor.rgb *= brightness; - } - - fragColor = pixColor; + fragColor = fragColor = blurPrepare(texture(tex, v_texcoord), contrast, brightness +#if USE_CM + , + sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange, srcRefLuminance, sdrBrightnessMultiplier +#endif + ); } diff --git a/src/render/shaders/glsl/blurprepare.glsl b/src/render/shaders/glsl/blurprepare.glsl new file mode 100644 index 00000000..e4a0daad --- /dev/null +++ b/src/render/shaders/glsl/blurprepare.glsl @@ -0,0 +1,37 @@ +#ifndef ALLOW_INCLUDES +#define ALLOW_INCLUDES +#extension GL_ARB_shading_language_include : enable +#endif + +#include "defines.h" + +#if USE_CM +#include "cm_helpers.glsl" +#endif + +#include "gain.glsl" + +vec4 blurPrepare(vec4 pixColor, float contrast, float brightness +#if USE_CM + , + int sourceTF, int targetTF, mat3 convertMatrix, vec2 srcTFRange, vec2 dstTFRange, float srcRefLuminance, float sdrBrightnessMultiplier +#endif +) { +#if USE_CM + if (sourceTF == CM_TRANSFER_FUNCTION_ST2084_PQ) { + pixColor.rgb /= sdrBrightnessMultiplier; + } + pixColor.rgb = convertMatrix * toLinearRGB(pixColor.rgb, sourceTF); + pixColor = toNit(pixColor, vec2(srcTFRange[0], srcRefLuminance)); + pixColor = fromLinearNit(pixColor, targetTF, dstTFRange); +#endif + + // contrast + if (contrast != 1.0) + pixColor.rgb = gain(pixColor.rgb, contrast); + + // brightness + pixColor.rgb *= max(1.0, brightness); + + return pixColor; +} diff --git a/src/render/shaders/glsl/border.frag b/src/render/shaders/glsl/border.frag index 223b4b29..151593c1 100644 --- a/src/render/shaders/glsl/border.frag +++ b/src/render/shaders/glsl/border.frag @@ -1,182 +1,60 @@ #version 300 es +#define ALLOW_INCLUDES #extension GL_ARB_shading_language_include : enable -precision highp float; -in vec2 v_texcoord; +precision highp float; +in vec2 v_texcoord; -uniform int skipCM; -uniform int sourceTF; // eTransferFunction -uniform int targetTF; // eTransferFunction -uniform mat4x2 targetPrimaries; +uniform int sourceTF; // eTransferFunction +uniform int targetTF; // eTransferFunction +uniform mat3 targetPrimariesXYZ; -uniform vec2 fullSizeUntransformed; +uniform vec2 fullSizeUntransformed; uniform float radiusOuter; uniform float thick; // Gradients are in OkLabA!!!! {l, a, b, alpha} -uniform vec4 gradient[10]; -uniform vec4 gradient2[10]; -uniform int gradientLength; -uniform int gradient2Length; +uniform vec4 gradient[10]; +uniform vec4 gradient2[10]; +uniform int gradientLength; +uniform int gradient2Length; uniform float angle; uniform float angle2; uniform float gradientLerp; uniform float alpha; +uniform float radius; +uniform float roundingPower; +uniform vec2 topLeft; +uniform vec2 fullSize; #include "rounding.glsl" #include "CM.glsl" - -vec4 okLabAToSrgb(vec4 lab) { - float l = pow(lab[0] + lab[1] * 0.3963377774 + lab[2] * 0.2158037573, 3.0); - float m = pow(lab[0] + lab[1] * (-0.1055613458) + lab[2] * (-0.0638541728), 3.0); - float s = pow(lab[0] + lab[1] * (-0.0894841775) + lab[2] * (-1.2914855480), 3.0); - - return vec4(fromLinearRGB( - vec3( - l * 4.0767416621 + m * -3.3077115913 + s * 0.2309699292, - l * (-1.2684380046) + m * 2.6097574011 + s * (-0.3413193965), - l * (-0.0041960863) + m * (-0.7034186147) + s * 1.7076147010 - ), CM_TRANSFER_FUNCTION_GAMMA22 - ), lab[3]); -} - -vec4 getOkColorForCoordArray1(vec2 normalizedCoord) { - if (gradientLength < 2) - return gradient[0]; - - float finalAng = 0.0; - - if (angle > 4.71 /* 270 deg */) { - normalizedCoord[1] = 1.0 - normalizedCoord[1]; - finalAng = 6.28 - angle; - } else if (angle > 3.14 /* 180 deg */) { - normalizedCoord[0] = 1.0 - normalizedCoord[0]; - normalizedCoord[1] = 1.0 - normalizedCoord[1]; - finalAng = angle - 3.14; - } else if (angle > 1.57 /* 90 deg */) { - normalizedCoord[0] = 1.0 - normalizedCoord[0]; - finalAng = 3.14 - angle; - } else { - finalAng = angle; - } - - float sine = sin(finalAng); - - float progress = (normalizedCoord[1] * sine + normalizedCoord[0] * (1.0 - sine)) * float(gradientLength - 1); - int bottom = int(floor(progress)); - int top = bottom + 1; - - return gradient[top] * (progress - float(bottom)) + gradient[bottom] * (float(top) - progress); -} - -vec4 getOkColorForCoordArray2(vec2 normalizedCoord) { - if (gradient2Length < 2) - return gradient2[0]; - - float finalAng = 0.0; - - if (angle2 > 4.71 /* 270 deg */) { - normalizedCoord[1] = 1.0 - normalizedCoord[1]; - finalAng = 6.28 - angle; - } else if (angle2 > 3.14 /* 180 deg */) { - normalizedCoord[0] = 1.0 - normalizedCoord[0]; - normalizedCoord[1] = 1.0 - normalizedCoord[1]; - finalAng = angle - 3.14; - } else if (angle2 > 1.57 /* 90 deg */) { - normalizedCoord[0] = 1.0 - normalizedCoord[0]; - finalAng = 3.14 - angle2; - } else { - finalAng = angle2; - } - - float sine = sin(finalAng); - - float progress = (normalizedCoord[1] * sine + normalizedCoord[0] * (1.0 - sine)) * float(gradient2Length - 1); - int bottom = int(floor(progress)); - int top = bottom + 1; - - return gradient2[top] * (progress - float(bottom)) + gradient2[bottom] * (float(top) - progress); -} - -vec4 getColorForCoord(vec2 normalizedCoord) { - vec4 result1 = getOkColorForCoordArray1(normalizedCoord); - - if (gradient2Length <= 0) - return okLabAToSrgb(result1); - - vec4 result2 = getOkColorForCoordArray2(normalizedCoord); - - return okLabAToSrgb(mix(result1, result2, gradientLerp)); -} +#include "border.glsl" layout(location = 0) out vec4 fragColor; void main() { - highp vec2 pixCoord = vec2(gl_FragCoord); - highp vec2 pixCoordOuter = pixCoord; - highp vec2 originalPixCoord = v_texcoord; - originalPixCoord *= fullSizeUntransformed; - float additionalAlpha = 1.0; - - vec4 pixColor = vec4(1.0, 1.0, 1.0, 1.0); - - bool done = false; - - pixCoord -= topLeft + fullSize * 0.5; - pixCoord *= vec2(lessThan(pixCoord, vec2(0.0))) * -2.0 + 1.0; - pixCoordOuter = pixCoord; - pixCoord -= fullSize * 0.5 - radius; - pixCoordOuter -= fullSize * 0.5 - radiusOuter; - - // center the pixes don't make it top-left - pixCoord += vec2(1.0, 1.0) / fullSize; - pixCoordOuter += vec2(1.0, 1.0) / fullSize; - - if (min(pixCoord.x, pixCoord.y) > 0.0 && radius > 0.0) { - float dist = pow(pow(pixCoord.x,roundingPower)+pow(pixCoord.y,roundingPower),1.0/roundingPower); - float distOuter = pow(pow(pixCoordOuter.x,roundingPower)+pow(pixCoordOuter.y,roundingPower),1.0/roundingPower); - float h = (thick / 2.0); - - if (dist < radius - h) { - // lower - float normalized = smoothstep(0.0, 1.0, (dist - radius + thick + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0)); - additionalAlpha *= normalized; - done = true; - } else if (min(pixCoordOuter.x, pixCoordOuter.y) > 0.0) { - // higher - float normalized = 1.0 - smoothstep(0.0, 1.0, (distOuter - radiusOuter + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0)); - additionalAlpha *= normalized; - done = true; - } else if (distOuter < radiusOuter - h) { - additionalAlpha = 1.0; - done = true; - } - } - - // now check for other shit - if (!done) { - // distance to all straight bb borders - float distanceT = originalPixCoord[1]; - float distanceB = fullSizeUntransformed[1] - originalPixCoord[1]; - float distanceL = originalPixCoord[0]; - float distanceR = fullSizeUntransformed[0] - originalPixCoord[0]; - - // get the smallest - float smallest = min(min(distanceT, distanceB), min(distanceL, distanceR)); - - if (smallest > thick) - discard; - } - - if (additionalAlpha == 0.0) - discard; - - pixColor = getColorForCoord(v_texcoord); - pixColor.rgb *= pixColor[3]; - - if (skipCM == 0) - pixColor = doColorManagement(pixColor, sourceTF, targetTF, targetPrimaries); - - pixColor *= alpha * additionalAlpha; - - fragColor = pixColor; + fragColor = getBorder(v_texcoord, alpha, fullSizeUntransformed, radiusOuter, thick, radius, roundingPower, topLeft, fullSize, gradientLength, gradient, angle, gradient2Length, + gradient2, angle2, gradientLerp +#if USE_CM + , + sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange +#if USE_ICC + , + iccLut3D, iccLutSize +#else +#if USE_TONEMAP || USE_SDR_MOD + , + targetPrimariesXYZ +#endif +#if USE_TONEMAP + , + maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance +#endif +#if USE_SDR_MOD + , + sdrSaturation, sdrBrightnessMultiplier +#endif +#endif +#endif + ); } diff --git a/src/render/shaders/glsl/border.glsl b/src/render/shaders/glsl/border.glsl new file mode 100644 index 00000000..fa2a6980 --- /dev/null +++ b/src/render/shaders/glsl/border.glsl @@ -0,0 +1,203 @@ +#ifndef ALLOW_INCLUDES +#define ALLOW_INCLUDES +#extension GL_ARB_shading_language_include : enable +#endif +#include "cm_helpers.glsl" +#if USE_ROUNDING +#include "rounding.glsl" +#endif + +vec4 okLabAToSrgb(vec4 lab) { + float l = pow(lab[0] + lab[1] * 0.3963377774 + lab[2] * 0.2158037573, 3.0); + float m = pow(lab[0] + lab[1] * (-0.1055613458) + lab[2] * (-0.0638541728), 3.0); + float s = pow(lab[0] + lab[1] * (-0.0894841775) + lab[2] * (-1.2914855480), 3.0); + + return vec4(fromLinearRGB(vec3(l * 4.0767416621 + m * -3.3077115913 + s * 0.2309699292, l * (-1.2684380046) + m * 2.6097574011 + s * (-0.3413193965), + l * (-0.0041960863) + m * (-0.7034186147) + s * 1.7076147010), + CM_TRANSFER_FUNCTION_GAMMA22), + lab[3]); +} + +vec4 getOkColorForCoordArray1(vec2 normalizedCoord, int gradientLength, vec4 gradient[10], float angle) { + if (gradientLength < 2) + return gradient[0]; + + float finalAng = 0.0; + + if (angle > 4.71 /* 270 deg */) { + normalizedCoord[1] = 1.0 - normalizedCoord[1]; + finalAng = 6.28 - angle; + } else if (angle > 3.14 /* 180 deg */) { + normalizedCoord[0] = 1.0 - normalizedCoord[0]; + normalizedCoord[1] = 1.0 - normalizedCoord[1]; + finalAng = angle - 3.14; + } else if (angle > 1.57 /* 90 deg */) { + normalizedCoord[0] = 1.0 - normalizedCoord[0]; + finalAng = 3.14 - angle; + } else { + finalAng = angle; + } + + float sine = sin(finalAng); + + float progress = (normalizedCoord[1] * sine + normalizedCoord[0] * (1.0 - sine)) * float(gradientLength - 1); + int bottom = int(floor(progress)); + int top = bottom + 1; + + return gradient[top] * (progress - float(bottom)) + gradient[bottom] * (float(top) - progress); +} + +vec4 getOkColorForCoordArray2(vec2 normalizedCoord, float angle, int gradient2Length, vec4 gradient2[10], float angle2) { + if (gradient2Length < 2) + return gradient2[0]; + + float finalAng = 0.0; + + if (angle2 > 4.71 /* 270 deg */) { + normalizedCoord[1] = 1.0 - normalizedCoord[1]; + finalAng = 6.28 - angle; + } else if (angle2 > 3.14 /* 180 deg */) { + normalizedCoord[0] = 1.0 - normalizedCoord[0]; + normalizedCoord[1] = 1.0 - normalizedCoord[1]; + finalAng = angle - 3.14; + } else if (angle2 > 1.57 /* 90 deg */) { + normalizedCoord[0] = 1.0 - normalizedCoord[0]; + finalAng = 3.14 - angle2; + } else { + finalAng = angle2; + } + + float sine = sin(finalAng); + + float progress = (normalizedCoord[1] * sine + normalizedCoord[0] * (1.0 - sine)) * float(gradient2Length - 1); + int bottom = int(floor(progress)); + int top = bottom + 1; + + return gradient2[top] * (progress - float(bottom)) + gradient2[bottom] * (float(top) - progress); +} + +vec4 getColorForCoord(vec2 normalizedCoord, int gradientLength, vec4 gradient[10], float angle, int gradient2Length, vec4 gradient2[10], float angle2, float gradientLerp) { + vec4 result1 = getOkColorForCoordArray1(normalizedCoord, gradientLength, gradient, angle); + + if (gradient2Length <= 0) + return okLabAToSrgb(result1); + + vec4 result2 = getOkColorForCoordArray2(normalizedCoord, angle, gradient2Length, gradient2, angle2); + + return okLabAToSrgb(mix(result1, result2, gradientLerp)); +} + +vec4 getBorder(vec2 v_texcoord, float alpha, vec2 fullSizeUntransformed, float radiusOuter, float thick, float radius, float roundingPower, vec2 topLeft, vec2 fullSize, + int gradientLength, vec4 gradient[10], float angle, int gradient2Length, vec4 gradient2[10], float angle2, float gradientLerp +#if USE_CM + , + int sourceTF, int targetTF, mat3 convertMatrix, vec2 srcTFRange, vec2 dstTFRange +#if USE_ICC + , + highp sampler3D iccLut3D, float iccLutSize +#else +#if USE_TONEMAP || USE_SDR_MOD + , + mat3 targetPrimariesXYZ +#endif +#if USE_TONEMAP + , + float maxLuminance, float dstMaxLuminance, float dstRefLuminance, float srcRefLuminance +#endif +#if USE_SDR_MOD + , + float sdrSaturation, float sdrBrightnessMultiplier +#endif +#endif +#endif +) { + vec2 pixCoord = vec2(gl_FragCoord); + vec2 pixCoordOuter = pixCoord; + vec2 originalPixCoord = v_texcoord; + originalPixCoord *= fullSizeUntransformed; + float additionalAlpha = 1.0; + + vec4 pixColor = vec4(1.0, 1.0, 1.0, 1.0); + + bool done = false; + + pixCoord -= topLeft + fullSize * 0.5; + pixCoord *= vec2(lessThan(pixCoord, vec2(0.0))) * -2.0 + 1.0; + pixCoordOuter = pixCoord; + pixCoord -= fullSize * 0.5 - radius; + pixCoordOuter -= fullSize * 0.5 - radiusOuter; + + // center the pixes don't make it top-left + pixCoord += vec2(1.0, 1.0) / fullSize; + pixCoordOuter += vec2(1.0, 1.0) / fullSize; + +#if USE_ROUNDING + if (min(pixCoord.x, pixCoord.y) > 0.0 && radius > 0.0) { + float dist = pow(pow(pixCoord.x, roundingPower) + pow(pixCoord.y, roundingPower), 1.0 / roundingPower); + float distOuter = pow(pow(pixCoordOuter.x, roundingPower) + pow(pixCoordOuter.y, roundingPower), 1.0 / roundingPower); + float h = (thick / 2.0); + + if (dist < radius - h) { + // lower + float normalized = smoothstep(0.0, 1.0, (dist - radius + thick + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0)); + additionalAlpha *= normalized; + done = true; + } else if (min(pixCoordOuter.x, pixCoordOuter.y) > 0.0) { + // higher + float normalized = 1.0 - smoothstep(0.0, 1.0, (distOuter - radiusOuter + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0)); + additionalAlpha *= normalized; + done = true; + } else if (distOuter < radiusOuter - h) { + additionalAlpha = 1.0; + done = true; + } + } +#endif + + // now check for other shit + if (!done) { + // distance to all straight bb borders + float distanceT = originalPixCoord[1]; + float distanceB = fullSizeUntransformed[1] - originalPixCoord[1]; + float distanceL = originalPixCoord[0]; + float distanceR = fullSizeUntransformed[0] - originalPixCoord[0]; + + // get the smallest + float smallest = min(min(distanceT, distanceB), min(distanceL, distanceR)); + + if (smallest > thick) + discard; + } + + if (additionalAlpha == 0.0) + discard; + + pixColor = getColorForCoord(v_texcoord, gradientLength, gradient, angle, gradient2Length, gradient2, angle2, gradientLerp); + pixColor.rgb *= pixColor[3]; + +#if USE_CM + pixColor = doColorManagement(pixColor, sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange +#if USE_ICC + , + iccLut3D, iccLutSize +#else +#if USE_TONEMAP || USE_SDR_MOD + , + targetPrimariesXYZ +#endif +#if USE_TONEMAP + , + maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance +#endif +#if USE_SDR_MOD + , + sdrSaturation, sdrBrightnessMultiplier +#endif +#endif + ); +#endif + + pixColor *= alpha * additionalAlpha; + + return pixColor; +} diff --git a/src/render/shaders/glsl/cm_helpers.glsl b/src/render/shaders/glsl/cm_helpers.glsl new file mode 100644 index 00000000..5e0d14f6 --- /dev/null +++ b/src/render/shaders/glsl/cm_helpers.glsl @@ -0,0 +1,248 @@ +#ifndef ALLOW_INCLUDES +#define ALLOW_INCLUDES +#extension GL_ARB_shading_language_include : enable +#endif +#ifndef CM_HELPERS_GLSL +#define CM_HELPERS_GLSL + +#include "defines.h" +#include "constants.h" + +#if USE_SDR_MOD +vec4 saturate(vec4 color, mat3 primaries, float saturation) { + if (saturation == 1.0) + return color; + vec3 brightness = vec3(primaries[1][0], primaries[1][1], primaries[1][2]); + float Y = dot(color.rgb, brightness); + return vec4(mix(vec3(Y), color.rgb, saturation), color[3]); +} +#endif + +vec3 applyIcc3DLut(vec3 linearRgb01, highp sampler3D iccLut3D, float iccLutSize) { + vec3 x = clamp(linearRgb01, 0.0, 1.0); + + // Map [0..1] to texel centers to avoid edge issues + float N = iccLutSize; + vec3 coord = (x * (N - 1.0) + 0.5) / N; + + return texture(iccLut3D, coord).rgb; +} + +vec3 xy2xyz(vec2 xy) { + if (xy.y == 0.0) + return vec3(0.0, 0.0, 0.0); + + return vec3(xy.x / xy.y, 1.0, (1.0 - xy.x - xy.y) / xy.y); +} + +// The primary source for these transfer functions is https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.1361-0-199802-W!!PDF-E.pdf +vec3 tfInvPQ(vec3 color) { + vec3 E = pow(clamp(color.rgb, vec3(0.0), vec3(1.0)), vec3(PQ_INV_M2)); + return pow((max(E - PQ_C1, vec3(0.0))) / (PQ_C2 - PQ_C3 * E), vec3(PQ_INV_M1)); +} + +vec3 tfInvHLG(vec3 color) { + bvec3 isLow = lessThanEqual(color.rgb, vec3(HLG_E_CUT)); + vec3 lo = color.rgb * color.rgb / 3.0; + vec3 hi = (exp((color.rgb - HLG_C) / HLG_A) + HLG_B) / 12.0; + return mix(hi, lo, isLow); +} + +// Many transfer functions (including sRGB) follow the same pattern: a linear +// segment for small values and a power function for larger values. The +// following function implements this pattern from which sRGB, BT.1886, and +// others can be derived by plugging in the right constants. +vec3 tfInvLinPow(vec3 color, float gamma, float thres, float scale, float alpha) { + bvec3 isLow = lessThanEqual(color.rgb, vec3(thres * scale)); + vec3 lo = color.rgb / scale; + vec3 hi = pow((color.rgb + alpha - 1.0) / alpha, vec3(gamma)); + return mix(hi, lo, isLow); +} + +vec3 tfInvSRGB(vec3 color) { + return tfInvLinPow(color, SRGB_POW, SRGB_CUT, SRGB_SCALE, SRGB_ALPHA); +} + +vec3 tfInvExtSRGB(vec3 color) { + // EXT sRGB is the sRGB transfer function mirrored around 0. + return sign(color) * tfInvSRGB(abs(color)); +} + +vec3 tfInvBT1886(vec3 color) { + return tfInvLinPow(color, BT1886_POW, BT1886_CUT, BT1886_SCALE, BT1886_ALPHA); +} + +vec3 tfInvXVYCC(vec3 color) { + // The inverse transfer function for XVYCC is the BT1886 transfer function mirrored around 0, + // same as what EXT sRGB is to sRGB. + return sign(color) * tfInvBT1886(abs(color)); +} + +vec3 tfInvST240(vec3 color) { + return tfInvLinPow(color, ST240_POW, ST240_CUT, ST240_SCALE, ST240_ALPHA); +} + +// Forward transfer functions corresponding to the inverse functions above. +vec3 tfPQ(vec3 color) { + vec3 E = pow(clamp(color.rgb, vec3(0.0), vec3(1.0)), vec3(PQ_M1)); + return pow((vec3(PQ_C1) + PQ_C2 * E) / (vec3(1.0) + PQ_C3 * E), vec3(PQ_M2)); +} + +vec3 tfHLG(vec3 color) { + bvec3 isLow = lessThanEqual(color.rgb, vec3(HLG_D_CUT)); + vec3 lo = sqrt(max(color.rgb, vec3(0.0)) * 3.0); + vec3 hi = HLG_A * log(max(12.0 * color.rgb - HLG_B, vec3(0.0001))) + HLG_C; + return mix(hi, lo, isLow); +} + +vec3 tfLinPow(vec3 color, float gamma, float thres, float scale, float alpha) { + bvec3 isLow = lessThanEqual(color.rgb, vec3(thres)); + vec3 lo = color.rgb * scale; + vec3 hi = pow(color.rgb, vec3(1.0 / gamma)) * alpha - (alpha - 1.0); + return mix(hi, lo, isLow); +} + +vec3 tfSRGB(vec3 color) { + return tfLinPow(color, SRGB_POW, SRGB_CUT, SRGB_SCALE, SRGB_ALPHA); +} + +vec3 tfExtSRGB(vec3 color) { + // EXT sRGB is the sRGB transfer function mirrored around 0. + return sign(color) * tfSRGB(abs(color)); +} + +vec3 tfBT1886(vec3 color) { + return tfLinPow(color, BT1886_POW, BT1886_CUT, BT1886_SCALE, BT1886_ALPHA); +} + +vec3 tfXVYCC(vec3 color) { + // The transfer function for XVYCC is the BT1886 transfer function mirrored around 0, + // same as what EXT sRGB is to sRGB. + return sign(color) * tfBT1886(abs(color)); +} + +vec3 tfST240(vec3 color) { + return tfLinPow(color, ST240_POW, ST240_CUT, ST240_SCALE, ST240_ALPHA); +} + +vec3 toLinearRGB(vec3 color, int tf) { + switch (tf) { + case CM_TRANSFER_FUNCTION_EXT_LINEAR: return color; + case CM_TRANSFER_FUNCTION_ST2084_PQ: return tfInvPQ(color); + case CM_TRANSFER_FUNCTION_GAMMA22: return pow(max(color, vec3(0.0)), vec3(2.2)); + case CM_TRANSFER_FUNCTION_GAMMA28: return pow(max(color, vec3(0.0)), vec3(2.8)); + case CM_TRANSFER_FUNCTION_HLG: return tfInvHLG(color); + case CM_TRANSFER_FUNCTION_EXT_SRGB: return tfInvExtSRGB(color); + case CM_TRANSFER_FUNCTION_BT1886: return tfInvBT1886(color); + case CM_TRANSFER_FUNCTION_ST240: return tfInvST240(color); + case CM_TRANSFER_FUNCTION_LOG_100: return mix(exp((color - 1.0) * 2.0 * log(10.0)), vec3(0.0), lessThanEqual(color, vec3(0.0))); + case CM_TRANSFER_FUNCTION_LOG_316: return mix(exp((color - 1.0) * 2.5 * log(10.0)), vec3(0.0), lessThanEqual(color, vec3(0.0))); + case CM_TRANSFER_FUNCTION_XVYCC: return tfInvXVYCC(color); + case CM_TRANSFER_FUNCTION_ST428: return pow(max(color, vec3(0.0)), vec3(ST428_POW)) * ST428_SCALE; + case CM_TRANSFER_FUNCTION_SRGB: + default: return tfInvSRGB(color); + } +} + +vec4 toLinear(vec4 color, int tf) { + if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR) + return color; + + color.rgb /= max(color.a, 0.001); + color.rgb = toLinearRGB(color.rgb, tf); + color.rgb *= color.a; + return color; +} + +vec4 toNit(vec4 color, vec2 range) { + color.rgb = color.rgb * (range[1] - range[0]) + range[0]; + return color; +} + +vec3 fromLinearRGB(vec3 color, int tf) { + switch (tf) { + case CM_TRANSFER_FUNCTION_EXT_LINEAR: return color; + case CM_TRANSFER_FUNCTION_ST2084_PQ: return tfPQ(color); + case CM_TRANSFER_FUNCTION_GAMMA22: return pow(max(color, vec3(0.0)), vec3(1.0 / 2.2)); + case CM_TRANSFER_FUNCTION_GAMMA28: return pow(max(color, vec3(0.0)), vec3(1.0 / 2.8)); + case CM_TRANSFER_FUNCTION_HLG: return tfHLG(color); + case CM_TRANSFER_FUNCTION_EXT_SRGB: return tfExtSRGB(color); + case CM_TRANSFER_FUNCTION_BT1886: return tfBT1886(color); + case CM_TRANSFER_FUNCTION_ST240: return tfST240(color); + case CM_TRANSFER_FUNCTION_LOG_100: return mix(1.0 + log(color) / log(10.0) / 2.0, vec3(0.0), lessThanEqual(color, vec3(0.01))); + case CM_TRANSFER_FUNCTION_LOG_316: return mix(1.0 + log(color) / log(10.0) / 2.5, vec3(0.0), lessThanEqual(color, vec3(sqrt(10.0) / 1000.0))); + case CM_TRANSFER_FUNCTION_XVYCC: return tfXVYCC(color); + case CM_TRANSFER_FUNCTION_ST428: return pow(max(color, vec3(0.0)) / ST428_SCALE, vec3(1.0 / ST428_POW)); + case CM_TRANSFER_FUNCTION_SRGB: + default: return tfSRGB(color); + } +} + +vec4 fromLinear(vec4 color, int tf) { + if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR) + return color; + + color.rgb /= max(color.a, 0.001); + color.rgb = fromLinearRGB(color.rgb, tf); + color.rgb *= color.a; + return color; +} + +vec4 fromLinearNit(vec4 color, int tf, vec2 range) { + if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR) + color.rgb = color.rgb / SDR_MAX_LUMINANCE; + else { + color.rgb /= max(color.a, 0.001); + color.rgb = (color.rgb - range[0]) / (range[1] - range[0]); + color.rgb = fromLinearRGB(color.rgb, tf); + color.rgb *= color.a; + } + return color; +} + +#if USE_TONEMAP +#include "tonemap.glsl" +#endif + +vec4 doColorManagement(vec4 pixColor, int srcTF, int dstTF, mat3 convertMatrix, vec2 srcTFRange, vec2 dstTFRange +#if USE_ICC + , + highp sampler3D iccLut3D, float iccLutSize +#else +#if USE_TONEMAP || USE_SDR_MOD + , + mat3 dstxyz +#endif +#if USE_TONEMAP + , + float maxLuminance, float dstMaxLuminance, float dstRefLuminance, float srcRefLuminance +#endif +#if USE_SDR_MOD + , + float sdrSaturation, float sdrBrightnessMultiplier +#endif +#endif +) { + pixColor.rgb /= max(pixColor.a, 0.001); + pixColor.rgb = toLinearRGB(pixColor.rgb, srcTF); +#if USE_ICC + pixColor.rgb = applyIcc3DLut(pixColor.rgb, iccLut3D, iccLutSize); + pixColor.rgb *= pixColor.a; +#else + pixColor.rgb = convertMatrix * pixColor.rgb; + pixColor = toNit(pixColor, srcTFRange); + pixColor.rgb *= pixColor.a; +#if USE_TONEMAP + pixColor = tonemap(pixColor, dstxyz, maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance); +#endif + pixColor = fromLinearNit(pixColor, dstTF, dstTFRange); +#if USE_SDR_MOD + pixColor = saturate(pixColor, dstxyz, sdrSaturation); + pixColor.rgb *= sdrBrightnessMultiplier; +#endif +#endif + + return pixColor; +} + +#endif \ No newline at end of file diff --git a/src/render/shaders/glsl/constants.h b/src/render/shaders/glsl/constants.h new file mode 100644 index 00000000..bbab5284 --- /dev/null +++ b/src/render/shaders/glsl/constants.h @@ -0,0 +1,62 @@ +#ifndef CONSTANTS_H +#define CONSTANTS_H +//enum eTransferFunction +#define CM_TRANSFER_FUNCTION_BT1886 1 +#define CM_TRANSFER_FUNCTION_GAMMA22 2 +#define CM_TRANSFER_FUNCTION_GAMMA28 3 +#define CM_TRANSFER_FUNCTION_ST240 4 +#define CM_TRANSFER_FUNCTION_EXT_LINEAR 5 +#define CM_TRANSFER_FUNCTION_LOG_100 6 +#define CM_TRANSFER_FUNCTION_LOG_316 7 +#define CM_TRANSFER_FUNCTION_XVYCC 8 +#define CM_TRANSFER_FUNCTION_SRGB 9 +#define CM_TRANSFER_FUNCTION_EXT_SRGB 10 +#define CM_TRANSFER_FUNCTION_ST2084_PQ 11 +#define CM_TRANSFER_FUNCTION_ST428 12 +#define CM_TRANSFER_FUNCTION_HLG 13 + +// sRGB constants +#define SRGB_POW 2.4 +#define SRGB_CUT 0.0031308 +#define SRGB_SCALE 12.92 +#define SRGB_ALPHA 1.055 + +#define BT1886_POW (1.0 / 0.45) +#define BT1886_CUT 0.018053968510807 +#define BT1886_SCALE 4.5 +#define BT1886_ALPHA (1.0 + 5.5 * BT1886_CUT) + +// See http://car.france3.mars.free.fr/HD/INA-%2026%20jan%2006/SMPTE%20normes%20et%20confs/s240m.pdf +#define ST240_POW (1.0 / 0.45) +#define ST240_CUT 0.0228 +#define ST240_SCALE 4.0 +#define ST240_ALPHA 1.1115 + +#define ST428_POW 2.6 +#define ST428_SCALE (52.37 / 48.0) + +// PQ constants +#define PQ_M1 0.1593017578125 +#define PQ_M2 78.84375 +#define PQ_INV_M1 (1.0 / PQ_M1) +#define PQ_INV_M2 (1.0 / PQ_M2) +#define PQ_C1 0.8359375 +#define PQ_C2 18.8515625 +#define PQ_C3 18.6875 + +// HLG constants +#define HLG_D_CUT (1.0 / 12.0) +#define HLG_E_CUT 0.5 +#define HLG_A 0.17883277 +#define HLG_B 0.28466892 +#define HLG_C 0.55991073 + +#define SDR_MIN_LUMINANCE 0.2 +#define SDR_MAX_LUMINANCE 80.0 +#define HDR_MIN_LUMINANCE 0.005 +#define HDR_MAX_LUMINANCE 10000.0 +#define HLG_MAX_LUMINANCE 1000.0 + +#define M_E 2.718281828459045 + +#endif diff --git a/src/render/shaders/glsl/defines.h b/src/render/shaders/glsl/defines.h new file mode 100644 index 00000000..31b120a4 --- /dev/null +++ b/src/render/shaders/glsl/defines.h @@ -0,0 +1,10 @@ +// DO NOT EDIT. Will be overwritten in runtime +#define USE_RGBA 1 +#define USE_DISCARD 1 +#define USE_TINT 1 +#define USE_ROUNDING 1 +#define USE_CM 1 +#define USE_TONEMAP 1 +#define USE_SDR_MOD 1 +#define USE_BLUR 1 +#define USE_ICC 1 diff --git a/src/render/shaders/glsl/ext.frag b/src/render/shaders/glsl/ext.frag index f540a9f9..1c614bd3 100644 --- a/src/render/shaders/glsl/ext.frag +++ b/src/render/shaders/glsl/ext.frag @@ -1,38 +1,43 @@ #version 300 es +#define ALLOW_INCLUDES #extension GL_ARB_shading_language_include : enable #extension GL_OES_EGL_image_external_essl3 : require -precision highp float; -in vec2 v_texcoord; -uniform samplerExternalOES texture0; -uniform float alpha; +precision highp float; +in vec2 v_texcoord; +uniform samplerExternalOES tex; +uniform float alpha; +uniform float radius; +uniform float roundingPower; +uniform vec2 topLeft; +uniform vec2 fullSize; #include "rounding.glsl" -uniform int discardOpaque; -uniform int discardAlpha; -uniform int discardAlphaValue; +uniform int discardOpaque; +uniform int discardAlpha; +uniform int discardAlphaValue; -uniform int applyTint; +uniform int applyTint; uniform vec3 tint; layout(location = 0) out vec4 fragColor; void main() { - vec4 pixColor = texture(texture0, v_texcoord); + vec4 pixColor = texture(tex, v_texcoord); if (discardOpaque == 1 && pixColor[3] * alpha == 1.0) - discard; + discard; if (applyTint == 1) { - pixColor[0] = pixColor[0] * tint[0]; - pixColor[1] = pixColor[1] * tint[1]; - pixColor[2] = pixColor[2] * tint[2]; + pixColor[0] = pixColor[0] * tint[0]; + pixColor[1] = pixColor[1] * tint[1]; + pixColor[2] = pixColor[2] * tint[2]; } if (radius > 0.0) - pixColor = rounding(pixColor); + pixColor = rounding(pixColor, radius, roundingPower, topLeft, fullSize); fragColor = pixColor * alpha; } diff --git a/src/render/shaders/glsl/gain.glsl b/src/render/shaders/glsl/gain.glsl new file mode 100644 index 00000000..2bdc0002 --- /dev/null +++ b/src/render/shaders/glsl/gain.glsl @@ -0,0 +1,6 @@ +vec3 gain(vec3 x, float k) { + vec3 t = step(0.5, x); + vec3 y = mix(x, 1.0 - x, t); + vec3 a = 0.5 * pow(2.0 * y, vec3(k)); + return mix(a, 1.0 - a, t); +} diff --git a/src/render/shaders/glsl/glitch.frag b/src/render/shaders/glsl/glitch.frag index e399a8b1..d7259cc4 100644 --- a/src/render/shaders/glsl/glitch.frag +++ b/src/render/shaders/glsl/glitch.frag @@ -5,7 +5,7 @@ in vec2 v_texcoord; uniform sampler2D tex; uniform float time; // quirk: time is set to 0 at the beginning, should be around 10 when crash. uniform float distort; -uniform vec2 screenSize; +uniform vec2 fullSize; float rand(float co) { return fract(sin(dot(vec2(co, co), vec2(12.9898, 78.233))) * 43758.5453); @@ -31,7 +31,7 @@ void main() { float ABERR_OFFSET = 4.0 * (distort / 5.5) * time; float TEAR_AMOUNT = 9000.0 * (1.0 - (distort / 5.5)); float TEAR_BANDS = 108.0 / 2.0 * (distort / 5.5) * 2.0; - float MELT_AMOUNT = (distort * 8.0) / screenSize.y; + float MELT_AMOUNT = (distort * 8.0) / fullSize.y; float NOISE = abs(mod(noise(v_texcoord) * distort * time * 2.771, 1.0)) * time / 10.0; if (time < 2.0) @@ -44,7 +44,7 @@ void main() { if (time < 3.0) blockOffset = vec2(0,0); - float meltSeed = abs(mod(rand(floor(v_texcoord.x * screenSize.x * 17.719)) * 281.882, 1.0)); + float meltSeed = abs(mod(rand(floor(v_texcoord.x * fullSize.x * 17.719)) * 281.882, 1.0)); if (meltSeed < 0.8) { meltSeed = 0.0; } else { @@ -52,11 +52,11 @@ void main() { } float meltAmount = MELT_AMOUNT * meltSeed; - vec2 pixCoord = vec2(v_texcoord.x + offset + NOISE * 3.0 / screenSize.x + blockOffset.x, v_texcoord.y - meltAmount + 0.02 * NOISE / screenSize.x + NOISE * 3.0 / screenSize.y + blockOffset.y); + vec2 pixCoord = vec2(v_texcoord.x + offset + NOISE * 3.0 / fullSize.x + blockOffset.x, v_texcoord.y - meltAmount + 0.02 * NOISE / fullSize.x + NOISE * 3.0 / fullSize.y + blockOffset.y); vec4 pixColor = texture(tex, pixCoord); - vec4 pixColorLeft = texture(tex, pixCoord + vec2(ABERR_OFFSET / screenSize.x, 0)); - vec4 pixColorRight = texture(tex, pixCoord + vec2(-ABERR_OFFSET / screenSize.x, 0)); + vec4 pixColorLeft = texture(tex, pixCoord + vec2(ABERR_OFFSET / fullSize.x, 0)); + vec4 pixColorRight = texture(tex, pixCoord + vec2(-ABERR_OFFSET / fullSize.x, 0)); pixColor[0] = pixColorLeft[0]; pixColor[2] = pixColorRight[2]; diff --git a/src/render/shaders/glsl/quad.frag b/src/render/shaders/glsl/quad.frag index 5dae493e..61895a60 100644 --- a/src/render/shaders/glsl/quad.frag +++ b/src/render/shaders/glsl/quad.frag @@ -1,17 +1,27 @@ #version 300 es +#define ALLOW_INCLUDES #extension GL_ARB_shading_language_include : enable -precision highp float; -in vec4 v_color; +#include "defines.h" +precision highp float; +in vec4 v_color; + +#if USE_ROUNDING +uniform float radius; +uniform float roundingPower; +uniform vec2 topLeft; +uniform vec2 fullSize; #include "rounding.glsl" +#endif layout(location = 0) out vec4 fragColor; void main() { vec4 pixColor = v_color; - if (radius > 0.0) - pixColor = rounding(pixColor); +#if USE_ROUNDING + pixColor = rounding(pixColor, radius, roundingPower, topLeft, fullSize); +#endif fragColor = pixColor; } diff --git a/src/render/shaders/glsl/rgba.frag b/src/render/shaders/glsl/rgba.frag deleted file mode 100644 index e4e04500..00000000 --- a/src/render/shaders/glsl/rgba.frag +++ /dev/null @@ -1,39 +0,0 @@ -#version 300 es - -#extension GL_ARB_shading_language_include : enable -precision highp float; -in vec2 v_texcoord; // is in 0-1 -uniform sampler2D tex; -uniform float alpha; - -#include "rounding.glsl" - -uniform int discardOpaque; -uniform int discardAlpha; -uniform float discardAlphaValue; - -uniform int applyTint; -uniform vec3 tint; - -layout(location = 0) out vec4 fragColor; -void main() { - - vec4 pixColor = texture(tex, v_texcoord); - - if (discardOpaque == 1 && pixColor[3] * alpha == 1.0) - discard; - - if (discardAlpha == 1 && pixColor[3] <= discardAlphaValue) - discard; - - if (applyTint == 1) { - pixColor[0] = pixColor[0] * tint[0]; - pixColor[1] = pixColor[1] * tint[1]; - pixColor[2] = pixColor[2] * tint[2]; - } - - if (radius > 0.0) - pixColor = rounding(pixColor); - - fragColor = pixColor * alpha; -} diff --git a/src/render/shaders/glsl/rgbx.frag b/src/render/shaders/glsl/rgbx.frag deleted file mode 100644 index 84672d76..00000000 --- a/src/render/shaders/glsl/rgbx.frag +++ /dev/null @@ -1,35 +0,0 @@ -#version 300 es -#extension GL_ARB_shading_language_include : enable -precision highp float; -in vec2 v_texcoord; -uniform sampler2D tex; -uniform float alpha; - -#include "rounding.glsl" - -uniform int discardOpaque; -uniform int discardAlpha; -uniform int discardAlphaValue; - -uniform int applyTint; -uniform vec3 tint; - -layout(location = 0) out vec4 fragColor; -void main() { - - if (discardOpaque == 1 && alpha == 1.0) - discard; - - vec4 pixColor = vec4(texture(tex, v_texcoord).rgb, 1.0); - - if (applyTint == 1) { - pixColor[0] = pixColor[0] * tint[0]; - pixColor[1] = pixColor[1] * tint[1]; - pixColor[2] = pixColor[2] * tint[2]; - } - - if (radius > 0.0) - pixColor = rounding(pixColor); - - fragColor = pixColor * alpha; -} diff --git a/src/render/shaders/glsl/rounding.glsl b/src/render/shaders/glsl/rounding.glsl index 472415fd..61a0bb9c 100644 --- a/src/render/shaders/glsl/rounding.glsl +++ b/src/render/shaders/glsl/rounding.glsl @@ -1,13 +1,10 @@ +#ifndef ROUNDING_GLSL +#define ROUNDING_GLSL // smoothing constant for the edge: more = blurrier, but smoother -#define M_PI 3.1415926535897932384626433832795 +#define M_PI 3.1415926535897932384626433832795 #define SMOOTHING_CONSTANT (M_PI / 5.34665792551) -uniform float radius; -uniform float roundingPower; -uniform vec2 topLeft; -uniform vec2 fullSize; - -vec4 rounding(vec4 color) { +vec4 rounding(vec4 color, float radius, float roundingPower, vec2 topLeft, vec2 fullSize) { vec2 pixCoord = vec2(gl_FragCoord); pixCoord -= topLeft + fullSize * 0.5; pixCoord *= vec2(lessThan(pixCoord, vec2(0.0))) * -2.0 + 1.0; @@ -15,7 +12,7 @@ vec4 rounding(vec4 color) { pixCoord += vec2(1.0, 1.0) / fullSize; // center the pix don't make it top-left if (pixCoord.x + pixCoord.y > radius) { - float dist = pow(pow(pixCoord.x, roundingPower) + pow(pixCoord.y, roundingPower), 1.0/roundingPower); + float dist = pow(pow(pixCoord.x, roundingPower) + pow(pixCoord.y, roundingPower), 1.0 / roundingPower); if (dist > radius + SMOOTHING_CONSTANT) discard; @@ -27,3 +24,4 @@ vec4 rounding(vec4 color) { return color; } +#endif \ No newline at end of file diff --git a/src/render/shaders/glsl/shadow.frag b/src/render/shaders/glsl/shadow.frag index b6fdf6ee..c23ebd5d 100644 --- a/src/render/shaders/glsl/shadow.frag +++ b/src/render/shaders/glsl/shadow.frag @@ -1,99 +1,57 @@ #version 300 es +#define ALLOW_INCLUDES #extension GL_ARB_shading_language_include : enable -precision highp float; -in vec4 v_color; -in vec2 v_texcoord; +#include "defines.h" -uniform int skipCM; -uniform int sourceTF; // eTransferFunction -uniform int targetTF; // eTransferFunction -uniform mat4x2 targetPrimaries; +precision highp float; +in vec4 v_color; +in vec2 v_texcoord; -uniform vec2 topLeft; -uniform vec2 bottomRight; -uniform vec2 fullSize; +uniform int sourceTF; // eTransferFunction +uniform int targetTF; // eTransferFunction +uniform mat3 targetPrimariesXYZ; + +uniform vec2 topLeft; +uniform vec2 bottomRight; +uniform vec2 fullSize; uniform float radius; uniform float roundingPower; uniform float range; uniform float shadowPower; +#if USE_CM +#include "cm_helpers.glsl" #include "CM.glsl" +#endif -float pixAlphaRoundedDistance(float distanceToCorner) { - if (distanceToCorner > radius) { - return 0.0; - } - - if (distanceToCorner > radius - range) { - return pow((range - (distanceToCorner - radius + range)) / range, shadowPower); // i think? - } - - return 1.0; -} - -float modifiedLength(vec2 a) { - return pow(pow(abs(a.x),roundingPower)+pow(abs(a.y),roundingPower),1.0/roundingPower); -} +#include "shadow.glsl" layout(location = 0) out vec4 fragColor; void main() { + vec4 pixColor = v_color; - vec4 pixColor = v_color; - float originalAlpha = pixColor[3]; - - bool done = false; - - vec2 pixCoord = fullSize * v_texcoord; - - // ok, now we check the distance to a border. - - if (pixCoord[0] < topLeft[0]) { - if (pixCoord[1] < topLeft[1]) { - // top left - pixColor[3] = pixColor[3] * pixAlphaRoundedDistance(modifiedLength(pixCoord - topLeft)); - done = true; - } else if (pixCoord[1] > bottomRight[1]) { - // bottom left - pixColor[3] = pixColor[3] * pixAlphaRoundedDistance(modifiedLength(pixCoord - vec2(topLeft[0], bottomRight[1]))); - done = true; - } - } else if (pixCoord[0] > bottomRight[0]) { - if (pixCoord[1] < topLeft[1]) { - // top right - pixColor[3] = pixColor[3] * pixAlphaRoundedDistance(modifiedLength(pixCoord - vec2(bottomRight[0], topLeft[1]))); - done = true; - } else if (pixCoord[1] > bottomRight[1]) { - // bottom right - pixColor[3] = pixColor[3] * pixAlphaRoundedDistance(modifiedLength(pixCoord - bottomRight)); - done = true; - } - } - - if (!done) { - // distance to all straight bb borders - float distanceT = pixCoord[1]; - float distanceB = fullSize[1] - pixCoord[1]; - float distanceL = pixCoord[0]; - float distanceR = fullSize[0] - pixCoord[0]; - - // get the smallest - float smallest = min(min(distanceT, distanceB), min(distanceL, distanceR)); - - if (smallest < range) { - pixColor[3] = pixColor[3] * pow((smallest / range), shadowPower); - } - } - - if (pixColor[3] == 0.0) { - discard; return; - } - - // premultiply - pixColor.rgb *= pixColor[3]; - - if (skipCM == 0) - pixColor = doColorManagement(pixColor, sourceTF, targetTF, targetPrimaries); - - fragColor = pixColor; + fragColor = getShadow(pixColor, v_texcoord, radius, roundingPower, topLeft, fullSize, range, shadowPower, bottomRight +#if USE_CM + , + sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange +#if USE_ICC + , + iccLut3D, iccLutSize +#else +#if USE_TONEMAP || USE_SDR_MOD + , + targetPrimariesXYZ +#endif +#if USE_TONEMAP + , + maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance +#endif +#if USE_SDR_MOD + , + sdrSaturation, sdrBrightnessMultiplier +#endif +#endif +#endif + ); } \ No newline at end of file diff --git a/src/render/shaders/glsl/shadow.glsl b/src/render/shaders/glsl/shadow.glsl new file mode 100644 index 00000000..48cde562 --- /dev/null +++ b/src/render/shaders/glsl/shadow.glsl @@ -0,0 +1,126 @@ +#ifndef ALLOW_INCLUDES +#define ALLOW_INCLUDES +#extension GL_ARB_shading_language_include : enable +#endif +#ifndef SHADOW_GLSL +#define SHADOW_GLSL + +#include "cm_helpers.glsl" + +float pixAlphaRoundedDistance(float distanceToCorner, float radius, float range, float shadowPower) { + if (distanceToCorner > radius) { + return 0.0; + } + + if (distanceToCorner > radius - range) { + return pow((range - (distanceToCorner - radius + range)) / range, shadowPower); // i think? + } + + return 1.0; +} + +float modifiedLength(vec2 a, float roundingPower) { + return pow(pow(abs(a.x), roundingPower) + pow(abs(a.y), roundingPower), 1.0 / roundingPower); +} + +vec4 getShadow(vec4 pixColor, vec2 v_texcoord, float radius, float roundingPower, vec2 topLeft, vec2 fullSize, float range, float shadowPower, vec2 bottomRight +#if USE_CM + , + int sourceTF, int targetTF, mat3 convertMatrix, vec2 srcTFRange, vec2 dstTFRange +#if USE_ICC + , + highp sampler3D iccLut3D, float iccLutSize +#else +#if USE_TONEMAP || USE_SDR_MOD + , + mat3 targetPrimariesXYZ +#endif +#if USE_TONEMAP + , + float maxLuminance, float dstMaxLuminance, float dstRefLuminance, float srcRefLuminance +#endif +#if USE_SDR_MOD + , + float sdrSaturation, float sdrBrightnessMultiplier +#endif +#endif +#endif +) { + float originalAlpha = pixColor[3]; + + bool done = false; + + vec2 pixCoord = fullSize * v_texcoord; + + // ok, now we check the distance to a border. + + if (pixCoord[0] < topLeft[0]) { + if (pixCoord[1] < topLeft[1]) { + // top left + pixColor[3] = pixColor[3] * pixAlphaRoundedDistance(modifiedLength(pixCoord - topLeft, roundingPower), radius, range, shadowPower); + done = true; + } else if (pixCoord[1] > bottomRight[1]) { + // bottom left + pixColor[3] = pixColor[3] * pixAlphaRoundedDistance(modifiedLength(pixCoord - vec2(topLeft[0], bottomRight[1]), roundingPower), radius, range, shadowPower); + done = true; + } + } else if (pixCoord[0] > bottomRight[0]) { + if (pixCoord[1] < topLeft[1]) { + // top right + pixColor[3] = pixColor[3] * pixAlphaRoundedDistance(modifiedLength(pixCoord - vec2(bottomRight[0], topLeft[1]), roundingPower), radius, range, shadowPower); + done = true; + } else if (pixCoord[1] > bottomRight[1]) { + // bottom right + pixColor[3] = pixColor[3] * pixAlphaRoundedDistance(modifiedLength(pixCoord - bottomRight, roundingPower), radius, range, shadowPower); + done = true; + } + } + + if (!done) { + // distance to all straight bb borders + float distanceT = pixCoord[1]; + float distanceB = fullSize[1] - pixCoord[1]; + float distanceL = pixCoord[0]; + float distanceR = fullSize[0] - pixCoord[0]; + + // get the smallest + float smallest = min(min(distanceT, distanceB), min(distanceL, distanceR)); + + if (smallest < range) { + pixColor[3] = pixColor[3] * pow((smallest / range), shadowPower); + } + } + + if (pixColor[3] == 0.0) { + discard; + return pixColor; + } + + // premultiply + pixColor.rgb *= pixColor[3]; + +#if USE_CM + pixColor = doColorManagement(pixColor, sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange +#if USE_ICC + , + iccLut3D, iccLutSize +#else +#if USE_TONEMAP || USE_SDR_MOD + , + targetPrimariesXYZ +#endif +#if USE_TONEMAP + , + maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance +#endif +#if USE_SDR_MOD + , + sdrSaturation, sdrBrightnessMultiplier +#endif +#endif + ); +#endif + + return pixColor; +} +#endif \ No newline at end of file diff --git a/src/render/shaders/glsl/surface.frag b/src/render/shaders/glsl/surface.frag new file mode 100644 index 00000000..30023bc8 --- /dev/null +++ b/src/render/shaders/glsl/surface.frag @@ -0,0 +1,104 @@ +#version 300 es +#define ALLOW_INCLUDES +#extension GL_ARB_shading_language_include : enable + +#include "defines.h" + +precision highp float; +in vec2 v_texcoord; +uniform sampler2D tex; +#if USE_BLUR +uniform vec2 uvSize; +uniform vec2 uvOffset; +uniform sampler2D blurredBG; +#endif + +uniform float alpha; + +#if USE_DISCARD +uniform bool discardOpaque; +uniform bool discardAlpha; +uniform float discardAlphaValue; +#endif + +#if USE_TINT +uniform vec3 tint; +#endif + +#if USE_ROUNDING +uniform float radius; +uniform float roundingPower; +uniform vec2 topLeft; +uniform vec2 fullSize; +#include "rounding.glsl" +#endif + +#if USE_CM +uniform int sourceTF; // eTransferFunction +uniform int targetTF; // eTransferFunction + +#if USE_TONEMAP || USE_SDR_MOD +uniform mat3 targetPrimariesXYZ; +#else +const mat3 targetPrimariesXYZ = mat3(0.0); +#endif + +#include "CM.glsl" +#endif + +layout(location = 0) out vec4 fragColor; +void main() { +#if USE_RGBA + vec4 pixColor = texture(tex, v_texcoord); +#else + vec4 pixColor = vec4(texture(tex, v_texcoord).rgb, 1.0); +#endif + +#if USE_DISCARD && !USE_BLUR + if (discardOpaque && pixColor.a * alpha == 1.0) + discard; + + if (discardAlpha && pixColor.a <= discardAlphaValue) + discard; +#endif + +#if USE_CM + pixColor = doColorManagement(pixColor, sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange +#if USE_ICC + , + iccLut3D, iccLutSize +#else +#if USE_TONEMAP || USE_SDR_MOD + , + targetPrimariesXYZ +#endif +#if USE_TONEMAP + , + maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance +#endif +#if USE_SDR_MOD + , + sdrSaturation, sdrBrightnessMultiplier +#endif +#endif + ); +#endif + +#if USE_TINT + pixColor.rgb = pixColor.rgb * tint; +#endif + +#if USE_ROUNDING + pixColor = rounding(pixColor, radius, roundingPower, topLeft, fullSize); +#endif +#if USE_BLUR +#if USE_DISCARD + pixColor = mix(pixColor, vec4(mix(texture(blurredBG, v_texcoord * uvSize + uvOffset).rgb, pixColor.rgb, pixColor.a), 1.0), + discardAlpha && (pixColor.a <= discardAlphaValue) ? 0.0 : 1.0); +#else + pixColor = vec4(mix(texture(blurredBG, v_texcoord * uvSize + uvOffset).rgb, pixColor.rgb, pixColor.a), 1.0); +#endif +#endif + + fragColor = pixColor * alpha; +} diff --git a/src/render/shaders/glsl/tonemap.glsl b/src/render/shaders/glsl/tonemap.glsl new file mode 100644 index 00000000..a0ba24ef --- /dev/null +++ b/src/render/shaders/glsl/tonemap.glsl @@ -0,0 +1,64 @@ +#ifndef ALLOW_INCLUDES +#define ALLOW_INCLUDES +#extension GL_ARB_shading_language_include : enable +#endif +#include "constants.h" + +const mat3 BT2020toLMS = mat3(0.3592, 0.6976, -0.0358, -0.1922, 1.1004, 0.0755, 0.0070, 0.0749, 0.8434); +//const mat3 LMStoBT2020 = inverse(BT2020toLMS); +const mat3 LMStoBT2020 = mat3( // + 2.0701800566956135096, -1.3264568761030210255, 0.20661600684785517081, // + 0.36498825003265747974, 0.68046736285223514102, -0.045421753075853231409, // + -0.049595542238932107896, -0.049421161186757487412, 1.1879959417328034394 // +); + +// const mat3 ICtCpPQ = transpose(mat3( +// 2048.0, 2048.0, 0.0, +// 6610.0, -13613.0, 7003.0, +// 17933.0, -17390.0, -543.0 +// ) / 4096.0); +const mat3 ICtCpPQ = mat3( // + 0.5, 1.61376953125, 4.378173828125, // + 0.5, -3.323486328125, -4.24560546875, // + 0.0, 1.709716796875, -0.132568359375 // +); +//const mat3 ICtCpPQInv = inverse(ICtCpPQ); +const mat3 ICtCpPQInv = mat3( // + 1.0, 1.0, 1.0, // + 0.0086090370379327566, -0.0086090370379327566, 0.560031335710679118, // + 0.11102962500302595656, -0.11102962500302595656, -0.32062717498731885185 // +); + +// unused for now +// const mat3 ICtCpHLG = transpose(mat3( +// 2048.0, 2048.0, 0.0, +// 3625.0, -7465.0, 3840.0, +// 9500.0, -9212.0, -288.0 +// ) / 4096.0); +// const mat3 ICtCpHLGInv = inverse(ICtCpHLG); + +vec4 tonemap(vec4 color, mat3 dstXYZ, float maxLuminance, float dstMaxLuminance, float dstRefLuminance, float srcRefLuminance) { + if (maxLuminance < dstMaxLuminance * 1.01) + return vec4(clamp(color.rgb, vec3(0.0), vec3(dstMaxLuminance)), color[3]); + + mat3 toLMS = BT2020toLMS * dstXYZ; + mat3 fromLMS = inverse(dstXYZ) * LMStoBT2020; + + vec3 lms = fromLinear(vec4((toLMS * color.rgb) / HDR_MAX_LUMINANCE, 1.0), CM_TRANSFER_FUNCTION_ST2084_PQ).rgb; + vec3 ICtCp = ICtCpPQ * lms; + + float E = pow(clamp(ICtCp[0], 0.0, 1.0), PQ_INV_M2); + float luminance = pow((max(E - PQ_C1, 0.0)) / (PQ_C2 - PQ_C3 * E), PQ_INV_M1) * HDR_MAX_LUMINANCE; + + float linearPart = min(luminance, dstRefLuminance); + float luminanceAboveRef = max(luminance - dstRefLuminance, 0.0); + float maxExcessLuminance = max(maxLuminance - dstRefLuminance, 1.0); + float shoulder = log((luminanceAboveRef / maxExcessLuminance + 1.0) * (M_E - 1.0)); + float mappedHigh = shoulder * (dstMaxLuminance - dstRefLuminance); + float newLum = clamp(linearPart + mappedHigh, 0.0, dstMaxLuminance); + + // scale src to dst reference + float refScale = dstRefLuminance / srcRefLuminance; + + return vec4(fromLMS * toLinear(vec4(ICtCpPQInv * ICtCp, 1.0), CM_TRANSFER_FUNCTION_ST2084_PQ).rgb * HDR_MAX_LUMINANCE * refScale, color[3]); +} diff --git a/src/xwayland/XSurface.cpp b/src/xwayland/XSurface.cpp index bc74f54d..5c5f3b5c 100644 --- a/src/xwayland/XSurface.cpp +++ b/src/xwayland/XSurface.cpp @@ -7,8 +7,6 @@ #ifndef NO_XWAYLAND -#include - CXWaylandSurface::CXWaylandSurface(uint32_t xID_, CBox geometry_, bool OR) : m_xID(xID_), m_geometry(geometry_), m_overrideRedirect(OR) { xcb_res_query_client_ids_cookie_t client_id_cookie = {0}; if (g_pXWayland->m_wm->m_xres) { @@ -42,9 +40,43 @@ CXWaylandSurface::CXWaylandSurface(uint32_t xID_, CBox geometry_, bool OR) : m_x free(reply); // NOLINT(cppcoreguidelines-no-malloc) } + // FIXME: this is a race, we need to listen to props changed + recheckSupportedProps(); + m_events.resourceChange.listenStatic([this] { ensureListeners(); }); } +void CXWaylandSurface::recheckSupportedProps() { + m_supportedProps.clear(); + + auto listCookie = xcb_list_properties(g_pXWayland->m_wm->getConnection(), m_xID); + auto* listReply = xcb_list_properties_reply(g_pXWayland->m_wm->getConnection(), listCookie, nullptr); + auto getCookie = xcb_get_property(g_pXWayland->m_wm->getConnection(), 0, m_xID, HYPRATOMS["WM_PROTOCOLS"], XCB_ATOM_ATOM, 0, 32); + auto* getReply = xcb_get_property_reply(g_pXWayland->m_wm->getConnection(), getCookie, nullptr); + + if (listReply) { + const auto* atoms = xcb_list_properties_atoms(listReply); + auto len = xcb_list_properties_atoms_length(listReply); + + for (auto i = 0; i < len; ++i) { + m_supportedProps[atoms[i]] = true; + } + + free(listReply); + } + + if (getReply) { + const auto* protocols = sc(xcb_get_property_value(getReply)); + const auto len = xcb_get_property_value_length(getReply) / sizeof(xcb_atom_t); + + for (auto i = 0u; i < len; ++i) { + m_supportedProps[protocols[i]] = true; + } + + free(getReply); + } +} + void CXWaylandSurface::ensureListeners() { bool connected = m_listeners.destroySurface; @@ -226,10 +258,19 @@ void CXWaylandSurface::restackToTop() { } void CXWaylandSurface::close() { - xcb_client_message_data_t msg = {}; - msg.data32[0] = HYPRATOMS["WM_DELETE_WINDOW"]; - msg.data32[1] = XCB_CURRENT_TIME; - g_pXWayland->m_wm->sendWMMessage(m_self.lock(), &msg, XCB_EVENT_MASK_NO_EVENT); + + // Recheck the supported props, check if we maybe have WM_DELETE_WINDOW. + recheckSupportedProps(); + + if (m_supportedProps[HYPRATOMS["WM_DELETE_WINDOW"]]) { + xcb_client_message_data_t msg = {}; + msg.data32[0] = HYPRATOMS["WM_DELETE_WINDOW"]; + msg.data32[1] = XCB_CURRENT_TIME; + g_pXWayland->m_wm->sendWMMessage(m_self.lock(), &msg, XCB_EVENT_MASK_NO_EVENT); + } else { + xcb_kill_client(g_pXWayland->m_wm->getConnection(), m_self->m_xID); + xcb_flush(g_pXWayland->m_wm->getConnection()); + } } void CXWaylandSurface::setWithdrawn(bool withdrawn_) { diff --git a/src/xwayland/XSurface.hpp b/src/xwayland/XSurface.hpp index 10eecbaf..a8ccac4d 100644 --- a/src/xwayland/XSurface.hpp +++ b/src/xwayland/XSurface.hpp @@ -11,6 +11,7 @@ class CXWaylandSurfaceResource; #ifdef NO_XWAYLAND using xcb_pixmap_t = uint32_t; using xcb_window_t = uint32_t; +using xcb_atom_t = uint32_t; using xcb_icccm_wm_hints_t = struct { int32_t flags; uint32_t input; @@ -111,6 +112,7 @@ class CXWaylandSurface { void unmap(); void considerMap(); void setWithdrawn(bool withdrawn); + void recheckSupportedProps(); struct { CHyprSignalListener destroyResource; @@ -118,5 +120,7 @@ class CXWaylandSurface { CHyprSignalListener commitSurface; } m_listeners; + std::unordered_map m_supportedProps; + friend class CXWM; }; diff --git a/src/xwayland/XWM.cpp b/src/xwayland/XWM.cpp index 4e7fc5be..7e20d9b1 100644 --- a/src/xwayland/XWM.cpp +++ b/src/xwayland/XWM.cpp @@ -33,6 +33,8 @@ static int onX11Event(int fd, uint32_t mask, void* data) { return g_pXWayland->m_wm->onEvent(fd, mask); } +static int writeDataSource(int fd, uint32_t mask, void* data); + struct SFreeDeleter { void operator()(void* ptr) const { std::free(ptr); // NOLINT(cppcoreguidelines-no-malloc) @@ -66,6 +68,8 @@ void CXWM::handleCreate(xcb_create_notify_event_t* e) { } void CXWM::handleDestroy(xcb_destroy_notify_event_t* e) { + removeTransfersForWindow(e->window); + const auto XSURF = windowForXID(e->window); if (!XSURF) @@ -341,14 +345,17 @@ void CXWM::readProp(SP XSURF, uint32_t atom, xcb_get_property_ void CXWM::handlePropertyNotify(xcb_property_notify_event_t* e) { const auto XSURF = windowForXID(e->window); - if (!XSURF) + if (!XSURF) { + removeTransfersForWindow(e->window); return; + } xcb_get_property_cookie_t cookie = xcb_get_property(getConnection(), 0, XSURF->m_xID, e->atom, XCB_ATOM_ANY, 0, 2048); XCBReplyPtr reply(xcb_get_property_reply(getConnection(), cookie, nullptr)); if (!reply) { - Log::logger->log(Log::ERR, "[xwm] Failed to read property notify cookie"); + Log::logger->log(Log::ERR, "[xwm] Failed to read property notify cookie for window {}", e->window); + removeTransfersForWindow(e->window); return; } @@ -643,19 +650,33 @@ void CXWM::handleSelectionNotify(xcb_selection_notify_event_t* e) { } bool CXWM::handleSelectionPropertyNotify(xcb_property_notify_event_t* e) { - if (e->state != XCB_PROPERTY_DELETE) - return false; - - for (auto* sel : {&m_clipboard, &m_primarySelection}) { + for (auto* sel : {&m_clipboard, &m_primarySelection, &m_dndSelection}) { auto it = std::ranges::find_if(sel->transfers, [e](const auto& t) { return t->incomingWindow == e->window; }); - if (it != sel->transfers.end()) { - if (!(*it)->getIncomingSelectionProp(true)) { - sel->transfers.erase(it); - return false; + if (it == sel->transfers.end()) + continue; + + auto& transfer = *it; + + if (e->state == XCB_PROPERTY_NEW_VALUE) { + if (!transfer->incremental) { + getTransferData(*sel); + return true; } + + if (!transfer->getIncomingSelectionProp(true) || xcb_get_property_value_length(transfer->propertyReply) == 0) { + sel->transfers.erase(it); + return true; + } + + int result = sel->onWrite(); + + if (result == 1 && !transfer->eventSource) { + transfer->eventSource = wl_event_loop_add_fd(g_pCompositor->m_wlEventLoop, transfer->wlFD.get(), WL_EVENT_WRITABLE, ::writeDataSource, sel); + } + } else if (e->state == XCB_PROPERTY_DELETE) { getTransferData(*sel); - return true; } + return true; } return false; @@ -1069,8 +1090,8 @@ void CXWM::sendState(SP surf) { if (surf->m_fullscreen) props.push_back(HYPRATOMS["_NET_WM_STATE_FULLSCREEN"]); if (surf->m_maximized) { - props.push_back(HYPRATOMS["NET_WM_STATE_MAXIMIZED_VERT"]); - props.push_back(HYPRATOMS["NET_WM_STATE_MAXIMIZED_HORZ"]); + props.push_back(HYPRATOMS["_NET_WM_STATE_MAXIMIZED_VERT"]); + props.push_back(HYPRATOMS["_NET_WM_STATE_MAXIMIZED_HORZ"]); } if (surf->m_minimized) props.push_back(HYPRATOMS["_NET_WM_STATE_HIDDEN"]); @@ -1315,20 +1336,23 @@ void CXWM::getTransferData(SXSelection& sel) { return; } - const size_t transferIndex = std::distance(sel.transfers.begin(), it); - int writeResult = sel.onWrite(); + // Store window ID before onWrite() - transfer may be erased during the call + const xcb_window_t targetWindow = transfer->incomingWindow; + int writeResult = sel.onWrite(); if (writeResult != 1) return; - if (transferIndex >= sel.transfers.size()) + // Re-find the transfer by window ID (safe after potential vector modification) + auto updatedIt = std::ranges::find_if(sel.transfers, [targetWindow](const auto& t) { return t->incomingWindow == targetWindow; }); + if (updatedIt == sel.transfers.end()) return; - Hyprutils::Memory::CUniquePointer& updatedTransfer = sel.transfers[transferIndex]; + auto& updatedTransfer = *updatedIt; if (!updatedTransfer) return; - if (updatedTransfer->eventSource && updatedTransfer->wlFD.get() == -1) + if (updatedTransfer->eventSource || updatedTransfer->wlFD.get() == -1) return; updatedTransfer->eventSource = wl_event_loop_add_fd(g_pCompositor->m_wlEventLoop, updatedTransfer->wlFD.get(), WL_EVENT_WRITABLE, ::writeDataSource, &sel); @@ -1607,16 +1631,32 @@ int SXSelection::onWrite() { Log::logger->log(Log::DEBUG, "[xwm] cb transfer to wl client complete, read {} bytes", len); if (!transfer->incremental) { transfers.erase(it); + return 0; } else { free(transfer->propertyReply); // NOLINT(cppcoreguidelines-no-malloc) transfer->propertyReply = nullptr; transfer->propertyStart = 0; + if (transfer->eventSource) { + wl_event_source_remove(transfer->eventSource); + transfer->eventSource = nullptr; + } + return 0; } } return 1; } +void SXSelection::removeTransfer(xcb_window_t window) { + std::erase_if(transfers, [window](const auto& t) { return t->incomingWindow == window; }); +} + +void CXWM::removeTransfersForWindow(xcb_window_t window) { + m_clipboard.removeTransfer(window); + m_primarySelection.removeTransfer(window); + m_dndSelection.removeTransfer(window); +} + SXTransfer::~SXTransfer() { if (eventSource) wl_event_source_remove(eventSource); diff --git a/src/xwayland/XWM.hpp b/src/xwayland/XWM.hpp index 1a922a45..af1fa06a 100644 --- a/src/xwayland/XWM.hpp +++ b/src/xwayland/XWM.hpp @@ -35,9 +35,9 @@ struct SXTransfer { xcb_selection_request_event_t request; - int propertyStart; - xcb_get_property_reply_t* propertyReply; - xcb_window_t incomingWindow; + int propertyStart = 0; + xcb_get_property_reply_t* propertyReply = nullptr; + xcb_window_t incomingWindow = 0; bool getIncomingSelectionProp(bool erase); }; @@ -54,6 +54,7 @@ struct SXSelection { bool sendData(xcb_selection_request_event_t* e, std::string mime); int onRead(int fd, uint32_t mask); int onWrite(); + void removeTransfer(xcb_window_t window); struct { CHyprSignalListener setSelection; @@ -164,6 +165,8 @@ class CXWM { void handleFocusOut(xcb_focus_out_event_t* e); void handleError(xcb_value_error_t* e); + void removeTransfersForWindow(xcb_window_t window); + bool handleSelectionEvent(xcb_generic_event_t* e); void handleSelectionNotify(xcb_selection_notify_event_t* e); bool handleSelectionPropertyNotify(xcb_property_notify_event_t* e); diff --git a/start/src/core/Instance.cpp b/start/src/core/Instance.cpp index c89d9d0b..2f5007bd 100644 --- a/start/src/core/Instance.cpp +++ b/start/src/core/Instance.cpp @@ -1,17 +1,24 @@ #include "Instance.hpp" #include "State.hpp" #include "../helpers/Logger.hpp" +#include "../helpers/Nix.hpp" #include #include #include #include #include -#include #include #include #include +#if defined(__linux__) +#include +#elif defined(__FreeBSD__) +#include +#include +#endif + #include using namespace Hyprutils::OS; @@ -41,9 +48,19 @@ void CHyprlandInstance::runHyprlandThread(bool safeMode) { int forkRet = fork(); if (forkRet == 0) { // Make hyprland die on our SIGKILL +#if defined(__linux__) prctl(PR_SET_PDEATHSIG, SIGKILL); +#elif defined(__FreeBSD__) + int sig = SIGKILL; + procctl(P_PID, getpid(), PROC_PDEATHSIG_CTL, &sig); +#endif - execvp(g_state->customPath.value_or("Hyprland").c_str(), args.data()); + if (Nix::shouldUseNixGL()) { + argsStd.insert(argsStd.begin(), g_state->customPath.value_or("Hyprland")); + args.insert(args.begin(), strdup(argsStd.front().c_str())); + execvp("nixGL", args.data()); + } else + execvp(g_state->customPath.value_or("Hyprland").c_str(), args.data()); g_logger->log(Hyprutils::CLI::LOG_ERR, "fork(): execvp failed: {}", strerror(errno)); std::fflush(stdout); @@ -164,4 +181,4 @@ bool CHyprlandInstance::run(bool safeMode) { m_hlThread.join(); return !m_hyprlandInitialized || m_hyprlandExiting; -} \ No newline at end of file +} diff --git a/start/src/core/State.hpp b/start/src/core/State.hpp index 6cf73a96..6a44c8d0 100644 --- a/start/src/core/State.hpp +++ b/start/src/core/State.hpp @@ -8,6 +8,8 @@ struct SState { std::span rawArgvNoBinPath; std::optional customPath; + bool noNixGl = false; + bool forceNixGl = false; }; inline UP g_state = makeUnique(); \ No newline at end of file diff --git a/start/src/helpers/Nix.cpp b/start/src/helpers/Nix.cpp new file mode 100644 index 00000000..da66183e --- /dev/null +++ b/start/src/helpers/Nix.cpp @@ -0,0 +1,125 @@ +#include "Nix.hpp" + +#include "Logger.hpp" +#include "../core/State.hpp" + +#include +#include +#include +#include +#include +#include + +#include + +using namespace Hyprutils::String; +using namespace Hyprutils::OS; + +using namespace Hyprutils::File; + +static std::optional getFromEtcOsRelease(const std::string_view& sv) { + static std::string content = ""; + static bool once = true; + + if (once) { + once = false; + + auto read = readFileAsString("/etc/os-release"); + content = read.value_or(""); + } + + static CVarList2 vars(std::move(content), 0, '\n', true); + + for (const auto& v : vars) { + if (v.starts_with(sv) && v.contains('=')) { + // found + auto value = trim(v.substr(v.find('=') + 1)); + + if (value.back() == value.front() && value.back() == '"') + value = value.substr(1, value.size() - 2); + + return std::string{value}; + } + } + + return std::nullopt; +} + +static bool executableExistsInPath(const std::string& exe) { + const char* PATHENV = std::getenv("PATH"); + if (!PATHENV) + return false; + + CVarList2 paths(PATHENV, 0, ':', true); + std::error_code ec; + + for (const auto& PATH : paths) { + std::filesystem::path candidate = std::filesystem::path(PATH) / exe; + if (!std::filesystem::exists(candidate, ec) || ec) + continue; + if (!std::filesystem::is_regular_file(candidate, ec) || ec) + continue; + auto perms = std::filesystem::status(candidate, ec).permissions(); + if (ec) + continue; + if ((perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none) + return true; + } + + return false; +} + +std::expected Nix::nixEnvironmentOk() { + if (!shouldUseNixGL()) + return {}; + + if (!executableExistsInPath("nixGL")) + return std::unexpected( + "Hyprland was installed using Nix, but you're not on NixOS. This requires nixGL to be installed as well.\nYou can install nixGL by running \"nix profile install " + "github:guibou/nixGL --impure\" in your terminal."); + + return {}; +} + +bool Nix::shouldUseNixGL() { + if (g_state->forceNixGl) + return true; + + if (g_state->noNixGl) + return false; + + // check if installed hyprland is nix'd + CProcess proc("Hyprland", {"--version-json"}); + if (!proc.runSync()) { + g_logger->log(Hyprutils::CLI::LOG_ERR, "failed to obtain hyprland version string"); + return false; + } + + auto json = glz::read_json(proc.stdOut()); + if (!json) { + g_logger->log(Hyprutils::CLI::LOG_ERR, "failed to obtain hyprland version string (bad json)"); + return false; + } + + const auto FLAGS = (*json)["flags"].get_array(); + const bool IS_NIX = std::ranges::any_of(FLAGS, [](const auto& e) { return e.get_string() == std::string_view{"nix"}; }); + + if (IS_NIX) { + const auto NAME = getFromEtcOsRelease("NAME"); + + // Hyprland is nix: recommend nixGL iff !NIX && !NIX-OPENGL + + if (*NAME == "NixOS") + return false; + + std::error_code ec; + + if (std::filesystem::exists("/run/opengl-driver", ec) && !ec) // NOLINTNEXTLINE + return false; + + // we are not on nix / no nix opengl driver + return true; + } + + return false; +} diff --git a/start/src/helpers/Nix.hpp b/start/src/helpers/Nix.hpp new file mode 100644 index 00000000..edc01b19 --- /dev/null +++ b/start/src/helpers/Nix.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include +#include + +namespace Nix { + std::expected nixEnvironmentOk(); + bool shouldUseNixGL(); +}; \ No newline at end of file diff --git a/start/src/main.cpp b/start/src/main.cpp index 74de393c..45a78357 100644 --- a/start/src/main.cpp +++ b/start/src/main.cpp @@ -3,6 +3,7 @@ #include #include "helpers/Logger.hpp" +#include "helpers/Nix.hpp" #include "core/State.hpp" #include "core/Instance.hpp" @@ -16,7 +17,13 @@ using namespace Hyprutils::CLI; } constexpr const char* HELP_INFO = R"#(start-hyprland - A binary to properly start Hyprland via a watchdog process. -Any arguments after -- are passed to Hyprland. For Hyprland help, run start-hyprland -- --help or Hyprland --help)#"; +Any arguments after -- are passed to Hyprland. For Hyprland help, run start-hyprland -- --help or Hyprland --help + +Additional arguments for start-hyprland: + --path [path] -> Override Hyprland path + --no-nixgl -> Force disable nixGL + --force-nixgl -> Force enable nixGL +)#"; // static void onSignal(int sig) { @@ -69,6 +76,14 @@ int main(int argc, const char** argv, const char** envp) { g_state->customPath = argv[++i]; continue; } + if (arg == "--no-nixgl") { + g_state->noNixGl = true; + continue; + } + if (arg == "--force-nixgl") { + g_state->forceNixGl = true; + continue; + } } if (startArgv != -1) @@ -77,6 +92,15 @@ int main(int argc, const char** argv, const char** envp) { if (!g_state->rawArgvNoBinPath.empty()) g_logger->log(Hyprutils::CLI::LOG_WARN, "Arguments after -- are passed to Hyprland"); + // check if our environment is OK + if (const auto RET = Nix::nixEnvironmentOk(); !RET) { + g_logger->log(Hyprutils::CLI::LOG_ERR, "Nix environment check failed:\n{}", RET.error()); + return 1; + } + + if (Nix::shouldUseNixGL()) + g_logger->log(Hyprutils::CLI::LOG_DEBUG, "Hyprland was compiled with Nix - will use nixGL"); + bool safeMode = false; while (true) { g_instance = makeUnique(); diff --git a/subprojects/tracy b/subprojects/tracy index 37aff70d..05cceee0 160000 --- a/subprojects/tracy +++ b/subprojects/tracy @@ -1 +1 @@ -Subproject commit 37aff70dfa50cf6307b3fee6074d627dc2929143 +Subproject commit 05cceee0df3b8d7c6fa87e9638af311dbabc63cb diff --git a/tests/desktop/Reserved.cpp b/tests/desktop/Reserved.cpp index 8fbb7172..b3942e32 100644 --- a/tests/desktop/Reserved.cpp +++ b/tests/desktop/Reserved.cpp @@ -35,4 +35,18 @@ TEST(Desktop, reservedArea) { EXPECT_EQ(b.top(), 30 - 10); EXPECT_EQ(b.right(), 1010 - 920); EXPECT_EQ(b.bottom(), 1010 - 930); + + Desktop::CReservedArea c{CBox{}, CBox{20, 30, 900, 900}}; + + EXPECT_EQ(c.left(), 0); + EXPECT_EQ(c.top(), 0); + EXPECT_EQ(c.right(), 0); + EXPECT_EQ(c.bottom(), 0); + + Desktop::CReservedArea d{CBox{20, 30, 900, 900}, CBox{}}; + + EXPECT_EQ(d.left(), 0); + EXPECT_EQ(d.top(), 0); + EXPECT_EQ(d.right(), 0); + EXPECT_EQ(d.bottom(), 0); } \ No newline at end of file