diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 75b4b7c5..fda97f57 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,8 +1,6 @@ diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2ec558ed..d14ac02e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -46,38 +46,11 @@ 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 - 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 + uses: jidicula/clang-format-action@v4.16.0 + with: + exclude-regex: ^subprojects$ diff --git a/.github/workflows/clang-format-check.sh b/.github/workflows/clang-format-check.sh deleted file mode 100755 index 41237aa7..00000000 --- a/.github/workflows/clang-format-check.sh +++ /dev/null @@ -1,92 +0,0 @@ -#!/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 87574b82..db1fcfe4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -110,7 +110,6 @@ add_compile_options( -Wno-narrowing -Wno-pointer-arith -Wno-clobbered - -frtti -fmacro-prefix-map=${CMAKE_SOURCE_DIR}/=) # disable lto as it may break plugins @@ -125,7 +124,6 @@ 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) @@ -245,7 +243,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.91) +set(WAYLAND_SERVER_MINIMUM_VERSION 1.22.90) set(WAYLAND_SERVER_PROTOCOLS_MINIMUM_VERSION 1.45) set(LIBINPUT_MINIMUM_VERSION 1.28) @@ -267,8 +265,7 @@ pkg_check_modules( gbm gio-2.0 re2 - muparser - lcms2) + muparser) find_package(hyprwayland-scanner 0.3.10 REQUIRED) @@ -301,6 +298,21 @@ 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) @@ -309,23 +321,6 @@ 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() @@ -479,11 +474,7 @@ function(protocolWayland) set(PROTOCOL_SOURCES "${PROTOCOL_SOURCES}" PARENT_SCOPE) endfunction() -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() +target_link_libraries(hyprland_lib PUBLIC OpenGL::EGL OpenGL::GL Threads::Threads) pkg_check_modules(hyprland_protocols_dep hyprland-protocols>=0.6.4) if(hyprland_protocols_dep_FOUND) @@ -558,8 +549,6 @@ 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 efdec21a..e881cf92 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2022-2026, vaxerski +Copyright (c) 2022-2025, vaxerski All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/Makefile b/Makefile index 852fcddf..282258ed 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,6 @@ 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 @@ -88,7 +87,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 + 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 --build ./build --config Debug --target all @echo "Hyprland done" diff --git a/VERSION b/VERSION index 524456c7..7f422a16 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.54.0 +0.53.0 diff --git a/example/hyprland.conf b/example/hyprland.conf index 15b0e4f7..98ac0996 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, layoutmsg, togglesplit # dwindle +bind = $mainMod, J, togglesplit, # dwindle # Move focus with mainMod + arrow keys bind = $mainMod, left, movefocus, l diff --git a/flake.lock b/flake.lock index 4a89c0fc..cffa6fac 100644 --- a/flake.lock +++ b/flake.lock @@ -16,11 +16,11 @@ ] }, "locked": { - "lastModified": 1772292445, - "narHash": "sha256-4F1Q7U313TKUDDovCC96m/Za4wZcJ3yqtu4eSrj8lk8=", + "lastModified": 1765900596, + "narHash": "sha256-+hn8v9jkkLP9m+o0Nm5SiEq10W0iWDSotH2XfjU45fA=", "owner": "hyprwm", "repo": "aquamarine", - "rev": "1dbbba659c1cef0b0202ce92cadfe13bae550e8f", + "rev": "d83c97f8f5c0aae553c1489c7d9eff3eadcadace", "type": "github" }, "original": { @@ -32,15 +32,15 @@ "flake-compat": { "flake": false, "locked": { - "lastModified": 1767039857, - "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", - "owner": "NixOS", + "lastModified": 1761588595, + "narHash": "sha256-XKUZz9zewJNUj46b4AJdiRZJAvSZ0Dqj2BNfXvFlJC4=", + "owner": "edolstra", "repo": "flake-compat", - "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", + "rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5", "type": "github" }, "original": { - "owner": "NixOS", + "owner": "edolstra", "repo": "flake-compat", "type": "github" } @@ -105,11 +105,11 @@ ] }, "locked": { - "lastModified": 1770511807, - "narHash": "sha256-suKmSbSk34uPOJDTg/GbPrKEJutzK08vj0VoTvAFBCA=", + "lastModified": 1763733840, + "narHash": "sha256-JnET78yl5RvpGuDQy3rCycOCkiKoLr5DN1fPhRNNMco=", "owner": "hyprwm", "repo": "hyprgraphics", - "rev": "7c75487edd43a71b61adb01cae8326d277aab683", + "rev": "8f1bec691b2d198c60cccabca7a94add2df4ed1a", "type": "github" }, "original": { @@ -144,11 +144,11 @@ ] }, "locked": { - "lastModified": 1767023960, - "narHash": "sha256-R2HgtVS1G3KSIKAQ77aOZ+Q0HituOmPgXW9nBNkpp3Q=", + "lastModified": 1765643131, + "narHash": "sha256-CCGohW5EBIRy4B7vTyBMqPgsNcaNenVad/wszfddET0=", "owner": "hyprwm", "repo": "hyprland-guiutils", - "rev": "c2e906261142f5dd1ee0bfc44abba23e2754c660", + "rev": "e50ae912813bdfa8372d62daf454f48d6df02297", "type": "github" }, "original": { @@ -193,11 +193,11 @@ ] }, "locked": { - "lastModified": 1771866172, - "narHash": "sha256-fYFoXhQLrm1rD8vSFKQBOEX4OGCuJdLt1amKfHd5GAw=", + "lastModified": 1764612430, + "narHash": "sha256-54ltTSbI6W+qYGMchAgCR6QnC1kOdKXN6X6pJhOWxFg=", "owner": "hyprwm", "repo": "hyprlang", - "rev": "0b219224910e7642eb0ed49f0db5ec3d008e3e41", + "rev": "0d00dc118981531aa731150b6ea551ef037acddd", "type": "github" }, "original": { @@ -261,11 +261,11 @@ ] }, "locked": { - "lastModified": 1771271487, - "narHash": "sha256-41gEiUS0Pyw3L/ge1l8MXn61cK14VAhgWB/JV8s/oNI=", + "lastModified": 1766160771, + "narHash": "sha256-roINUGikWRqqgKrD4iotKbGj3ZKJl3hjMz5l/SyKrHw=", "owner": "hyprwm", "repo": "hyprutils", - "rev": "340a792e3b3d482c4ae5f66d27a9096bdee6d76d", + "rev": "5ac060bfcf2f12b3a6381156ebbc13826a05b09f", "type": "github" }, "original": { @@ -284,11 +284,11 @@ ] }, "locked": { - "lastModified": 1770501770, - "narHash": "sha256-NWRM6+YxTRv+bT9yvlhhJ2iLae1B1pNH3mAL5wi2rlQ=", + "lastModified": 1763640274, + "narHash": "sha256-Uan1Nl9i4TF/kyFoHnTq1bd/rsWh4GAK/9/jDqLbY5A=", "owner": "hyprwm", "repo": "hyprwayland-scanner", - "rev": "0bd8b6cde9ec27d48aad9e5b4deefb3746909d40", + "rev": "f6cf414ca0e16a4d30198fd670ec86df3c89f671", "type": "github" }, "original": { @@ -310,11 +310,11 @@ ] }, "locked": { - "lastModified": 1771606233, - "narHash": "sha256-F3PLUqQ/TwgR70U+UeOqJnihJZ2EuunzojYC4g5xHr0=", + "lastModified": 1766253200, + "narHash": "sha256-26qPwrd3od+xoYVywSB7hC2cz9ivN46VPLlrsXyGxvE=", "owner": "hyprwm", "repo": "hyprwire", - "rev": "06c7f1f8c4194786c8400653c4efc49dc14c0f3a", + "rev": "1079777525b30a947c8d657fac158e00ae85de9d", "type": "github" }, "original": { @@ -325,11 +325,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1772198003, - "narHash": "sha256-I45esRSssFtJ8p/gLHUZ1OUaaTaVLluNkABkk6arQwE=", + "lastModified": 1766070988, + "narHash": "sha256-G/WVghka6c4bAzMhTwT2vjLccg/awmHkdKSd2JrycLc=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "dd9b079222d43e1943b6ebd802f04fd959dc8e61", + "rev": "c6245e83d836d0433170a16eb185cefe0572f8b8", "type": "github" }, "original": { @@ -348,11 +348,11 @@ ] }, "locked": { - "lastModified": 1772024342, - "narHash": "sha256-+eXlIc4/7dE6EcPs9a2DaSY3fTA9AE526hGqkNID3Wg=", + "lastModified": 1765911976, + "narHash": "sha256-t3T/xm8zstHRLx+pIHxVpQTiySbKqcQbK+r+01XVKc0=", "owner": "cachix", "repo": "git-hooks.nix", - "rev": "6e34e97ed9788b17796ee43ccdbaf871a5c2b476", + "rev": "b68b780b69702a090c8bb1b973bab13756cc7a27", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 6d695bfb..21561cc5 100644 --- a/flake.nix +++ b/flake.nix @@ -88,122 +88,108 @@ }; }; - 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 3d26a102..fa2edc0a 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,13 +62,6 @@ Destroys this object. Children remain alive until destroyed. - - - - Creates a status object - - - @@ -148,25 +141,4 @@ - - - - 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 93f4182a..afa7f653 100644 --- a/hyprctl/src/hyprpaper/Hyprpaper.cpp +++ b/hyprctl/src/hyprpaper/Hyprpaper.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include @@ -16,7 +15,7 @@ using namespace std::string_literals; constexpr const char* SOCKET_NAME = ".hyprpaper.sock"; static SP g_coreImpl; -constexpr const uint32_t PROTOCOL_VERSION_SUPPORTED = 2; +constexpr const uint32_t PROTOCOL_VERSION_SUPPORTED = 1; // static hyprpaperCoreWallpaperFitMode fitFromString(const std::string_view& sv) { @@ -54,7 +53,21 @@ static std::expected getFullPath(const std::string_vie return resolvePath(sv); } -static std::expected doWallpaper(const std::string_view& RHS) { +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"); + CVarList2 args(std::string{RHS}, 0, ','); const std::string MONITOR = std::string{args[0]}; @@ -86,7 +99,7 @@ static std::expected doWallpaper(const std::string_view& RHS) if (!socket) return std::unexpected("can't send: failed to connect to hyprpaper (is it running?)"); - g_coreImpl = makeShared(PROTOCOL_VERSION_SUPPORTED); + g_coreImpl = makeShared(1); socket->addImplementation(g_coreImpl); @@ -98,7 +111,7 @@ static std::expected doWallpaper(const std::string_view& RHS) if (!spec) return std::unexpected("can't send: hyprpaper doesn't have the spec?!"); - auto manager = makeShared(socket->bindProtocol(g_coreImpl->protocol(), std::min(PROTOCOL_VERSION_SUPPORTED, spec->specVer()))); + auto manager = makeShared(socket->bindProtocol(g_coreImpl->protocol(), PROTOCOL_VERSION_SUPPORTED)); if (!manager) return std::unexpected("wire error: couldn't create manager"); @@ -113,11 +126,7 @@ static std::expected doWallpaper(const std::string_view& RHS) wallpaper->setFailed([&canExit, &err](uint32_t code) { canExit = true; - 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; - } + err = std::format("failed to set wallpaper, code {}", code); }); wallpaper->setSuccess([&canExit]() { canExit = true; }); @@ -136,73 +145,4 @@ static std::expected doWallpaper(const std::string_view& RHS) return std::unexpected(*err); return {}; -} - -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 {}; -} +} \ No newline at end of file diff --git a/hyprctl/src/main.cpp b/hyprctl/src/main.cpp index 0a33f3ed..7146c635 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}; - // 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); + 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) { + 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 7d5b8eda..ee738104 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.1 QUIET) +find_package(glaze 6.0.0 QUIET) if (NOT glaze_FOUND) - set(GLAZE_VERSION v6.0.1) + set(GLAZE_VERSION v6.1.0) message(STATUS "glaze dependency not found, retrieving ${GLAZE_VERSION} with FetchContent") include(FetchContent) FetchContent_Declare( @@ -21,7 +21,6 @@ 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 6621a49f..0d35b4ae 100644 --- a/hyprpm/src/core/PluginManager.cpp +++ b/hyprpm/src/core/PluginManager.cpp @@ -94,18 +94,15 @@ 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 {}, nix: {}", hlcommit, hlbranch, hldate, commits, isNix)); + std::println("{}", verboseString("parsed commit {} at branch {} on {}, commits {}", hlcommit, hlbranch, hldate, commits)); - auto ver = SHyprlandVersion{hlbranch, hlcommit, hldate, abiHash, commits, isNix}; + auto ver = SHyprlandVersion{hlbranch, hlcommit, hldate, abiHash, commits}; if (running) verRunning = ver; @@ -131,20 +128,12 @@ 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, cpio, pkg-config, git, g++, gcc")); + std::println(stderr, "\n{}", + failureString("Could not clone the plugin repository. Dependencies not satisfied. Hyprpm requires: cmake, meson, cpio, pkg-config, git, g++, gcc")); return false; } @@ -207,7 +196,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)); @@ -314,14 +303,8 @@ bool CPluginManager::addNewPluginRepo(const std::string& url, const std::string& progress.printMessageAbove(infoString("Building {}", p.name)); for (auto const& bs : p.buildSteps) { - 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"; + const std::string& cmd = std::format("cd {} && PKG_CONFIG_PATH=\"{}/share/pkgconfig\" {}", m_szWorkingPluginDirectory, DataState::getHeadersPath(), bs); + out += " -> " + cmd + "\n" + execAndGet(cmd) + "\n"; } if (m_bVerbose) @@ -406,7 +389,7 @@ eHeadersErrors CPluginManager::headersValid() { return HEADERS_MISSING; // find headers commit - const std::string& cmd = std::format("PKG_CONFIG_PATH=\"{}\" pkgconf --cflags --keep-system-cflags hyprland", getPkgConfigPath()); + const std::string& cmd = std::format("PKG_CONFIG_PATH=\"{}/share/pkgconfig\" pkgconf --cflags --keep-system-cflags hyprland", DataState::getHeadersPath()); auto headers = execAndGet(cmd); if (!headers.contains("-I/")) @@ -470,7 +453,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, cpio, pkg-config, git, g++, gcc")); + std::println("\n{}", failureString("Could not update. Dependencies not satisfied. Hyprpm requires: cmake, meson, cpio, pkg-config, git, g++, gcc")); return false; } @@ -512,11 +495,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")) { @@ -559,17 +542,8 @@ bool CPluginManager::updateHeaders(bool force) { if (m_bVerbose) progress.printMessageAbove(verboseString("setting PREFIX for cmake to {}", 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); + ret = execAndGet(std::format("cd {} && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_INSTALL_PREFIX:STRING=\"{}\" -S . -B ./build -G Ninja", WORKINGDIR, + DataState::getHeadersPath())); if (m_bVerbose) progress.printMessageAbove(verboseString("cmake returned: {}", ret)); @@ -657,7 +631,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(); @@ -678,7 +652,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)); @@ -688,7 +662,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)); @@ -767,14 +741,8 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) { progress.printMessageAbove(infoString("Building {}", p.name)); for (auto const& bs : p.buildSteps) { - 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"; + const std::string& cmd = std::format("cd {} && PKG_CONFIG_PATH=\"{}/share/pkgconfig\" {}", m_szWorkingPluginDirectory, DataState::getHeadersPath(), bs); + out += " -> " + cmd + "\n" + execAndGet(cmd) + "\n"; } if (m_bVerbose) @@ -804,8 +772,8 @@ bool CPluginManager::updatePlugins(bool forceUpdateAll) { repohash.pop_back(); newrepo.hash = repohash; for (auto const& p : pManifest->m_plugins) { - 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}); + 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}); } DataState::removePluginRepo(SPluginRepoIdentifier::fromName(newrepo.name)); DataState::addNewPluginRepo(newrepo); @@ -931,7 +899,7 @@ ePluginLoadStateReturn CPluginManager::ensurePluginsLoadState(bool forceReload) if (!p.enabled) continue; - if (!forceReload && std::ranges::find_if(loadedPlugins, [&](const auto& other) { return other == p.name; }) != loadedPlugins.end()) + if (!forceReload && std::find_if(loadedPlugins.begin(), loadedPlugins.end(), [&](const auto& other) { return other == p.name; }) != loadedPlugins.end()) continue; if (!loadUnloadPlugin(HYPRPMPATH / repoForName(p.name) / p.filename, true)) { @@ -1019,11 +987,8 @@ 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 = {"cpio", "cmake", "pkg-config", "g++", "gcc", "git"}; + std::vector deps = {"meson", "cpio", "cmake", "pkg-config", "g++", "gcc", "git"}; for (auto const& d : deps) { if (!execAndGet("command -v " + d).contains("/")) { @@ -1034,92 +999,3 @@ 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 25878f54..10a71469 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,7 +41,6 @@ struct SHyprlandVersion { std::string date; std::string abiHash; int commits = 0; - bool isNix = false; }; class CPluginManager { @@ -66,26 +65,20 @@ 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; - bool m_bNoNix = false; - std::string m_szCustomHlUrl, m_szUsername, m_szArgv0; + std::string m_szCustomHlUrl, m_szUsername; // will delete recursively if exists!! bool createSafeDirectory(const std::string& path); private: - std::string headerError(const eHeadersErrors err); - std::string headerErrorShort(const eHeadersErrors err); - bool validArg(const std::string& s); + std::string headerError(const eHeadersErrors err); + std::string headerErrorShort(const eHeadersErrors err); - std::expected nixDevelopIfNeeded(const std::string& cmd, const SHyprlandVersion& ver); - - std::string m_szWorkingPluginDirectory; + 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 e9dd4c85..c18d4748 100644 --- a/hyprpm/src/helpers/Sys.cpp +++ b/hyprpm/src/helpers/Sys.cpp @@ -35,13 +35,9 @@ 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 std::nullopt; + return false; CVarList paths(PATHENV, 0, ':', true); std::error_code ec; @@ -56,10 +52,10 @@ std::optional NSys::findInPath(const std::string& exe) { if (ec) continue; if ((perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none) - return candidate.string(); + return true; } - return std::nullopt; + return false; } static std::string subin() { diff --git a/hyprpm/src/helpers/Sys.hpp b/hyprpm/src/helpers/Sys.hpp index 03d5a0de..b44eb758 100644 --- a/hyprpm/src/helpers/Sys.hpp +++ b/hyprpm/src/helpers/Sys.hpp @@ -1,13 +1,11 @@ #pragma once #include -#include namespace NSys { - bool isSuperuser(); - int getUID(); - int getEUID(); - std::optional findInPath(const std::string& exe); + bool isSuperuser(); + int getUID(); + int getEUID(); // NOLINTNEXTLINE namespace root { @@ -22,4 +20,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 f5e14bbb..dced58e7 100644 --- a/hyprpm/src/main.cpp +++ b/hyprpm/src/main.cpp @@ -25,7 +25,6 @@ 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. @@ -48,7 +47,7 @@ int main(int argc, char** argv, char** envp) { } std::vector command; - bool notify = false, verbose = false, force = false, noShallow = false, noNix = false; + bool notify = false, verbose = false, force = false, noShallow = false; std::string customHlUrl; for (int i = 1; i < argc; ++i) { @@ -64,8 +63,6 @@ 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") { @@ -94,9 +91,7 @@ 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 f17f73b1..d771c658 100644 --- a/hyprtester/CMakeLists.txt +++ b/hyprtester/CMakeLists.txt @@ -96,9 +96,7 @@ 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") -clientNew("shortcut-inhibitor" PROTOS "xdg-shell" "keyboard-shortcuts-inhibit-unstable-v1") +clientNew("child-window" PROTOS "xdg-shell") \ No newline at end of file diff --git a/hyprtester/clients/child-window.cpp b/hyprtester/clients/child-window.cpp index 5f66be6b..30bc3fe1 100644 --- a/hyprtester/clients/child-window.cpp +++ b/hyprtester/clients/child-window.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -333,4 +332,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 59120961..140e4700 100644 --- a/hyprtester/clients/pointer-scroll.cpp +++ b/hyprtester/clients/pointer-scroll.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include #include #include diff --git a/hyprtester/clients/pointer-warp.cpp b/hyprtester/clients/pointer-warp.cpp index a57f99ae..2d3624d5 100644 --- a/hyprtester/clients/pointer-warp.cpp +++ b/hyprtester/clients/pointer-warp.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #include #include #include diff --git a/hyprtester/clients/shortcut-inhibitor.cpp b/hyprtester/clients/shortcut-inhibitor.cpp deleted file mode 100644 index 0c6b4341..00000000 --- a/hyprtester/clients/shortcut-inhibitor.cpp +++ /dev/null @@ -1,297 +0,0 @@ -#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 ce83c5b4..d8f3c971 100644 --- a/hyprtester/plugin/src/main.cpp +++ b/hyprtester/plugin/src/main.cpp @@ -6,16 +6,15 @@ #define private public #include #include +#include +#include #include #include #include #include -#include #include -#include #include #include -#include #undef private #include @@ -54,9 +53,8 @@ static SDispatchResult snapMove(std::string in) { Vector2D pos = PLASTWINDOW->m_realPosition->goal(); Vector2D size = PLASTWINDOW->m_realSize->goal(); - g_layoutManager->performSnap(pos, size, PLASTWINDOW->layoutTarget(), MBIND_MOVE, -1, size); - - PLASTWINDOW->layoutTarget()->setPositionGlobal(CBox{pos, size}); + g_pLayoutManager->getCurrentLayout()->performSnap(pos, size, PLASTWINDOW, MBIND_MOVE, -1, size); + *PLASTWINDOW->m_realPosition = pos.round(); return {}; } @@ -272,85 +270,32 @@ static SDispatchResult keybind(std::string in) { return {}; } -static Desktop::Rule::CWindowRuleEffectContainer::storageType windowRuleIDX = 0; +static Desktop::Rule::CWindowRuleEffectContainer::storageType ruleIDX = 0; // -static SDispatchResult addWindowRule(std::string in) { - windowRuleIDX = Desktop::Rule::windowEffects()->registerEffect("plugin_rule"); +static SDispatchResult addRule(std::string in) { + ruleIDX = Desktop::Rule::windowEffects()->registerEffect("plugin_rule"); - if (Desktop::Rule::windowEffects()->registerEffect("plugin_rule") != windowRuleIDX) + if (Desktop::Rule::windowEffects()->registerEffect("plugin_rule") != ruleIDX) return {.success = false, .error = "re-registering returned a different id?"}; return {}; } -static SDispatchResult checkWindowRule(std::string in) { +static SDispatchResult checkRule(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(windowRuleIDX)) + if (!PLASTWINDOW->m_ruleApplicator->m_otherProps.props.contains(ruleIDX)) return {.success = false, .error = "No rule"}; - if (PLASTWINDOW->m_ruleApplicator->m_otherProps.props[windowRuleIDX]->effect != "effect") + if (PLASTWINDOW->m_ruleApplicator->m_otherProps.props[ruleIDX]->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; @@ -362,11 +307,8 @@ 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_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); + HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:add_rule", ::addRule); + HyprlandAPI::addDispatcherV2(PHANDLE, "plugin:test:check_rule", ::checkRule); // init mouse g_mouse = CTestMouse::create(false); diff --git a/hyprtester/src/shared.hpp b/hyprtester/src/shared.hpp index 941788fd..1090aa9a 100644 --- a/hyprtester/src/shared.hpp +++ b/hyprtester/src/shared.hpp @@ -39,16 +39,6 @@ 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 a5680d4f..1740b029 100644 --- a/hyprtester/src/tests/clients/child-window.cpp +++ b/hyprtester/src/tests/clients/child-window.cpp @@ -8,7 +8,6 @@ #include #include -#include #include #include @@ -118,34 +117,7 @@ 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); +REGISTER_CLIENT_TEST_FN(test); \ No newline at end of file diff --git a/hyprtester/src/tests/clients/pointer-scroll.cpp b/hyprtester/src/tests/clients/pointer-scroll.cpp index b5fb68fb..2ea93a14 100644 --- a/hyprtester/src/tests/clients/pointer-scroll.cpp +++ b/hyprtester/src/tests/clients/pointer-scroll.cpp @@ -8,7 +8,6 @@ #include #include -#include #include #include @@ -43,7 +42,6 @@ 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]); @@ -64,16 +62,7 @@ static bool startClient(SClient& client) { } // 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("{}pointer-scroll client took too long to open", Colors::RED); - return false; - } - } + std::this_thread::sleep_for(std::chrono::milliseconds(5000)); 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 be992566..bb03afd2 100644 --- a/hyprtester/src/tests/clients/pointer-warp.cpp +++ b/hyprtester/src/tests/clients/pointer-warp.cpp @@ -8,7 +8,6 @@ #include #include -#include #include #include @@ -43,7 +42,6 @@ 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]); @@ -64,16 +62,7 @@ static bool startClient(SClient& client) { } // 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("{}pointer-warp client took too long to open", Colors::RED); - return false; - } - } + std::this_thread::sleep_for(std::chrono::milliseconds(5000)); 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 deleted file mode 100644 index 91c3376c..00000000 --- a/hyprtester/src/tests/clients/shortcut-inhibitor.cpp +++ /dev/null @@ -1,180 +0,0 @@ -#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 234bfc33..8f17c815 100644 --- a/hyprtester/src/tests/main/dwindle.cpp +++ b/hyprtester/src/tests/main/dwindle.cpp @@ -34,197 +34,6 @@ 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() { @@ -234,15 +43,6 @@ 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 a410494a..fd42cf06 100644 --- a/hyprtester/src/tests/main/exec.cpp +++ b/hyprtester/src/tests/main/exec.cpp @@ -2,7 +2,6 @@ #include "../../shared.hpp" #include "../../hyprctlCompat.hpp" #include -#include #include #include #include @@ -16,46 +15,40 @@ using namespace Hyprutils::Memory; #define UP CUniquePointer #define SP CSharedPointer -const static auto SLEEP_DURATIONS = std::array{1, 10}; - -static bool test() { +static bool test() { NLog::log("{}Testing process spawning", Colors::GREEN); - 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))); + // Note: POSIX sleep does not support fractional seconds, so + // can't sleep for less than 1 second. + OK(getFromSocket("/dispatch exec sleep 1")); - // 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; + // 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; } - return false; + 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; } REGISTER_TEST_FN(test) diff --git a/hyprtester/src/tests/main/groups.cpp b/hyprtester/src/tests/main/groups.cpp index 2f9c5062..3cf15851 100644 --- a/hyprtester/src/tests/main/groups.cpp +++ b/hyprtester/src/tests/main/groups.cpp @@ -127,34 +127,6 @@ 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")); @@ -201,99 +173,6 @@ 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 e5e6f1fc..e8759d28 100644 --- a/hyprtester/src/tests/main/hyprctl.cpp +++ b/hyprtester/src/tests/main/hyprctl.cpp @@ -77,16 +77,6 @@ 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 deleted file mode 100644 index 73e30ba6..00000000 --- a/hyprtester/src/tests/main/layer.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#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 deleted file mode 100644 index 186d7034..00000000 --- a/hyprtester/src/tests/main/layout.cpp +++ /dev/null @@ -1,127 +0,0 @@ -#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 9aaa4cf0..9cd20e83 100644 --- a/hyprtester/src/tests/main/master.cpp +++ b/hyprtester/src/tests/main/master.cpp @@ -3,53 +3,7 @@ #include "../../hyprctlCompat.hpp" #include "tests.hpp" -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 int ret = 0; static void focusMasterPrevious() { // setup @@ -90,74 +44,11 @@ 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); @@ -169,9 +60,6 @@ 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 471eef7a..3187694b 100644 --- a/hyprtester/src/tests/main/misc.cpp +++ b/hyprtester/src/tests/main/misc.cpp @@ -18,121 +18,6 @@ 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 deleted file mode 100644 index 8bb950dd..00000000 --- a/hyprtester/src/tests/main/scroll.cpp +++ /dev/null @@ -1,228 +0,0 @@ -#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 beac0298..37442790 100644 --- a/hyprtester/src/tests/main/window.cpp +++ b/hyprtester/src/tests/main/window.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -25,28 +24,6 @@ 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) { @@ -93,9 +70,9 @@ static void testSwapWindow() { { getFromSocket("/dispatch focuswindow class:kitty_A"); auto pos = getWindowAttribute(getFromSocket("/activewindow"), "at:"); - NLog::log("{}Testing kitty_A {}, swapwindow with direction 'r'", Colors::YELLOW, pos); + NLog::log("{}Testing kitty_A {}, swapwindow with direction 'l'", Colors::YELLOW, pos); - OK(getFromSocket("/dispatch swapwindow r")); + OK(getFromSocket("/dispatch swapwindow l")); OK(getFromSocket("/dispatch focuswindow class:kitty_B")); EXPECT_CONTAINS(getFromSocket("/activewindow"), std::format("{}", pos)); @@ -220,7 +197,7 @@ static void testGroupRules() { Tests::killAllWindows(); } -static bool isActiveWindow(const std::string& class_, char fullscreen = '0', bool log = true) { +static bool isActiveWindow(const std::string& class_, char fullscreen, bool log = true) { std::string activeWin = getFromSocket("/activewindow"); auto winClass = getWindowAttribute(activeWin, "class:"); auto winFullscreen = getWindowAttribute(activeWin, "fullscreen:").back(); @@ -233,13 +210,13 @@ static bool isActiveWindow(const std::string& class_, char fullscreen = '0', boo } } -static bool waitForActiveWindow(const std::string& class_, char fullscreen = '0', bool logLastCheck = true, int maxTries = 50) { +static bool waitForActiveWindow(const std::string& class_, char fullscreen, 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, logLastCheck); + return isActiveWindow(class_, fullscreen, true); } } return true; @@ -255,6 +232,24 @@ 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")); @@ -378,61 +373,6 @@ 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); @@ -472,192 +412,6 @@ 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); @@ -771,14 +525,14 @@ static bool test() { EXPECT(Tests::windowCount(), 3); NLog::log("{}Checking props of xeyes", Colors::YELLOW); - // check some window props of xeyes, try to float it + // check some window props of xeyes, try to tile them { auto str = getFromSocket("/clients"); - EXPECT_NOT_CONTAINS(str, "floating: 1"); - getFromSocket("/dispatch setfloating class:XEyes"); + EXPECT_CONTAINS(str, "floating: 1"); + getFromSocket("/dispatch settiled class:XEyes"); std::this_thread::sleep_for(std::chrono::milliseconds(200)); str = getFromSocket("/clients"); - EXPECT_CONTAINS(str, "floating: 1"); + EXPECT_NOT_CONTAINS(str, "floating: 1"); } // kill all @@ -962,23 +716,6 @@ 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; @@ -1062,8 +799,7 @@ 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, 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")); + 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")); if (!spawnKitty("expr_kitty")) return false; @@ -1073,20 +809,12 @@ 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_window_rule")); + OK(getFromSocket("/dispatch plugin:test:add_rule")); OK(getFromSocket("/reload")); OK(getFromSocket("/keyword windowrule match:class plugin_kitty, plugin_rule effect")); @@ -1094,12 +822,12 @@ static bool test() { if (!spawnKitty("plugin_kitty")) return false; - OK(getFromSocket("/dispatch plugin:test:check_window_rule")); + OK(getFromSocket("/dispatch plugin:test:check_rule")); OK(getFromSocket("/reload")); Tests::killAllWindows(); - OK(getFromSocket("/dispatch plugin:test:add_window_rule")); + OK(getFromSocket("/dispatch plugin:test:add_rule")); OK(getFromSocket("/reload")); OK(getFromSocket("/keyword windowrule[test-plugin-rule]:match:class plugin_kitty")); @@ -1108,20 +836,16 @@ static bool test() { if (!spawnKitty("plugin_kitty")) return false; - OK(getFromSocket("/dispatch plugin:test:check_window_rule")); + OK(getFromSocket("/dispatch plugin:test:check_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 122cd619..c1b9690a 100644 --- a/hyprtester/src/tests/main/workspaces.cpp +++ b/hyprtester/src/tests/main/workspaces.cpp @@ -20,91 +20,6 @@ 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); { @@ -193,206 +108,6 @@ 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); @@ -727,19 +442,14 @@ 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 f6e2fce9..8cdd648e 100644 --- a/hyprtester/src/tests/shared.cpp +++ b/hyprtester/src/tests/shared.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include "../shared.hpp" #include "../hyprctlCompat.hpp" @@ -40,38 +39,6 @@ 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); @@ -129,38 +96,6 @@ 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}); @@ -170,14 +105,3 @@ 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 bf875f8b..fe28a69d 100644 --- a/hyprtester/src/tests/shared.hpp +++ b/hyprtester/src/tests/shared.hpp @@ -9,14 +9,10 @@ //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 ab4f8ee3..ac28bc5a 100644 --- a/hyprtester/test.conf +++ b/hyprtester/test.conf @@ -179,17 +179,6 @@ 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 @@ -250,7 +239,7 @@ bind = $mainMod, E, exec, $fileManager bind = $mainMod, V, togglefloating, bind = $mainMod, R, exec, $menu bind = $mainMod, P, pseudo, # dwindle -bind = $mainMod, J, layoutmsg, togglesplit, # dwindle +bind = $mainMod, J, togglesplit, # dwindle # Move focus with mainMod + arrow keys bind = $mainMod, left, movefocus, l @@ -409,5 +398,3 @@ 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 bc5ba309..54776871 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -12,7 +12,6 @@ epoll-shim, git, glaze-hyprland, - glslang, gtest, hyprcursor, hyprgraphics, @@ -22,17 +21,10 @@ hyprutils, hyprwayland-scanner, hyprwire, - lcms2, libGL, libdrm, libexecinfo, libinput, - libxcb, - libxcb-errors, - libxcb-render-util, - libxcb-wm, - libxdmcp, - libxcursor, libxkbcommon, libuuid, libgbm, @@ -46,6 +38,7 @@ wayland, wayland-protocols, wayland-scanner, + xorg, xwayland, debug ? false, withTests ? false, @@ -66,20 +59,8 @@ 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 [ @@ -91,8 +72,7 @@ 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: { @@ -105,29 +85,23 @@ 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 - ../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 - ]) - ]) - ); + (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]) + ])); }; postPatch = '' @@ -143,10 +117,7 @@ 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")}"; }; @@ -174,7 +145,6 @@ in cairo git glaze-hyprland - glslang gtest hyprcursor hyprgraphics @@ -182,14 +152,12 @@ in hyprlang hyprutils hyprwire - lcms2 libdrm - libgbm libGL libinput libuuid - libxcursor libxkbcommon + libgbm muparser pango pciutils @@ -199,15 +167,16 @@ in wayland wayland-protocols wayland-scanner + xorg.libXcursor ] (optionals customStdenv.hostPlatform.isBSD [epoll-shim]) (optionals customStdenv.hostPlatform.isMusl [libexecinfo]) (optionals enableXWayland [ - libxcb - libxcb-errors - libxcb-render-util - libxcb-wm - libxdmcp + xorg.libxcb + xorg.libXdmcp + xorg.xcbutilerrors + xorg.xcbutilrenderutil + xorg.xcbutilwm xwayland ]) (optional withSystemd systemd) @@ -230,6 +199,7 @@ in "NO_SYSTEMD" = !withSystemd; "CMAKE_DISABLE_PRECOMPILE_HEADERS" = true; "NO_UWSM" = !withSystemd; + "NO_HYPRPM" = true; "TRACY_ENABLE" = false; "WITH_TESTS" = withTests; }; @@ -243,26 +213,23 @@ 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"] ++ optionals withSystemd ["hyprland-uwsm"]; + passthru.providedSessions = ["hyprland"]; meta = { homepage = "https://github.com/hyprwm/Hyprland"; diff --git a/nix/formatter.nix b/nix/formatter.nix index ac340ae2..66721c2c 100644 --- a/nix/formatter.nix +++ b/nix/formatter.nix @@ -2,7 +2,7 @@ writeShellApplication, deadnix, statix, - nixfmt, + alejandra, llvmPackages_19, fd, }: @@ -11,7 +11,7 @@ writeShellApplication { runtimeInputs = [ deadnix statix - nixfmt + alejandra 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 nixfmt {} \; + fd '.*\.nix' . -E "$excludes" -X deadnix -e -- {} \; -X alejandra {} \; elif [ -d "$1" ]; then fd '.*\.nix' "$1" -E "$excludes" -i -x statix fix -- {} \; - fd '.*\.nix' "$1" -E "$excludes" -i -X deadnix -e -- {} \; -X nixfmt {} \; + fd '.*\.nix' "$1" -E "$excludes" -i -X deadnix -e -- {} \; -X alejandra {} \; else statix fix -- "$1" deadnix -e "$1" - nixfmt "$1" + alejandra "$1" fi } diff --git a/nix/hm-module.nix b/nix/hm-module.nix index 948b8217..e3c788d0 100644 --- a/nix/hm-module.nix +++ b/nix/hm-module.nix @@ -1,15 +1,13 @@ -self: -{ +self: { + config, 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 54d23440..ca3aadee 100644 --- a/nix/lib.nix +++ b/nix/lib.nix @@ -1,5 +1,4 @@ -lib: -let +lib: let inherit (lib) attrNames filterAttrs @@ -18,7 +17,7 @@ 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. @@ -82,51 +81,44 @@ 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; - in - # Concatenate strings from mapping `mkCommands` over top, regular, and bottom commands. - concatMapStrings mkCommands [ - topCommands - regularCommands - bottomCommands - ]; + # 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 toHyprlang' attrs; /** @@ -139,7 +131,7 @@ let Configuration: * `pred` - A function `(string -> string -> string)` defining how keys should be concatenated. - + # Inputs Structured function argument: @@ -147,7 +139,7 @@ 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. @@ -182,21 +174,26 @@ 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 32263943..91705347 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -1,21 +1,18 @@ -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. @@ -23,25 +20,23 @@ 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 @@ -97,15 +92,8 @@ 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. ''; @@ -113,8 +101,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. ''; @@ -129,36 +117,35 @@ 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 - { + 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 { 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 { - topCommandsPrefixes = cfg.topPrefixes; - bottomCommandsPrefixes = cfg.bottomPrefixes; - } cfg.settings - ) + cfg.settings) + lib.optionalString (cfg.extraConfig != "") cfg.extraConfig; }; }) diff --git a/nix/overlays.nix b/nix/overlays.nix index 0d157701..fdb3e652 100644 --- a/nix/overlays.nix +++ b/nix/overlays.nix @@ -2,27 +2,20 @@ 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 [ @@ -40,45 +33,49 @@ 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 @@ -86,10 +83,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;}; }) ]; @@ -103,23 +100,21 @@ 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 25c4077b..bdb3fe7c 100644 --- a/nix/tests/default.nix +++ b/nix/tests/default.nix @@ -1,76 +1,69 @@ -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 - xeyes - ]; + nodes.machine = {pkgs, ...}: { + environment.systemPackages = with pkgs; [ + # Programs needed for tests + jq + kitty + wl-clipboard + xorg.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 - 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" ]; - }; + 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; + }; + + # 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 machine.wait_for_unit("multi-user.target") diff --git a/scripts/generateShaderIncludes.sh b/scripts/generateShaderIncludes.sh index c9419031..20c78e9d 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 -n 'R"#('; cat ${SHADERS_SRC}/${filename}; echo ')#"'; } > ./src/render/shaders/${filename}.inc + { echo '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 b0fc1545..772f87fe 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -7,7 +7,6 @@ #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" @@ -20,7 +19,6 @@ #include "managers/ANRManager.hpp" #include "managers/eventLoop/EventLoopManager.hpp" #include "managers/permissions/DynamicPermissionManager.hpp" -#include "managers/screenshare/ScreenshareManager.hpp" #include #include #include @@ -47,7 +45,6 @@ #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" @@ -63,7 +60,9 @@ #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" @@ -72,9 +71,6 @@ #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 @@ -107,6 +103,11 @@ 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"; @@ -282,6 +283,7 @@ 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(); @@ -370,11 +372,11 @@ void CCompositor::initServer(std::string socketName, int socketFd) { return ret == 0 && cap != 0; }; - m_drm.syncobjSupport = syncObjSupport(m_drm.fd); - Log::logger->log(Log::DEBUG, "DRM DisplayNode syncobj timeline support: {}", m_drm.syncobjSupport ? "yes" : "no"); + if ((m_drm.syncobjSupport = syncObjSupport(m_drm.fd))) + Log::logger->log(Log::DEBUG, "DRM DisplayNode syncobj timeline support: {}", m_drm.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_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"); @@ -481,10 +483,6 @@ 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); @@ -588,10 +586,11 @@ void CCompositor::cleanup() { g_pHyprRenderer.reset(); g_pHyprOpenGL.reset(); g_pConfigManager.reset(); - g_layoutManager.reset(); + g_pLayoutManager.reset(); g_pHyprError.reset(); g_pConfigManager.reset(); g_pKeybindManager.reset(); + g_pHookSystem.reset(); g_pXWaylandManager.reset(); g_pPointerManager.reset(); g_pSeatManager.reset(); @@ -620,6 +619,9 @@ 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(); @@ -636,7 +638,7 @@ void CCompositor::initManagers(eManagersInitStage stage) { g_pHyprError = makeUnique(); Log::logger->log(Log::DEBUG, "Creating the LayoutManager!"); - g_layoutManager = makeUnique(); + g_pLayoutManager = makeUnique(); Log::logger->log(Log::DEBUG, "Creating the TokenManager!"); g_pTokenManager = makeUnique(); @@ -790,8 +792,7 @@ void CCompositor::startCompositor() { createLockFile(); - Event::bus()->m_events.ready.emit(); - + EMIT_HOOK_EVENT("ready", nullptr); if (m_watchdogWriteFd.isValid()) write(m_watchdogWriteFd.get(), "vax", 3); @@ -871,7 +872,7 @@ PHLMONITOR CCompositor::getMonitorFromVector(const Vector2D& point) { void CCompositor::removeWindowFromVectorSafe(PHLWINDOW pWindow) { if (!pWindow->m_fadingOut) { - Event::bus()->m_events.window.destroy.emit(pWindow); + EMIT_HOOK_EVENT("destroyWindow", pWindow); std::erase_if(m_windows, [&](SP& el) { return el == pWindow; }); std::erase_if(m_windowsFadingOut, [&](PHLWINDOWREF el) { return el.lock() == pWindow; }); @@ -1033,8 +1034,6 @@ 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; } @@ -1180,7 +1179,7 @@ PHLWINDOW CCompositor::getWindowFromSurface(SP pSurface) { const auto VIEW = pSurface->m_hlSurface->view(); - if (!VIEW || VIEW->type() != Desktop::View::VIEW_TYPE_WINDOW) + if (VIEW->type() != Desktop::View::VIEW_TYPE_WINDOW) return nullptr; return dynamicPointerCast(VIEW); @@ -1372,8 +1371,8 @@ void CCompositor::addToFadingOutSafe(PHLWINDOW pWindow) { m_windowsFadingOut.emplace_back(pWindow); } -PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, Math::eDirection dir) { - if (dir == Math::DIRECTION_DEFAULT) +PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, char dir) { + if (!isDirection(dir)) return nullptr; const auto PMONITOR = pWindow->m_monitor.lock(); @@ -1390,8 +1389,8 @@ PHLWINDOW CCompositor::getWindowInDirection(PHLWINDOW pWindow, Math::eDirection return getWindowInDirection(WINDOWIDEALBB, PWORKSPACE, dir, pWindow, pWindow->m_isFloating); } -PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, Math::eDirection dir, PHLWINDOW ignoreWindow, bool useVectorAngles) { - if (dir == Math::DIRECTION_DEFAULT) +PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, char dir, PHLWINDOW ignoreWindow, bool useVectorAngles) { + if (!isDirection(dir)) return nullptr; // 0 -> history, 1 -> shared length @@ -1405,35 +1404,6 @@ 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; @@ -1455,23 +1425,28 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks double intersectLength = -1; switch (dir) { - case Math::DIRECTION_LEFT: - if (isAdjacent(POSA.x, POSA.x + SIZEA.x, POSB.x, POSB.x + SIZEB.x)) + case 'l': + if (STICKS(POSA.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 Math::DIRECTION_RIGHT: - if (isAdjacent(POSB.x, POSB.x + SIZEB.x, POSA.x, POSA.x + SIZEA.x)) + case 'r': + if (STICKS(POSA.x + SIZEA.x, POSB.x)) { intersectLength = std::max(0.0, std::min(POSA.y + SIZEA.y, POSB.y + SIZEB.y) - std::max(POSA.y, POSB.y)); + } break; - case Math::DIRECTION_UP: - if (isAdjacent(POSA.y, POSA.y + SIZEA.y, POSB.y, POSB.y + SIZEB.y)) + case 't': + case 'u': + if (STICKS(POSA.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 Math::DIRECTION_DOWN: - if (isAdjacent(POSB.y, POSB.y + SIZEB.y, POSA.y, POSA.y + SIZEA.y)) + case 'b': + case 'd': + if (STICKS(POSA.y + SIZEA.y, POSB.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 */) { @@ -1500,8 +1475,12 @@ PHLWINDOW CCompositor::getWindowInDirection(const CBox& box, PHLWORKSPACE pWorks } } } else { - 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}}}; + 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}}}; // auto vectorAngles = [](const Vector2D& a, const Vector2D& b) -> double { @@ -1653,28 +1632,38 @@ bool CCompositor::isPointOnReservedArea(const Vector2D& point, const PHLMONITOR return VECNOTINRECT(point, box.x, box.y, box.x + box.w, box.y + box.h); } -std::optional CCompositor::calculateX11WorkArea() { +CBox CCompositor::calculateX11WorkArea() { static auto PXWLFORCESCALEZERO = CConfigValue("xwayland:force_zero_scaling"); - // 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; + CBox workbox = {0, 0, 0, 0}; + bool firstMonitor = true; - const auto M = m_monitors.front(); + 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); - // we ignore monitor->m_position on purpose - CBox box = M->logicalBoxMinusReserved().translate(-M->m_position); - if ((*PXWLFORCESCALEZERO)) - box.scale(M->m_scale); + 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; + } + } + } - return box.translate(M->m_xwaylandPosition); + // returning 0, 0 will remove the _NET_WORKAREA property + return workbox; } -PHLMONITOR CCompositor::getMonitorInDirection(Math::eDirection dir) { +PHLMONITOR CCompositor::getMonitorInDirection(const char& dir) { return getMonitorInDirection(Desktop::focusState()->monitor(), dir); } -PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, Math::eDirection dir) { +PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, const char& dir) { if (!pSourceMonitor) return nullptr; @@ -1691,7 +1680,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, Math::e const auto POSB = m->m_position; const auto SIZEB = m->m_size; switch (dir) { - case Math::DIRECTION_LEFT: + case 'l': 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) { @@ -1700,7 +1689,7 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, Math::e } } break; - case Math::DIRECTION_RIGHT: + case 'r': 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) { @@ -1709,7 +1698,8 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, Math::e } } break; - case Math::DIRECTION_UP: + case 't': + case 'u': 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) { @@ -1718,7 +1708,8 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, Math::e } } break; - case Math::DIRECTION_DOWN: + case 'b': + case 'd': 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) { @@ -1727,7 +1718,6 @@ PHLMONITOR CCompositor::getMonitorInDirection(PHLMONITOR pSourceMonitor, Math::e } } break; - default: break; } } @@ -1783,7 +1773,7 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor // additionally, move floating and fs windows manually if (w->m_isFloating) - w->layoutTarget()->setPositionGlobal(w->layoutTarget()->position().translate(-pMonitorA->m_position + pMonitorB->m_position)); + *w->m_realPosition = w->m_realPosition->goal() - pMonitorA->m_position + pMonitorB->m_position; if (w->isFullscreen()) { *w->m_realPosition = pMonitorB->m_position; @@ -1808,7 +1798,7 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor // additionally, move floating and fs windows manually if (w->m_isFloating) - w->layoutTarget()->setPositionGlobal(w->layoutTarget()->position().translate(-pMonitorB->m_position + pMonitorA->m_position)); + *w->m_realPosition = w->m_realPosition->goal() - pMonitorB->m_position + pMonitorA->m_position; if (w->isFullscreen()) { *w->m_realPosition = pMonitorA->m_position; @@ -1822,11 +1812,8 @@ void CCompositor::swapActiveWorkspaces(PHLMONITOR pMonitorA, PHLMONITOR pMonitor pMonitorA->m_activeWorkspace = PWORKSPACEB; pMonitorB->m_activeWorkspace = PWORKSPACEA; - g_layoutManager->recalculateMonitor(pMonitorA); - g_layoutManager->recalculateMonitor(pMonitorB); - - g_pHyprRenderer->damageMonitor(pMonitorB); - g_pHyprRenderer->damageMonitor(pMonitorA); + g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitorA->m_id); + g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitorB->m_id); g_pDesktopAnimationManager->setFullscreenFadeAnimation( PWORKSPACEB, PWORKSPACEB->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : CDesktopAnimationManager::ANIMATION_TYPE_OUT); @@ -1838,30 +1825,29 @@ 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::FOCUS_REASON_DESKTOP_STATE_CHANGE); + Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::ALLOW_FLOATING))); 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)}); - Event::bus()->m_events.workspace.active.emit(PNEWWORKSPACE); + EMIT_HOOK_EVENT("workspace", PNEWWORKSPACE); } - // events + // event 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)}); - Event::bus()->m_events.workspace.moveToMonitor.emit(PWORKSPACEA, pMonitorB); - Event::bus()->m_events.workspace.moveToMonitor.emit(PWORKSPACEB, pMonitorA); + EMIT_HOOK_EVENT("moveWorkspace", (std::vector{PWORKSPACEB, pMonitorA})); } PHLMONITOR CCompositor::getMonitorFromString(const std::string& name) { if (name == "current") return Desktop::focusState()->monitor(); else if (isDirection(name)) - return getMonitorInDirection(Math::fromChar(name[0])); + return getMonitorInDirection(name[0]); else if (name[0] == '+' || name[0] == '-') { // relative @@ -1982,7 +1968,6 @@ 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) { @@ -1998,18 +1983,17 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo if (w->m_isMapped && !w->isHidden()) { if (POLDMON) { if (w->m_isFloating) - w->layoutTarget()->setPositionGlobal(w->layoutTarget()->position().translate(-POLDMON->m_position + pMonitor->m_position)); + *w->m_realPosition = w->m_realPosition->goal() - POLDMON->m_position + pMonitor->m_position; if (w->isFullscreen()) { *w->m_realPosition = pMonitor->m_position; *w->m_realSize = pMonitor->m_size; } } else - 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->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->updateToplevel(); @@ -2037,8 +2021,7 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo pWorkspace->m_events.activeChanged.emit(); - g_layoutManager->recalculateMonitor(pMonitor); - g_pHyprRenderer->damageMonitor(pMonitor); + g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitor->m_id); g_pDesktopAnimationManager->startAnimation(pWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); pWorkspace->m_visible = true; @@ -2051,7 +2034,7 @@ void CCompositor::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMo // finalize if (POLDMON) { - g_layoutManager->recalculateMonitor(POLDMON); + g_pLayoutManager->getCurrentLayout()->recalculateMonitor(POLDMON->m_id); if (valid(POLDMON->m_activeWorkspace)) g_pDesktopAnimationManager->setFullscreenFadeAnimation(POLDMON->m_activeWorkspace, POLDMON->m_activeWorkspace->m_hasFullscreenWindow ? CDesktopAnimationManager::ANIMATION_TYPE_IN : @@ -2066,8 +2049,7 @@ 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)}); - - Event::bus()->m_events.workspace.moveToMonitor.emit(pWorkspace, pMonitor); + EMIT_HOOK_EVENT("moveWorkspace", (std::vector{pWorkspace, pMonitor})); } bool CCompositor::workspaceIDOutOfBounds(const WORKSPACEID& id) { @@ -2150,25 +2132,24 @@ 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_layoutManager->recalculateMonitor(PMONITOR); + g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->monitorID()); return; } - PWORKSPACE->m_fullscreenMode = EFFECTIVE_MODE; - PWORKSPACE->m_hasFullscreenWindow = EFFECTIVE_MODE != FSMODE_NONE; - - g_layoutManager->fullscreenRequestForTarget(PWINDOW->layoutTarget(), CURRENT_EFFECTIVE_MODE, EFFECTIVE_MODE); + g_pLayoutManager->getCurrentLayout()->fullscreenRequestForWindow(PWINDOW, 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)}); - Event::bus()->m_events.window.fullscreen.emit(PWINDOW); + EMIT_HOOK_EVENT("fullscreen", 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_layoutManager->recalculateMonitor(PMONITOR); + g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->monitorID()); // make all windows and layers on the same workspace under the fullscreen window for (auto const& w : m_windows) { @@ -2281,7 +2262,7 @@ PHLWINDOW CCompositor::getWindowByRegex(const std::string& regexp_) { } for (auto const& w : g_pCompositor->m_windows) { - if (!w->m_isMapped) + if (!w->m_isMapped || (w->isHidden() && !g_pLayoutManager->getCurrentLayout()->isWindowReachable(w))) continue; switch (mode) { @@ -2586,30 +2567,59 @@ void CCompositor::moveWindowToWorkspaceSafe(PHLWINDOW pWindow, PHLWORKSPACE pWor setWindowFullscreenInternal(pWindow, FSMODE_NONE); const PHLWINDOW pFirstWindowOnWorkspace = pWorkspace->getFirstWindow(); - const int visibleWindowsOnWorkspace = pWorkspace->getWindows(true, std::nullopt, true); + const int visibleWindowsOnWorkspace = pWorkspace->getWindows(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_group && - pWindow->canBeGroupedInto(pFirstWindowOnWorkspace->m_group)) { - pFirstWindowOnWorkspace->m_group->add(pWindow); + 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)); + } else { + if (!pWindow->m_isFloating) + g_pLayoutManager->getCurrentLayout()->onWindowCreatedTiling(pWindow); + if (pWindow->m_isFloating) - pWindow->layoutTarget()->setPositionGlobal(CBox{POSTOMON + PWORKSPACEMONITOR->m_position, pWindow->layoutTarget()->position().size()}); + *pWindow->m_realPosition = POSTOMON + PWORKSPACEMONITOR->m_position; } pWindow->updateToplevel(); pWindow->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE); pWindow->uncacheWindowDecos(); + pWindow->updateGroupOutputs(); - if (pWindow->m_group) - pWindow->m_group->updateWorkspace(pWorkspace); - - g_layoutManager->newTarget(pWindow->layoutTarget(), pWorkspace->m_space); + 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 (FULLSCREEN) setWindowFullscreenInternal(pWindow, FULLSCREENMODE); @@ -2778,17 +2788,12 @@ void CCompositor::arrangeMonitors() { } PROTO::xdgOutput->updateAllOutputs(); - Event::bus()->m_events.monitor.layoutChanged.emit(); #ifndef NO_XWAYLAND - 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); - } - + 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 } @@ -2915,7 +2920,7 @@ void CCompositor::onNewMonitor(SP output) { PNEWMONITOR->m_id = FALLBACK ? MONITOR_INVALID : g_pCompositor->getNextAvailableMonitorID(output->name); PNEWMONITOR->m_isUnsafeFallback = FALLBACK; - Event::bus()->m_events.monitor.newMon.emit(PNEWMONITOR); + EMIT_HOOK_EVENT("newMonitor", PNEWMONITOR); if (!FALLBACK) PNEWMONITOR->onConnect(false); @@ -2968,16 +2973,13 @@ 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), - .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()}) : + 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}}) : DEFAULT_HDR_IMAGE_DESCRIPTION; } @@ -3074,27 +3076,6 @@ 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; @@ -3115,7 +3096,3 @@ 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 6d2044fe..abcf7ec6 100644 --- a/src/Compositor.hpp +++ b/src/Compositor.hpp @@ -4,12 +4,11 @@ #include -#include "helpers/math/Direction.hpp" #include "managers/XWaylandManager.hpp" #include "managers/KeybindManager.hpp" #include "managers/SessionLockManager.hpp" #include "desktop/view/Window.hpp" -#include "helpers/cm/ColorManagement.hpp" +#include "protocols/types/ColorManagement.hpp" #include #include @@ -114,16 +113,16 @@ class CCompositor { bool isWindowActive(PHLWINDOW); void changeWindowZOrder(PHLWINDOW, bool); void cleanupFadingOut(const MONITORID& monid); - PHLWINDOW getWindowInDirection(PHLWINDOW, Math::eDirection); - PHLWINDOW getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, Math::eDirection dir, PHLWINDOW ignoreWindow = nullptr, bool useVectorAngles = false); + PHLWINDOW getWindowInDirection(PHLWINDOW, char); + PHLWINDOW getWindowInDirection(const CBox& box, PHLWORKSPACE pWorkspace, char 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); - std::optional calculateX11WorkArea(); - PHLMONITOR getMonitorInDirection(Math::eDirection); - PHLMONITOR getMonitorInDirection(PHLMONITOR, Math::eDirection); + CBox calculateX11WorkArea(); + PHLMONITOR getMonitorInDirection(const char&); + PHLMONITOR getMonitorInDirection(PHLMONITOR, const char&); void updateAllWindowsAnimatedDecorationValues(); MONITORID getNextAvailableMonitorID(std::string const& name); void moveWorkspaceToMonitor(PHLWORKSPACE, PHLMONITOR, bool noWarpCursor = false); @@ -161,9 +160,7 @@ 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 bb7c601e..639d160a 100644 --- a/src/SharedDefs.hpp +++ b/src/SharedDefs.hpp @@ -38,6 +38,10 @@ 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 @@ -58,3 +62,5 @@ 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 f6f777d1..132b4789 100644 --- a/src/config/ConfigDescriptions.hpp +++ b/src/config/ConfigDescriptions.hpp @@ -1115,12 +1115,6 @@ 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", @@ -1568,28 +1562,10 @@ inline static const std::vector CONFIG_OPTIONS = { }, SConfigOptionDescription{ .value = "render:cm_sdr_eotf", - .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}, + .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"}, }, /* @@ -1714,9 +1690,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. 0 - off, 1 - on, 2 - auto (nvidia only)", - .type = CONFIG_OPTION_INT, - .data = SConfigOptionDescription::SRangeData{.value = 2, .min = 0, .max = 2}, + .description = "Makes HW cursors use a CPU buffer. Required on Nvidia to have HW cursors. Experimental", + .type = CONFIG_OPTION_BOOL, + .data = SConfigOptionDescription::SBoolData{false}, }, SConfigOptionDescription{ .value = "cursor:sync_gsettings_theme", @@ -1770,12 +1746,6 @@ 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", @@ -1854,46 +1824,6 @@ 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: @@ -1974,6 +1904,18 @@ 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: @@ -2059,65 +2001,6 @@ 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 */ @@ -2128,11 +2011,5 @@ 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 29967e58..d82983a1 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -18,14 +18,13 @@ #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" @@ -40,10 +39,8 @@ #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 @@ -402,12 +399,6 @@ 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; @@ -420,12 +411,6 @@ 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); @@ -556,14 +541,12 @@ 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}); @@ -575,10 +558,6 @@ 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}); @@ -618,9 +597,6 @@ 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}); @@ -633,6 +609,8 @@ 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}); @@ -648,16 +626,6 @@ 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}); @@ -728,9 +696,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}); @@ -797,17 +765,13 @@ 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", {"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("render:cm_sdr_eotf", 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"}); @@ -863,7 +827,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", {"default"}); + m_config->addSpecialConfigValue("monitorv2", "sdr_eotf", Hyprlang::INT{0}); 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}); @@ -875,7 +839,6 @@ 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"}); @@ -905,13 +868,9 @@ 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", {true}); + m_config->registerHandler(&::handleGesture, "gesture", {false}); 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}); @@ -1072,7 +1031,7 @@ static void clearHlVersionVars() { } void CConfigManager::reload() { - Event::bus()->m_events.config.preReload.emit(); + EMIT_HOOK_EVENT("preConfigReload", nullptr); setDefaultAnimationVars(); resetHLConfig(); m_configCurrentPath = getMainConfigPath(); @@ -1214,18 +1173,8 @@ 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) { - 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); - } + if (VAL && VAL->m_bSetByUser) + parser.rule().sdrEotf = std::any_cast(VAL->getValue()); VAL = m_config->getSpecialConfigValuePtr("monitorv2", "sdrbrightness", output.c_str()); if (VAL && VAL->m_bSetByUser) parser.rule().sdrBrightness = std::any_cast(VAL->getValue()); @@ -1262,10 +1211,6 @@ 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; }); @@ -1368,8 +1313,7 @@ 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; - if (m->m_activeWorkspace) - m->m_activeWorkspace->m_space->recalculate(); + g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id); } // Update the keyboard layout to the cfg'd one if this is not the first launch @@ -1437,6 +1381,9 @@ 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; @@ -1475,10 +1422,7 @@ void CConfigManager::postConfigReload(const Hyprlang::CParseResult& result) { if (!m_isFirstLaunch) ensurePersistentWorkspacesPresent(); - // update layouts - Layout::Supplementary::algoMatcher()->updateWorkspaceLayouts(); - - Event::bus()->m_events.config.reloaded.emit(); + EMIT_HOOK_EVENT("configReloaded", nullptr); if (g_pEventManager) g_pEventManager->postEvent(SHyprIPCEvent{"configreloaded", ""}); } @@ -1500,9 +1444,8 @@ 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_layoutManager->recalculateMonitor(m); - } + for (auto const& m : g_pCompositor->m_monitors) + g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id); } // Update window border colors @@ -1674,8 +1617,6 @@ 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; @@ -1767,7 +1708,7 @@ void CConfigManager::performMonitorReload() { m_wantsMonitorReload = false; - Event::bus()->m_events.monitor.layoutChanged.emit(); + EMIT_HOOK_EVENT("monitorLayoutChanged", nullptr); } void* const* CConfigManager::getConfigValuePtr(const std::string& val) { @@ -1825,41 +1766,24 @@ 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); - 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(); + 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 -> false", m->m_output->name); } - m->m_vrrActive = 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; return; } else if (USEVRR == 2 || USEVRR == 3) { + const auto PWORKSPACE = m->m_activeWorkspace; + if (!PWORKSPACE) return; // ??? @@ -2284,15 +2208,6 @@ 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; } @@ -2387,9 +2302,6 @@ 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])); @@ -2756,9 +2668,6 @@ 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 {}; @@ -2895,8 +2804,6 @@ 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") @@ -2914,7 +2821,7 @@ std::optional CConfigManager::handlePermission(const std::string& c if (mode == PERMISSION_RULE_ALLOW_MODE_UNKNOWN) return "unknown permission allow mode"; - if (m_isFirstLaunch && g_pDynamicPermissionManager) + if (m_isFirstLaunch) g_pDynamicPermissionManager->addConfigPermissionRule(std::string(data[0]), type, mode); return {}; @@ -2938,17 +2845,9 @@ 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; - bool disableInhibit = false; - - for (const auto arg : command.substr(7)) { - switch (arg) { - case 'p': disableInhibit = true; break; - default: return "gesture: invalid flag"; - } - } + int startDataIdx = 2; + uint32_t modMask = 0; + float deltaScale = 1.F; while (true) { @@ -2971,29 +2870,23 @@ 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, disableInhibit); + direction, modMask, deltaScale); else if (data[startDataIdx] == "workspace") - result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale, disableInhibit); + result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale); else if (data[startDataIdx] == "resize") - result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale, disableInhibit); + result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale); else if (data[startDataIdx] == "move") - result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale, disableInhibit); + result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale); else if (data[startDataIdx] == "special") - result = - g_pTrackpadGestures->addGesture(makeUnique(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale, disableInhibit); + result = g_pTrackpadGestures->addGesture(makeUnique(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale); else if (data[startDataIdx] == "close") - result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale, disableInhibit); + result = g_pTrackpadGestures->addGesture(makeUnique(), fingerCount, direction, modMask, deltaScale); else if (data[startDataIdx] == "float") - result = - g_pTrackpadGestures->addGesture(makeUnique(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale, disableInhibit); + result = g_pTrackpadGestures->addGesture(makeUnique(std::string{data[startDataIdx + 1]}), fingerCount, direction, modMask, deltaScale); else if (data[startDataIdx] == "fullscreen") - 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); + 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); else return std::format("Invalid gesture: {}", data[startDataIdx]); @@ -3096,7 +2989,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() || g_pCompositor->isVRRActiveOnAnyMonitor()); + case 2: return g_pHyprRenderer->isNvidia() && g_pHyprRenderer->isMgpu(); default: break; } @@ -3123,20 +3016,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::format("\"{}\"", std::any_cast(CONFIGVALUE)); + currentValue = 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 = std::format("\"{}\"", DATA->toString()); + currentValue = 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) { @@ -3155,7 +3048,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) { @@ -3176,12 +3069,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 36e25f6b..83fef7b0 100644 --- a/src/config/ConfigManager.hpp +++ b/src/config/ConfigManager.hpp @@ -46,7 +46,6 @@ struct SWorkspaceRule { std::optional noShadow; std::optional onCreatedEmptyRunCmd; std::optional defaultName; - std::optional layout; std::map layoutopts; }; @@ -177,7 +176,6 @@ 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 3f51e8df..41b34e8a 100644 --- a/src/debug/HyprCtl.cpp +++ b/src/debug/HyprCtl.cpp @@ -44,7 +44,6 @@ 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" @@ -53,15 +52,12 @@ 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 @@ -148,13 +144,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\"", "\"FAILED\"", "\"CM\"", + "\"UNKNOWN\"", "\"USER\"", "\"WINDOWED\"", "\"CONTENT\"", "\"MIRROR\"", "\"RECORD\"", "\"SW\"", + "\"CANDIDATE\"", "\"SURFACE\"", "\"TRANSFORM\"", "\"DMA\"", "\"TEARING\"", "\"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", "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", "tearing", "activation failed", "color management", }; std::string CHyprCtl::getDSBlockedReason(Hyprutils::Memory::CSharedPointer m, eHyprCtlOutputFormat format) { @@ -177,13 +173,14 @@ std::string CHyprCtl::getDSBlockedReason(Hyprutils::Memory::CSharedPointer TEARING_REASONS_JSON = { - "\"UNKNOWN\"", "\"NOT_TORN\"", "\"USER\"", "\"ZOOM\"", "\"SUPPORT\"", "\"CANDIDATE\"", "\"WINDOW\"", "\"HW_CURSOR\"", + "\"UNKNOWN\"", "\"NOT_TORN\"", "\"USER\"", "\"ZOOM\"", "\"SUPPORT\"", "\"CANDIDATE\"", "\"WINDOW\"", }; -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"}; +const std::array TEARING_REASONS_TEXT = { + "unknown reason", "next frame is not torn", "user settings", "zoom", "not supported by monitor", "missing candidate", "window settings", +}; -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"; @@ -334,19 +331,23 @@ 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_group) + if (w->m_groupData.pNextWindow.expired()) return isJson ? "" : "0"; std::ostringstream result; - for (const auto& curr : w->m_group->windows()) { + PHLWINDOW head = w->getGroupHead(); + PHLWINDOW curr = head; + while (true) { if (isJson) result << std::format("\"0x{:x}\"", rc(curr.get())); else result << std::format("{:x}", rc(curr.get())); - - if (curr != w->m_group->windows().back()) - result << (isJson ? ", " : ","); + curr = curr->m_groupData.pNextWindow.lock(); + // We've wrapped around to the start, break out without trailing comma + if (curr == head) + break; + result << (isJson ? ", " : ","); } return result.str(); @@ -375,6 +376,7 @@ std::string CHyprCtl::getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format) { "name": "{}" }}, "floating": {}, + "pseudo": {}, "monitor": {}, "class": "{}", "title": "{}", @@ -385,7 +387,6 @@ std::string CHyprCtl::getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format) { "pinned": {}, "fullscreen": {}, "fullscreenClient": {}, - "overFullscreen": {}, "grouped": [{}], "tags": [{}], "swallowing": "0x{:x}", @@ -393,31 +394,29 @@ std::string CHyprCtl::getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format) { "inhibitingIdle": {}, "xdgTag": "{}", "xdgDescription": "{}", - "contentType": "{}", - "stableId": "{:x}" + "contentType": "{}" }},)#", 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->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"), + 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), 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())), w->m_stableID); + escapeJSONStrings(NContentType::toString(w->getContentType()))); } else { return std::format( - "Window {:x} -> {}:\n\tmapped: {}\n\thidden: {}\n\tat: {},{}\n\tsize: {},{}\n\tworkspace: {} ({})\n\tfloating: {}\n\tmonitor: {}\n\tclass: {}\n\ttitle: " + "Window {:x} -> {}:\n\tmapped: {}\n\thidden: {}\n\tat: {},{}\n\tsize: {},{}\n\tworkspace: {} ({})\n\tfloating: {}\n\tpseudo: {}\n\tmonitor: {}\n\tclass: {}\n\ttitle: " "{}\n\tinitialClass: {}\n\tinitialTitle: {}\n\tpid: " "{}\n\txwayland: {}\n\tpinned: " - "{}\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", + "{}\n\tfullscreen: {}\n\tfullscreenClient: {}\n\tgrouped: {}\n\ttags: {}\n\tswallowing: {:x}\n\tfocusHistoryID: {}\n\tinhibitingIdle: {}\n\txdgTag: " + "{}\n\txdgDescription: {}\n\tcontentType: {}\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), 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), + (!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), 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->m_stableID); + w->xdgTag().value_or(""), w->xdgDescription().value_or(""), NContentType::toString(w->getContentType())); } } @@ -451,15 +450,8 @@ 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(); - - 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())); - } - + const auto PLASTW = w->getLastFocusedWindow(); + const auto PMONITOR = w->m_monitor.lock(); if (format == eHyprCtlOutputFormat::FORMAT_JSON) { return std::format(R"#({{ "id": {}, @@ -470,17 +462,16 @@ std::string CHyprCtl::getWorkspaceData(PHLWORKSPACE w, eHyprCtlOutputFormat form "hasfullscreen": {}, "lastwindow": "0x{:x}", "lastwindowtitle": "{}", - "ispersistent": {}, - "tiledLayout": "{}" + "ispersistent": {} }})#", 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", escapeJSONStrings(layoutName)); + rc(PLASTW.get()), PLASTW ? escapeJSONStrings(PLASTW->m_title) : "", w->isPersistent() ? "true" : "false"); } else { - 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); + 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())); } } @@ -679,6 +670,28 @@ 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(); @@ -789,7 +802,7 @@ static std::string devicesRequest(eHyprCtlOutputFormat format, std::string reque result += std::format( R"#( {{ "address": "0x{:x}", - "type": "tabletTool" + "type": "tabletTool", }},)#", rc(d.get())); } @@ -1281,13 +1294,8 @@ 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") + if (COMMAND == "monitor" || COMMAND == "source") g_pConfigManager->m_wantsMonitorReload = true; // for monitor keywords if (COMMAND.contains("monitorv2")) @@ -1300,8 +1308,10 @@ static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in) g_pInputManager->setTabletConfigs(); // update tablets } - if (COMMAND.contains("general:layout") || (COMMAND.contains("workspace") && VALUE.contains("layout:"))) - Layout::Supplementary::algoMatcher()->updateWorkspaceLayouts(); + static auto PLAYOUT = CConfigValue("general:layout"); + + if (COMMAND.contains("general:layout")) + g_pLayoutManager->switchToLayout(*PLAYOUT); // update layout if (COMMAND.contains("decoration:screen_shader") || COMMAND == "source") g_pHyprOpenGL->m_reloadScreenShader = true; @@ -1321,22 +1331,13 @@ 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); - if (m->m_activeWorkspace) - m->m_activeWorkspace->m_space->recalculate(); + g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id); } } 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(); @@ -1596,7 +1597,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_group ? PWINDOW->m_group->locked() : false; + const bool GROUPLOCKED = PWINDOW->m_groupData.pNextWindow.lock() ? PWINDOW->getGroupHead()->m_groupData.locked : false; if (active) { auto* const ACTIVECOL = (CGradientValueData*)(PACTIVECOL.ptr())->getData(); @@ -1604,7 +1605,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_group ? (!(PWINDOW->m_groupRules & Desktop::View::GROUP_DENY) ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL); + !PWINDOW->m_groupData.pNextWindow.lock() ? (!PWINDOW->m_groupData.deny ? ACTIVECOL : NOGROUPACTIVECOL) : (GROUPLOCKED ? GROUPACTIVELOCKEDCOL : GROUPACTIVECOL); std::string borderColorString = PWINDOW->m_ruleApplicator->activeBorderColor().valueOr(*ACTIVECOLOR).toString(); if (FORMNORM) @@ -1616,8 +1617,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_group ? (!(PWINDOW->m_groupRules & Desktop::View::GROUP_DENY) ? INACTIVECOL : NOGROUPINACTIVECOL) : - (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL); + const auto* const INACTIVECOLOR = !PWINDOW->m_groupData.pNextWindow.lock() ? (!PWINDOW->m_groupData.deny ? INACTIVECOL : NOGROUPINACTIVECOL) : + (GROUPLOCKED ? GROUPINACTIVELOCKEDCOL : GROUPINACTIVECOL); std::string borderColorString = PWINDOW->m_ruleApplicator->inactiveBorderColor().valueOr(*INACTIVECOLOR).toString(); if (FORMNORM) @@ -2049,12 +2050,7 @@ static std::string submapRequest(eHyprCtlOutputFormat format, std::string reques } static std::string reloadShaders(eHyprCtlOutputFormat format, std::string request) { - CVarList vars(request, 0, ' '); - - if (vars.size() > 2) - return "too many args"; - - if (g_pHyprOpenGL && g_pHyprRenderer->reloadShaders(vars.size() == 2 ? vars[1] : "")) + if (g_pHyprOpenGL->initShaders()) return format == FORMAT_JSON ? "{\"ok\": true}" : "ok"; else return format == FORMAT_JSON ? "{\"ok\": false}" : "error"; @@ -2077,12 +2073,13 @@ 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}); @@ -2189,6 +2186,10 @@ 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) { @@ -2202,17 +2203,9 @@ 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); } } @@ -2224,38 +2217,16 @@ std::string CHyprCtl::makeDynamicCall(const std::string& input) { } static bool successWrite(int fd, const std::string& data, bool needLog = 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 (write(fd, data.c_str(), data.length()) > 0) + return true; - while (totalWritten < data.length()) { - ssize_t written = write(fd, data.c_str() + totalWritten, remaining); + if (errno == EAGAIN) + return true; - 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; - } + if (needLog) + Log::logger->log(Log::ERR, "Couldn't write to socket. Error: " + std::string(strerror(errno))); - 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; + return false; } static void runWritingDebugLogThread(const int conn) { diff --git a/src/debug/HyprDebugOverlay.cpp b/src/debug/HyprDebugOverlay.cpp index 17ce12fa..8f4189b0 100644 --- a/src/debug/HyprDebugOverlay.cpp +++ b/src/debug/HyprDebugOverlay.cpp @@ -8,7 +8,7 @@ #include "../desktop/state/FocusState.hpp" CHyprDebugOverlay::CHyprDebugOverlay() { - m_texture = g_pHyprRenderer->createTexture(); + m_texture = makeShared(); } void CHyprMonitorDebugOverlay::renderData(PHLMONITOR pMonitor, float durationUs) { @@ -259,7 +259,15 @@ void CHyprDebugOverlay::draw() { cairo_surface_flush(m_cairoSurface); // copy the data to an OpenGL texture we have - m_texture = g_pHyprRenderer->createTexture(m_cairoSurface); + 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); CTexPassElement::SRenderData data; data.tex = m_texture; diff --git a/src/debug/HyprDebugOverlay.hpp b/src/debug/HyprDebugOverlay.hpp index bf188359..72987d94 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 e67b0434..1c66a53b 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,12 +22,14 @@ static inline auto iconBackendFromLayout(PangoLayout* layout) { } CHyprNotificationOverlay::CHyprNotificationOverlay() { - static auto P = Event::bus()->m_events.monitor.focused.listen([&](PHLMONITOR mon) { + static auto P = g_pHookSystem->hookDynamic("focusedMon", [&](void* self, SCallbackInfo& info, std::any param) { if (m_notifications.empty()) return; g_pHyprRenderer->damageBox(m_lastDamage); }); + + m_texture = makeShared(); } CHyprNotificationOverlay::~CHyprNotificationOverlay() { @@ -230,7 +232,16 @@ void CHyprNotificationOverlay::draw(PHLMONITOR pMonitor) { m_lastDamage = damage; - m_texture = g_pHyprRenderer->createTexture(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, MONSIZE.x, MONSIZE.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA); CTexPassElement::SRenderData data; data.tex = m_texture; diff --git a/src/debug/HyprNotificationOverlay.hpp b/src/debug/HyprNotificationOverlay.hpp index ec7aed72..868eb05b 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{1.0, 204 / 255.0, 102 / 255.0, 1.0}, +static const std::array ICONS_COLORS = {CHyprColor{255.0 / 255.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 1aca4d51..44b82a50 100644 --- a/src/debug/log/Logger.cpp +++ b/src/debug/log/Logger.cpp @@ -1,8 +1,9 @@ #include "Logger.hpp" #include "RollingLogFollow.hpp" -#include "../../event/EventBus.hpp" +#include "../../defines.hpp" +#include "../../managers/HookSystemManager.hpp" #include "../../config/ConfigValue.hpp" using namespace Log; @@ -38,7 +39,7 @@ void CLogger::initIS(const std::string_view& IS) { } void CLogger::initCallbacks() { - static auto P = Event::bus()->m_events.config.reloaded.listen([this]() { recheckCfg(); }); + static auto P = g_pHookSystem->hookDynamic("configReloaded", [this](void* hk, SCallbackInfo& info, std::any param) { recheckCfg(); }); recheckCfg(); } diff --git a/src/defines.hpp b/src/defines.hpp index 571679dc..cd4c524d 100644 --- a/src/defines.hpp +++ b/src/defines.hpp @@ -5,7 +5,3 @@ #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 5df3f087..2895137d 100644 --- a/src/desktop/Workspace.cpp +++ b/src/desktop/Workspace.cpp @@ -1,13 +1,10 @@ #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 "../layout/space/Space.hpp" -#include "../layout/supplementary/WorkspaceAlgoMatcher.hpp" -#include "../event/EventBus.hpp" +#include "../managers/HookSystemManager.hpp" #include #include @@ -37,14 +34,13 @@ void CWorkspace::init(PHLWORKSPACE self) { if (RULEFORTHIS.defaultName.has_value()) m_name = RULEFORTHIS.defaultName.value(); - m_focusedWindowHook = Event::bus()->m_events.window.close.listen([this](PHLWINDOW pWindow) { - if (pWindow == m_lastFocusedWindow.lock()) + 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_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); @@ -56,19 +52,22 @@ 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)}); - Event::bus()->m_events.workspace.created.emit(self); + EMIT_HOOK_EVENT("createWorkspace", this); } 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(); } @@ -407,19 +406,14 @@ bool CWorkspace::isVisibleNotCovered() { int CWorkspace::getWindows(std::optional onlyTiled, std::optional onlyPinned, std::optional onlyVisible) { int no = 0; - - if (!m_space) - return 0; - - for (auto const& t : m_space->targets()) { - if (!t) + for (auto const& w : g_pCompositor->m_windows) { + if (w->workspaceID() != m_id || !w->m_isMapped) continue; - - if (onlyTiled.has_value() && t->floating() == onlyTiled.value()) + if (onlyTiled.has_value() && w->m_isFloating == onlyTiled.value()) continue; - if (onlyPinned.has_value() && (!t->window() || t->window()->m_pinned != onlyPinned.value())) + if (onlyPinned.has_value() && w->m_pinned != onlyPinned.value()) continue; - if (onlyVisible.has_value() && (!t->window() || t->window()->isHidden() == onlyVisible.value())) + if (onlyVisible.has_value() && w->isHidden() == onlyVisible.value()) continue; no++; } @@ -429,16 +423,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& g : Desktop::View::groups()) { - const auto HEAD = g->head(); - - if (HEAD->workspaceID() != m_id || !HEAD->m_isMapped) + for (auto const& w : g_pCompositor->m_windows) { + if (w->workspaceID() != m_id || !w->m_isMapped) continue; - if (onlyTiled.has_value() && HEAD->m_isFloating == onlyTiled.value()) + if (!w->m_groupData.head) continue; - if (onlyPinned.has_value() && HEAD->m_pinned != onlyPinned.value()) + if (onlyTiled.has_value() && w->m_isFloating == onlyTiled.value()) continue; - if (onlyVisible.has_value() && g->current()->isHidden() == onlyVisible.value()) + if (onlyPinned.has_value() && w->m_pinned != onlyPinned.value()) + continue; + if (onlyVisible.has_value() && w->isHidden() == onlyVisible.value()) continue; no++; } @@ -520,11 +514,13 @@ void CWorkspace::rename(const std::string& name) { } void CWorkspace::updateWindows() { - m_hasFullscreenWindow = std::ranges::any_of(m_space->targets(), [](const auto& t) { return t->fullscreenMode() != FSMODE_NONE; }); + 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(); }); - for (auto const& t : m_space->targets()) { - if (t->window()) - t->window()->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_ON_WORKSPACE); + 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); } } diff --git a/src/desktop/Workspace.hpp b/src/desktop/Workspace.hpp index 87d1c2d8..1aad1aaa 100644 --- a/src/desktop/Workspace.hpp +++ b/src/desktop/Workspace.hpp @@ -6,10 +6,6 @@ #include "../helpers/MiscFunctions.hpp" #include "../helpers/signal/Signal.hpp" -namespace Layout { - class CSpace; -}; - enum eFullscreenMode : int8_t { FSMODE_NONE = 0, FSMODE_MAXIMIZED = 1 << 0, @@ -24,9 +20,7 @@ class CWorkspace { CWorkspace(WORKSPACEID id, PHLMONITOR monitor, std::string name, bool special = false, bool isEmpty = true); ~CWorkspace(); - WP m_self; - - SP m_space; + WP m_self; // Workspaces ID-based have IDs > 0 // and workspaces name-based have IDs starting with -1337 @@ -93,13 +87,13 @@ class CWorkspace { } m_events; private: - void init(PHLWORKSPACE self); + void init(PHLWORKSPACE self); - CHyprSignalListener m_focusedWindowHook; - bool m_inert = true; + SP 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 1dd32164..5dc0742f 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,12 +12,18 @@ SP History::windowTracker() { } CWindowHistoryTracker::CWindowHistoryTracker() { - static auto P = Event::bus()->m_events.window.openEarly.listen([this](PHLWINDOW pWindow) { + static auto P = g_pHookSystem->hookDynamic("openWindowEarly", [this](void* self, SCallbackInfo& info, std::any data) { + auto window = std::any_cast(data); + // add a last track - m_history.insert(m_history.begin(), pWindow); + m_history.insert(m_history.begin(), window); }); - static auto P1 = Event::bus()->m_events.window.active.listen([this](PHLWINDOW window, uint8_t reason) { track(window); }); + static auto P1 = g_pHookSystem->hookDynamic("activeWindow", [this](void* self, SCallbackInfo& info, std::any data) { + auto window = std::any_cast(data); + + track(window); + }); } void CWindowHistoryTracker::track(PHLWINDOW w) { diff --git a/src/desktop/history/WorkspaceHistoryTracker.cpp b/src/desktop/history/WorkspaceHistoryTracker.cpp index daa115f8..bfedda13 100644 --- a/src/desktop/history/WorkspaceHistoryTracker.cpp +++ b/src/desktop/history/WorkspaceHistoryTracker.cpp @@ -2,13 +2,9 @@ #include "../../helpers/Monitor.hpp" #include "../Workspace.hpp" -#include "../state/FocusState.hpp" -#include "../../managers/eventLoop/EventLoopManager.hpp" -#include "../../event/EventBus.hpp" +#include "../../managers/HookSystemManager.hpp" #include "../../config/ConfigValue.hpp" -#include - using namespace Desktop; using namespace Desktop::History; @@ -18,16 +14,27 @@ SP History::workspaceTracker() { } CWorkspaceHistoryTracker::CWorkspaceHistoryTracker() { - static auto P = Event::bus()->m_events.workspace.active.listen([this](PHLWORKSPACE workspace) { track(workspace); }); + 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 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); - }); + 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, }); } @@ -45,32 +52,44 @@ CWorkspaceHistoryTracker::SWorkspacePreviousData& CWorkspaceHistoryTracker::data } void CWorkspaceHistoryTracker::track(PHLWORKSPACE w) { - if (!w || !w->m_monitor || w == m_lastWorkspaceData.workspace) + if (!w->m_monitor) return; - static auto PALLOWWORKSPACECYCLES = CConfigValue("binds:allow_workspace_cycles"); + static auto PALLOWWORKSPACECYCLES = CConfigValue("binds:allow_workspace_cycles"); - auto& data = dataFor(w); + auto& data = dataFor(w); + auto& monData = dataFor(w->m_monitor.lock()); - Hyprutils::Utils::CScopeGuard x([&] { setLastWorkspaceData(w); }); - - if (m_lastWorkspaceData.workspace == w && !*PALLOWWORKSPACECYCLES) + if (!monData.workspace) { + data.previous.reset(); + data.previousID = WORKSPACE_INVALID; + data.previousName = ""; 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) { @@ -137,15 +156,3 @@ 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 baecb363..4a3c109a 100644 --- a/src/desktop/history/WorkspaceHistoryTracker.hpp +++ b/src/desktop/history/WorkspaceHistoryTracker.hpp @@ -32,19 +32,21 @@ namespace Desktop::History { SWorkspaceIDName previousWorkspaceIDName(PHLWORKSPACE ws, PHLMONITOR restrict); private: - struct SLastWorkspaceData { + struct SMonitorData { 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 8b4956dd..b67524ce 100644 --- a/src/desktop/reserved/ReservedArea.cpp +++ b/src/desktop/reserved/ReservedArea.cpp @@ -6,18 +6,16 @@ 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.clamp({0, 0})), m_initialBottomRight(br.clamp({0, 0})) { +CReservedArea::CReservedArea(const Vector2D& tl, const Vector2D& br) : m_initialTopLeft(tl), m_initialBottomRight(br) { calculate(); } -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)) { +CReservedArea::CReservedArea(double top, double right, double bottom, double left) : m_initialTopLeft(left, top), m_initialBottomRight(right, bottom) { calculate(); } CReservedArea::CReservedArea(const CBox& parent, const CBox& child) { - if (parent.empty() || child.empty()) - return; // empty reserved area + ASSERT(!parent.empty() && !child.empty()); ASSERT(parent.containsPoint(child.pos() + Vector2D{0.0001, 0.0001})); ASSERT(parent.containsPoint(child.pos() + child.size() - Vector2D{0.0001, 0.0001})); @@ -83,8 +81,6 @@ 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 ab5525e8..3d981587 100644 --- a/src/desktop/rule/Rule.cpp +++ b/src/desktop/rule/Rule.cpp @@ -52,7 +52,6 @@ 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() { @@ -126,11 +125,10 @@ const std::string& IRule::name() { return m_name; } -void IRule::markAsExecRule(const std::string& token, uint64_t pid, bool persistent) { +void IRule::markAsExecRule(const std::string& token, 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 efd3cb39..2b852b3a 100644 --- a/src/desktop/rule/Rule.hpp +++ b/src/desktop/rule/Rule.hpp @@ -6,6 +6,7 @@ #include "../../helpers/time/Time.hpp" #include #include +#include #include namespace Desktop::Rule { @@ -30,7 +31,6 @@ 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, uint64_t pid, bool persistent = false); + void markAsExecRule(const std::string& token, bool persistent = false); bool isExecRule(); bool isExecPersistent(); bool execExpired(); @@ -78,8 +78,7 @@ 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 fec3a5b2..d80f839c 100644 --- a/src/desktop/rule/layerRule/LayerRuleApplicator.cpp +++ b/src/desktop/rule/layerRule/LayerRuleApplicator.cpp @@ -4,7 +4,6 @@ #include "../../view/LayerSurface.hpp" #include "../../types/OverridableVar.hpp" #include "../../../helpers/MiscFunctions.hpp" -#include "../../../event/EventBus.hpp" using namespace Desktop; using namespace Desktop::Rule; @@ -33,38 +32,11 @@ 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; @@ -101,8 +73,8 @@ void CLayerRuleApplicator::applyDynamicRule(const SP& rule) { } case LAYER_RULE_EFFECT_ORDER: { try { - m_order.first.set(std::stoi(effect), Types::PRIORITY_WINDOW_RULE); - m_order.second |= rule->getPropertiesMask(); + m_noScreenShare.first.set(std::stoi(effect), Types::PRIORITY_WINDOW_RULE); + m_noScreenShare.second |= rule->getPropertiesMask(); } catch (...) { Log::logger->log(Log::ERR, "CLayerRuleApplicator::applyDynamicRule: invalid order {}", effect); } break; } @@ -153,7 +125,4 @@ 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 35aa18c5..97f15b04 100644 --- a/src/desktop/rule/layerRule/LayerRuleApplicator.hpp +++ b/src/desktop/rule/layerRule/LayerRuleApplicator.hpp @@ -1,6 +1,5 @@ #pragma once -#include "LayerRuleEffectContainer.hpp" #include "../../DesktopTypes.hpp" #include "../Rule.hpp" #include "../../types/OverridableVar.hpp" @@ -22,17 +21,6 @@ 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 fea5c384..abaa1657 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 && ws->matchesStaticSelector(m_value); + return ws->matchesStaticSelector(m_value); } diff --git a/src/desktop/rule/windowRule/WindowRule.cpp b/src/desktop/rule/windowRule/WindowRule.cpp index 8028e482..d1994d2e 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_group)) + if (!engine->match(w->m_groupData.pNextWindow)) return false; break; case RULE_PROP_MODAL: @@ -97,31 +97,27 @@ bool CWindowRule::matches(PHLWINDOW w, bool allowEnvLookup) { return false; break; case RULE_PROP_CONTENT: - if (!engine->match(w->getContentType()) && !engine->match(NContentType::toString(w->getContentType()))) + if (!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(); - bool match = false; - + const auto ENV = w->getEnv(); 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; + const auto TKN = ENV.at(EXEC_RULE_ENV_NAME); + if (!engine->match(TKN)) + return false; + break; } - if (!match) - return false; - break; + + return false; } } @@ -157,6 +153,11 @@ 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 07cb5f64..0b6cba0f 100644 --- a/src/desktop/rule/windowRule/WindowRuleApplicator.cpp +++ b/src/desktop/rule/windowRule/WindowRuleApplicator.cpp @@ -4,7 +4,8 @@ #include "../utils/SetUtils.hpp" #include "../../view/Window.hpp" #include "../../types/OverridableVar.hpp" -#include "../../../event/EventBus.hpp" +#include "../../../managers/LayoutManager.hpp" +#include "../../../managers/HookSystemManager.hpp" #include @@ -150,7 +151,7 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const CGradientValueData activeBorderGradient = {}; CGradientValueData inactiveBorderGradient = {}; bool active = true; - CVarList colorsAndAngles = CVarList(trim(effect), 0, 's', true); + CVarList colorsAndAngles = CVarList(trim(effect.substr(effect.find_first_of(' ') + 1)), 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")) { @@ -158,8 +159,6 @@ 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; } @@ -266,17 +265,13 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const if (!m_window) break; - 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) { + const auto VEC = configStringToVector2D(effect); + 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()); @@ -291,18 +286,13 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyDynamicRule(const if (!m_window) break; - 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) { + const auto VEC = configStringToVector2D(effect); + 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()); } @@ -547,7 +537,7 @@ CWindowRuleApplicator::SRuleResult CWindowRuleApplicator::applyStaticRule(const return SRuleResult{}; } -void CWindowRuleApplicator::readStaticRules(bool preRead) { +void CWindowRuleApplicator::readStaticRules() { if (!m_window) return; @@ -602,8 +592,7 @@ void CWindowRuleApplicator::readStaticRules(bool preRead) { for (const auto& wr : execRules) { applyStaticRule(wr); applyDynamicRule(wr); - if (!preRead) - ruleEngine()->unregisterRule(wr); + ruleEngine()->unregisterRule(wr); } } @@ -630,13 +619,11 @@ void CWindowRuleApplicator::propertiesChanged(std::underlying_type_tupdateWindowData(); - m_window->updateWindowDecos(); m_window->updateDecorationValues(); if (needsRelayout) g_pDecorationPositioner->forceRecalcFor(m_window.lock()); // for plugins - Event::bus()->m_events.window.updateRules.emit(m_window.lock()); + EMIT_HOOK_EVENT("windowUpdateRules", m_window.lock()); } diff --git a/src/desktop/rule/windowRule/WindowRuleApplicator.hpp b/src/desktop/rule/windowRule/WindowRuleApplicator.hpp index 5c1d4fd1..121de727 100644 --- a/src/desktop/rule/windowRule/WindowRuleApplicator.hpp +++ b/src/desktop/rule/windowRule/WindowRuleApplicator.hpp @@ -33,7 +33,8 @@ 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(bool preRead = false); + void readStaticRules(); + void applyStaticRules(); // static props struct { diff --git a/src/desktop/state/FocusState.cpp b/src/desktop/state/FocusState.cpp index c1298766..2c1158be 100644 --- a/src/desktop/state/FocusState.cpp +++ b/src/desktop/state/FocusState.cpp @@ -3,19 +3,14 @@ #include "../../Compositor.hpp" #include "../../protocols/XDGShell.hpp" #include "../../render/Renderer.hpp" +#include "../../managers/LayoutManager.hpp" #include "../../managers/EventManager.hpp" -#include "../../managers/input/InputManager.hpp" -#include "../../managers/SeatManager.hpp" +#include "../../managers/HookSystemManager.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; @@ -37,7 +32,6 @@ 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 {}; } @@ -67,7 +61,7 @@ static SFullscreenWorkspaceFocusResult onFullscreenWorkspaceFocusWindow(PHLWINDO return {}; } -void CFocusState::fullWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SP surface, bool forceFSCycle) { +void CFocusState::fullWindowFocus(PHLWINDOW pWindow, SP surface, bool forceFSCycle) { if (pWindow) { if (!pWindow->m_workspace) return; @@ -87,10 +81,10 @@ void CFocusState::fullWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SP surface) { +void CFocusState::rawWindowFocus(PHLWINDOW pWindow, SP surface) { static auto PFOLLOWMOUSE = CConfigValue("input:follow_mouse"); static auto PSPECIALFALLTHROUGH = CConfigValue("input:special_fallthrough"); @@ -109,9 +103,7 @@ void CFocusState::rawWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SPm_isX11 && pWindow->isX11OverrideRedirect() && !pWindow->m_xwaylandSurface->wantsFocus()) return; - // m_target on purpose, this avoids the group - if (pWindow) - g_layoutManager->bringTargetToTop(pWindow->m_target); + g_pLayoutManager->getCurrentLayout()->bringWindowToTop(pWindow); if (!pWindow || !validMapped(pWindow)) { @@ -133,7 +125,9 @@ void CFocusState::rawWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SPpostEvent(SHyprIPCEvent{"activewindow", ","}); g_pEventManager->postEvent(SHyprIPCEvent{"activewindowv2", ""}); - Event::bus()->m_events.window.active.emit(nullptr, reason); + EMIT_HOOK_EVENT("activeWindow", PHLWINDOW{nullptr}); + + g_pLayoutManager->getCurrentLayout()->onWindowFocusChange(nullptr); m_focusSurface.reset(); @@ -200,14 +194,16 @@ void CFocusState::rawWindowFocus(PHLWINDOW pWindow, eFocusReason reason, SPpostEvent(SHyprIPCEvent{.event = "activewindow", .data = pWindow->m_class + "," + pWindow->m_title}); g_pEventManager->postEvent(SHyprIPCEvent{.event = "activewindowv2", .data = std::format("{:x}", rc(pWindow.get()))}); - Event::bus()->m_events.window.active.emit(pWindow, reason); + EMIT_HOOK_EVENT("activeWindow", pWindow); + + g_pLayoutManager->getCurrentLayout()->onWindowFocusChange(pWindow); g_pInputManager->recheckIdleInhibitorStatus(); if (*PFOLLOWMOUSE == 0) g_pInputManager->sendMotionEventsToFocused(); - if (pWindow->m_group) + if (pWindow->m_groupData.pNextWindow) pWindow->deactivateGroupMembers(); } @@ -233,7 +229,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 = ""}); - Event::bus()->m_events.input.keyboard.focus.emit(nullptr); + EMIT_HOOK_EVENT("keyboardFocus", SP{nullptr}); m_focusSurface.reset(); return; } @@ -249,7 +245,7 @@ void CFocusState::rawSurfaceFocus(SP pSurface, PHLWINDOW pWi g_pXWaylandManager->activateSurface(pSurface, true); m_focusSurface = pSurface; - Event::bus()->m_events.input.keyboard.focus.emit(pSurface); + EMIT_HOOK_EVENT("keyboardFocus", pSurface); const auto SURF = Desktop::View::CWLSurface::fromResource(pSurface); const auto OLDSURF = Desktop::View::CWLSurface::fromResource(PLASTSURF); @@ -278,7 +274,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}); - Event::bus()->m_events.monitor.focused.emit(pMonitor); + EMIT_HOOK_EVENT("focusedMon", pMonitor); m_focusMonitor = pMonitor; } @@ -298,7 +294,3 @@ 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 71330a3e..93ab2215 100644 --- a/src/desktop/state/FocusState.hpp +++ b/src/desktop/state/FocusState.hpp @@ -1,24 +1,11 @@ #pragma once #include "../DesktopTypes.hpp" -#include "../../helpers/signal/Signal.hpp" +#include "../../SharedDefs.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(); @@ -28,8 +15,8 @@ namespace Desktop { CFocusState(CFocusState&) = delete; CFocusState(const CFocusState&) = delete; - void fullWindowFocus(PHLWINDOW w, eFocusReason reason, SP surface = nullptr, bool forceFSCycle = false); - void rawWindowFocus(PHLWINDOW w, eFocusReason reason, SP surface = nullptr); + void fullWindowFocus(PHLWINDOW w, SP surface = nullptr, bool forceFSCycle = false); + void rawWindowFocus(PHLWINDOW w, SP surface = nullptr); void rawSurfaceFocus(SP s, PHLWINDOW pWindowOwner = nullptr); void rawMonitorFocus(PHLMONITOR m); @@ -44,7 +31,7 @@ namespace Desktop { PHLWINDOWREF m_focusWindow; PHLMONITORREF m_focusMonitor; - CHyprSignalListener m_windowOpen, m_windowClose; + SP m_windowOpen, m_windowClose; }; SP focusState(); diff --git a/src/desktop/view/Group.cpp b/src/desktop/view/Group.cpp deleted file mode 100644 index 06884cff..00000000 --- a/src/desktop/view/Group.cpp +++ /dev/null @@ -1,351 +0,0 @@ -#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 deleted file mode 100644 index 36c4baae..00000000 --- a/src/desktop/view/Group.hpp +++ /dev/null @@ -1,69 +0,0 @@ -#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 a10c9d4d..f61d9554 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,11 +23,24 @@ 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); @@ -37,19 +50,6 @@ 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}); - Event::bus()->m_events.layer.opened.emit(m_self.lock()); + EMIT_HOOK_EVENT("openLayer", 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}); - Event::bus()->m_events.layer.closed.emit(m_self.lock()); + EMIT_HOOK_EVENT("closeLayer", m_self.lock()); std::erase_if(g_pInputManager->m_exclusiveLSes, [this](const auto& other) { return !other || other == m_self; }); @@ -323,18 +323,16 @@ 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[NEW_LAYER].emplace_back(*it); + PMONITOR->m_layerSurfaceLayers[m_layerSurface->m_current.layer].emplace_back(*it); PMONITOR->m_layerSurfaceLayers[m_layer].erase(it); break; } } - m_layer = NEW_LAYER; - m_aboveFullscreen = NEW_LAYER >= ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; + m_layer = m_layerSurface->m_current.layer; + m_aboveFullscreen = m_layerSurface->m_current.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 f6f681d5..841674e7 100644 --- a/src/desktop/view/Popup.cpp +++ b/src/desktop/view/Popup.cpp @@ -9,7 +9,6 @@ #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 @@ -109,12 +108,8 @@ void CPopup::initAllSignals() { m_alpha->setCallbackOnEnd( [this](auto) { if (inert()) { - g_pEventLoopManager->doLater([p = m_self] { - if (!p) - return; - g_pHyprRenderer->damageBox(CBox{p->coordsGlobal(), p->size()}); - p->fullyDestroy(); - }); + g_pHyprRenderer->damageBox(CBox{coordsGlobal(), size()}); + fullyDestroy(); } }, false); @@ -199,7 +194,7 @@ void CPopup::onMap() { //unconstrain(); sendScale(); - m_wlSurface->resource()->breadthfirst([PMONITOR](SP s, const Vector2D& offset, void* d) { s->enter(PMONITOR->m_self.lock()); }, nullptr); + m_resource->m_surface->m_surface->enter(PMONITOR->m_self.lock()); if (!m_layerOwner.expired() && m_layerOwner->m_layer < ZWLR_LAYER_SHELL_V1_LAYER_TOP) g_pHyprOpenGL->markBlurDirtyForMonitor(g_pCompositor->getMonitorFromID(m_layerOwner->m_layer)); @@ -339,7 +334,8 @@ void CPopup::reposition() { if (!PMONITOR) return; - m_resource->applyPositioning(m_windowOwner ? PMONITOR->logicalBoxMinusReserved() : PMONITOR->logicalBox(), COORDS); + CBox box = {PMONITOR->m_position.x, PMONITOR->m_position.y, PMONITOR->m_size.x, PMONITOR->m_size.y}; + m_resource->applyPositioning(box, COORDS); } SP CPopup::getT1Owner() const { @@ -403,14 +399,10 @@ 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 || !c->visible()) + if (!c->visible()) continue; - - // keep ref, onCommit can call onDestroy - auto x = c.lock(); - - x->onCommit(true); - x->recheckChildrenRecursive(); + c->onCommit(true); + c->recheckChildrenRecursive(); } } diff --git a/src/desktop/view/Window.cpp b/src/desktop/view/Window.cpp index abf4da95..695ba81f 100644 --- a/src/desktop/view/Window.cpp +++ b/src/desktop/view/Window.cpp @@ -3,8 +3,6 @@ #include #include -#include "Group.hpp" - #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) #include #include @@ -39,15 +37,12 @@ #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 @@ -58,13 +53,6 @@ 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)); @@ -87,8 +75,6 @@ PHLWINDOW CWindow::create(SP surface) { pWindow->addWindowDeco(makeUnique(pWindow)); pWindow->addWindowDeco(makeUnique(pWindow)); - pWindow->m_target = Layout::CWindowTarget::create(pWindow); - return pWindow; } @@ -114,14 +100,12 @@ 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), m_stableID(windowIDCounter++) { +CWindow::CWindow(SP resource) : IView(CWLSurface::create()), m_xdgSurface(resource) { 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(); }); @@ -131,7 +115,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), m_stableID(windowIDCounter++) { +CWindow::CWindow(SP surface) : IView(CWLSurface::create()), m_xwaylandSurface(surface) { 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(); }); @@ -262,7 +246,7 @@ CBox CWindow::getFullWindowBoundingBox() const { CBox CWindow::getWindowIdealBoundingBoxIgnoreReserved() { const auto PMONITOR = m_monitor.lock(); - if (!PMONITOR || !m_workspace) + if (!PMONITOR) return {m_position, m_size}; auto POS = m_position; @@ -275,25 +259,20 @@ CBox CWindow::getWindowIdealBoundingBoxIgnoreReserved() { return CBox{sc(POS.x), sc(POS.y), sc(SIZE.x), sc(SIZE.y)}; } - // 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.y - PMONITOR->m_position.y, PMONITOR->m_reservedArea.top(), 1)) { + POS.y = PMONITOR->m_position.y; + SIZE.y += PMONITOR->m_reservedArea.top(); } - - if (DELTALESSTHAN(POS.y, WORKAREA.y, 1)) { - POS.y -= RESERVED.top(); - SIZE.y += RESERVED.top(); + 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.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)}; } @@ -370,18 +349,14 @@ void CWindow::addWindowDeco(UP deco) { m_windowDecorations.emplace_back(std::move(deco)); g_pDecorationPositioner->forceRecalcFor(m_self.lock()); updateWindowDecos(); - - if (layoutTarget()) - layoutTarget()->recalc(); + g_pLayoutManager->getCurrentLayout()->recalculateWindow(m_self.lock()); } void CWindow::removeWindowDeco(IHyprWindowDecoration* deco) { m_decosToRemove.push_back(deco); g_pDecorationPositioner->forceRecalcFor(m_self.lock()); updateWindowDecos(); - - if (layoutTarget()) - layoutTarget()->recalc(); + g_pLayoutManager->getCurrentLayout()->recalculateWindow(m_self.lock()); } void CWindow::uncacheWindowDecos() { @@ -484,13 +459,11 @@ void CWindow::moveToWorkspace(PHLWORKSPACE pWorkspace) { if (TOKEN) { if (*PINITIALWSTRACKING == 2) { // persistent - 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) { ; } + SInitialWorkspaceToken token = std::any_cast(TOKEN->m_data); + if (token.primaryOwner == m_self) { + token.workspace = pWorkspace->getConfigName(); + TOKEN->m_data = token; + } } } } @@ -510,12 +483,20 @@ 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)}); - Event::bus()->m_events.window.moveToWorkspace.emit(m_self.lock(), pWorkspace); + EMIT_HOOK_EVENT("moveWindow", (std::vector{m_self.lock(), pWorkspace})); } if (const auto SWALLOWED = m_swallowed.lock()) { @@ -568,11 +549,9 @@ void CWindow::onUnmap() { if (TOKEN) { if (*PINITIALWSTRACKING == 2) { // persistent token, but the first window got removed so the token is gone - 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); } + SInitialWorkspaceToken token = std::any_cast(TOKEN->m_data); + if (token.primaryOwner == m_self) + g_pTokenManager->removeToken(TOKEN); } } } @@ -600,7 +579,7 @@ void CWindow::onUnmap() { m_workspace->updateWindows(); m_workspace->updateWindowData(); } - + g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitorID()); g_pCompositor->updateAllWindowsAnimatedDecorationValues(); m_workspace.reset(); @@ -647,18 +626,6 @@ 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; @@ -693,9 +660,6 @@ 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(); @@ -747,6 +711,311 @@ 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; } @@ -801,13 +1070,9 @@ void CWindow::updateWindowData() { } void CWindow::updateWindowData(const SWorkspaceRule& workspaceRule) { - 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->borderSize().matchOptional(workspaceRule.borderSize, 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); } @@ -871,7 +1136,7 @@ void CWindow::setAnimationsToMove() { void CWindow::onWorkspaceAnimUpdate() { // clip box for animated offsets - if (!m_isFloating || m_pinned || isFullscreen()) { + if (!m_isFloating || m_pinned || isFullscreen() || m_draggingTiled) { m_floatingOffset = Vector2D(0, 0); return; } @@ -1035,7 +1300,7 @@ void CWindow::activate(bool force) { m_isUrgent = true; g_pEventManager->postEvent(SHyprIPCEvent{.event = "urgent", .data = std::format("{:x}", rc(this))}); - Event::bus()->m_events.window.urgent.emit(m_self.lock()); + EMIT_HOOK_EVENT("urgent", m_self.lock()); if (!force && (!m_ruleApplicator->focusOnActivate().valueOr(*PFOCUSONACTIVATE) || (m_suppressedEvents & SUPPRESS_ACTIVATE_FOCUSONLY) || (m_suppressedEvents & SUPPRESS_ACTIVATE))) @@ -1049,7 +1314,7 @@ void CWindow::activate(bool force) { if (m_isFloating) g_pCompositor->changeWindowZOrder(m_self.lock(), true); - Desktop::focusState()->fullWindowFocus(m_self.lock(), FOCUS_REASON_DESKTOP_STATE_CHANGE); + Desktop::focusState()->fullWindowFocus(m_self.lock()); warpCursor(); } @@ -1096,13 +1361,12 @@ 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)}); - Event::bus()->m_events.window.title.emit(m_self.lock()); + EMIT_HOOK_EVENT("windowTitle", 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))}); - - // no need for a hook event + EMIT_HOOK_EVENT("activeWindow", m_self.lock()); } Log::logger->log(Log::DEBUG, "Window {:x} set title to {}", rc(this), m_title); @@ -1113,13 +1377,10 @@ 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))}); - - // no need for a hook event + EMIT_HOOK_EVENT("activeWindow", m_self.lock()); } Log::logger->log(Log::DEBUG, "Window {:x} set class to {}", rc(this), m_class); @@ -1199,7 +1460,7 @@ void CWindow::onX11ConfigureRequest(CBox box) { g_pHyprRenderer->damageWindow(m_self.lock()); - if (!m_isFloating || isFullscreen() || g_layoutManager->dragController()->target() == m_self) { + if (!m_isFloating || isFullscreen() || g_pInputManager->m_currentlyDraggedWindow == m_self) { sendWindowSize(true); g_pInputManager->refocus(); g_pHyprRenderer->damageWindow(m_self.lock()); @@ -1230,22 +1491,7 @@ void CWindow::onX11ConfigureRequest(CBox box) { if (!m_workspace || !m_workspace->isVisible()) return; // further things are only for visible windows - 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; - } + m_workspace = g_pCompositor->getMonitorFromVector(m_realPosition->goal() + m_realSize->goal() / 2.f)->m_activeWorkspace; g_pCompositor->changeWindowZOrder(m_self.lock(), true); @@ -1338,8 +1584,7 @@ Vector2D CWindow::realToReportSize() { const auto PMONITOR = m_monitor.lock(); if (*PXWLFORCESCALEZERO && PMONITOR) - // Keep X11 configure sizes integral to avoid truncation (e.g. 2879.999 -> 2879) later in xcb. - return (REPORTSIZE * PMONITOR->m_scale).round(); + return REPORTSIZE * PMONITOR->m_scale; return REPORTSIZE; } @@ -1416,17 +1661,20 @@ void CWindow::setContentType(NContentType::eContentType contentType) { } void CWindow::deactivateGroupMembers() { - if (!m_group) - return; - for (const auto& w : m_group->windows()) { - if (w != m_self.lock()) { + auto curr = getGroupHead(); + while (curr) { + if (curr != 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 (!w->m_isX11 && w->m_xdgSurface && w->m_xdgSurface->m_toplevel) - w->m_xdgSurface->m_toplevel->setActive(false); + if (!curr->m_isX11 && curr->m_xdgSurface && curr->m_xdgSurface->m_toplevel) + curr->m_xdgSurface->m_toplevel->setActive(false); } + + curr = curr->m_groupData.pNextWindow.lock(); + if (curr == getGroupHead()) + break; } } @@ -1551,13 +1799,21 @@ void CWindow::updateDecorationValues() { const bool IS_SHADOWED_BY_MODAL = m_xdgSurface && m_xdgSurface->m_toplevel && m_xdgSurface->m_toplevel->anyChildModal(); - 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)); + // 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)); + } } // opacity @@ -1645,7 +1901,6 @@ 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; @@ -1770,8 +2025,8 @@ void CWindow::mapWindow() { requestedFSMonitor = MONITOR_INVALID; } - m_isFloating = m_ruleApplicator->static_.floating.value_or(m_isFloating); - m_target->setPseudo(m_ruleApplicator->static_.pseudo.value_or(m_target->isPseudo())); + m_isFloating = m_ruleApplicator->static_.floating.value_or(m_isFloating); + m_isPseudotiled = m_ruleApplicator->static_.pseudo.value_or(m_isPseudotiled); m_noInitialFocus = m_ruleApplicator->static_.noInitialFocus.value_or(m_noInitialFocus); m_pinned = m_ruleApplicator->static_.pin.value_or(m_pinned); @@ -1826,7 +2081,7 @@ void CWindow::mapWindow() { } else if (v == "barred") { m_groupRules |= Desktop::View::GROUP_BARRED; } else if (v == "deny") { - m_groupRules |= Desktop::View::GROUP_DENY; + m_groupData.deny = true; } else if (v == "override") { // Clear existing rules m_groupRules = Desktop::View::GROUP_OVERRIDE; @@ -1871,12 +2126,11 @@ void CWindow::mapWindow() { if (WORKSPACEARGS.contains("silent")) workspaceSilent = true; - auto joined = WORKSPACEARGS.join(" ", 0, workspaceSilent ? WORKSPACEARGS.size() - 1 : 0); - if (joined.starts_with("empty") && PWORKSPACE->getWindows() == 0) { + if (WORKSPACEARGS.contains("empty") && PWORKSPACE->getWindows() <= 1) { requestedWorkspaceID = PWORKSPACE->m_id; requestedWorkspaceName = PWORKSPACE->m_name; } else { - auto result = getWorkspaceIDNameFromString(joined); + auto result = getWorkspaceIDNameFromString(WORKSPACEARGS.join(" ", 0, workspaceSilent ? WORKSPACEARGS.size() - 1 : 0)); requestedWorkspaceID = result.id; requestedWorkspaceName = result.name; } @@ -1931,9 +2185,9 @@ void CWindow::mapWindow() { m_isFloating = true; if (PWORKSPACE->m_defaultPseudo) { + m_isPseudotiled = true; CBox desiredGeometry = g_pXWaylandManager->getGeometryForWindow(m_self.lock()); - m_target->setPseudoSize(Vector2D{desiredGeometry.width, desiredGeometry.height}); - m_target->setPseudo(true); + m_pseudoSize = Vector2D(desiredGeometry.width, desiredGeometry.height); } updateWindowData(); @@ -1946,33 +2200,45 @@ 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)}); - 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}); + EMIT_HOOK_EVENT("openWindowEarly", m_self.lock()); 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_target->setPseudoSize(m_realSize->goal()); + m_pseudoSize = 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()) { @@ -1980,14 +2246,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_target->setPseudoSize(*COMPUTED); + setPseudo = true; + m_pseudoSize = *COMPUTED; setHidden(false); } } if (!setPseudo) - m_target->setPseudoSize(m_realSize->goal() - Vector2D(10, 10)); + m_pseudoSize = m_realSize->goal() - Vector2D(10, 10); } const auto PFOCUSEDWINDOWPREV = Desktop::focusState()->window(); @@ -2017,13 +2283,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 = m_group && m_group->has(LAST_FOCUS_WINDOW); + const bool SAME_GROUP = hasInGroup(LAST_FOCUS_WINDOW); if (IS_LAST_IN_FS && SAME_GROUP) { - Desktop::focusState()->rawWindowFocus(m_self.lock(), FOCUS_REASON_NEW_WINDOW); + Desktop::focusState()->rawWindowFocus(m_self.lock()); g_pCompositor->setWindowFullscreenInternal(m_self.lock(), LAST_FS_MODE); } else - Desktop::focusState()->fullWindowFocus(m_self.lock(), FOCUS_REASON_NEW_WINDOW); + Desktop::focusState()->fullWindowFocus(m_self.lock()); m_activeInactiveAlpha->setValueAndWarp(*PACTIVEALPHA); m_dimPercent->setValueAndWarp(m_ruleApplicator->noDim().valueOrDefault() ? 0.f : *PDIMSTRENGTH); @@ -2064,16 +2330,18 @@ void CWindow::mapWindow() { if (workspaceSilent) { if (validMapped(PFOCUSEDWINDOWPREV)) { - Desktop::focusState()->rawWindowFocus(PFOCUSEDWINDOWPREV, FOCUS_REASON_NEW_WINDOW); + Desktop::focusState()->rawWindowFocus(PFOCUSEDWINDOWPREV); PFOCUSEDWINDOWPREV->updateWindowDecos(); // need to for some reason i cba to find out why } else if (!PFOCUSEDWINDOWPREV) - Desktop::focusState()->rawWindowFocus(nullptr, FOCUS_REASON_NEW_WINDOW); + Desktop::focusState()->rawWindowFocus(nullptr); } // swallow if (SWALLOWER) { - g_layoutManager->removeTarget(SWALLOWER->layoutTarget()); + g_pLayoutManager->getCurrentLayout()->onWindowRemoved(SWALLOWER); + g_pHyprRenderer->damageWindow(SWALLOWER); SWALLOWER->setHidden(true); + g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitorID()); } m_firstMap = false; @@ -2081,12 +2349,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 - Event::bus()->m_events.window.open.emit(m_self.lock()); + EMIT_HOOK_EVENT("openWindow", m_self.lock()); // apply data from default decos. Borders, shadows. g_pDecorationPositioner->forceRecalcFor(m_self.lock()); updateWindowDecos(); - layoutTarget()->recalc(); + g_pLayoutManager->getCurrentLayout()->recalculateWindow(m_self.lock()); // do animations g_pDesktopAnimationManager->startAnimation(m_self.lock(), CDesktopAnimationManager::ANIMATION_TYPE_IN); @@ -2112,11 +2380,8 @@ void CWindow::mapWindow() { if (m_workspace) m_workspace->updateWindows(); - if (PMONITOR && isX11OverrideRedirect()) { - static auto PXWLFORCESCALEZERO = CConfigValue("xwayland:force_zero_scaling"); - if (*PXWLFORCESCALEZERO) - m_X11SurfaceScaledBy = PMONITOR->m_scale; - } + if (PMONITOR && isX11OverrideRedirect()) + m_X11SurfaceScaledBy = PMONITOR->m_scale; } void CWindow::unmapWindow() { @@ -2140,9 +2405,8 @@ void CWindow::unmapWindow() { m_originalClosedExtents = getFullWindowExtents(); } - m_events.unmap.emit(); g_pEventManager->postEvent(SHyprIPCEvent{"closewindow", std::format("{:x}", m_self.lock())}); - Event::bus()->m_events.window.close.emit(m_self.lock()); + EMIT_HOOK_EVENT("closeWindow", 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); @@ -2162,10 +2426,10 @@ void CWindow::unmapWindow() { m_swallowed->m_currentlySwallowed = false; m_swallowed->setHidden(false); - if (m_group) + if (m_groupData.pNextWindow.lock()) m_swallowed->m_groupSwallowed = true; // flag for the swallowed window to be created into the group where it belongs when auto_group = false. - g_layoutManager->newTarget(m_swallowed->layoutTarget(), m_workspace->m_space); + g_pLayoutManager->getCurrentLayout()->onWindowCreated(m_swallowed.lock()); } m_swallowed->m_groupSwallowed = false; @@ -2173,24 +2437,7 @@ void CWindow::unmapWindow() { } bool wasLastWindow = false; - 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; - }(); + PHLWINDOW nextInGroup = m_groupData.pNextWindow ? m_groupData.pNextWindow.lock() : nullptr; if (m_self.lock() == Desktop::focusState()->window()) { wasLastWindow = true; @@ -2199,7 +2446,7 @@ void CWindow::unmapWindow() { g_pInputManager->releaseAllMouseButtons(); } - if (m_self.lock() == g_layoutManager->dragController()->target()) + if (m_self.lock() == g_pInputManager->m_currentlyDraggedWindow.lock()) CKeybindManager::changeMouseBindMode(MBIND_INVALID); // remove the fullscreen window status from workspace if we closed it @@ -2208,10 +2455,7 @@ void CWindow::unmapWindow() { if (PWORKSPACE->m_hasFullscreenWindow && isFullscreen()) PWORKSPACE->m_hasFullscreenWindow = false; - if (m_group) - m_group->remove(m_self.lock()); - - g_layoutManager->removeTarget(m_target); + g_pLayoutManager->getCurrentLayout()->onWindowRemoved(m_self.lock()); g_pHyprRenderer->damageWindow(m_self.lock()); @@ -2227,20 +2471,17 @@ 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 { - const auto CAND = g_layoutManager->getNextCandidate(m_workspace->m_space, layoutTarget()); - if (CAND) - candidate = CAND->window(); - } + else + candidate = g_pLayoutManager->getCurrentLayout()->getNextWindowCandidate(m_self.lock()); } 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, FOCUS_REASON_DESKTOP_STATE_CHANGE); + Desktop::focusState()->rawWindowFocus(candidate); else - Desktop::focusState()->fullWindowFocus(candidate, FOCUS_REASON_DESKTOP_STATE_CHANGE); + Desktop::focusState()->fullWindowFocus(candidate); if ((*PEXITRETAINSFS || candidate == nextInGroup) && CURRENTWINDOWFSSTATE) g_pCompositor->setWindowFullscreenInternal(candidate, CURRENTFSMODE); @@ -2255,8 +2496,7 @@ void CWindow::unmapWindow() { if (m_self.lock() == Desktop::focusState()->window() || !Desktop::focusState()->window()) { g_pEventManager->postEvent(SHyprIPCEvent{"activewindow", ","}); g_pEventManager->postEvent(SHyprIPCEvent{"activewindowv2", ""}); - - Event::bus()->m_events.window.active.emit(m_self.lock(), FOCUS_REASON_OTHER); + EMIT_HOOK_EVENT("activeWindow", PHLWINDOW{nullptr}); } } else { Log::logger->log(Log::DEBUG, "Unmapped was not focused, ignoring a refocus."); @@ -2285,16 +2525,7 @@ void CWindow::unmapWindow() { void CWindow::commitWindow() { if (!m_isX11 && m_xdgSurface->m_initialCommit) { - // 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{}; + Vector2D predSize = g_pLayoutManager->getCurrentLayout()->predictSizeForNewWindow(m_self.lock()); Log::logger->log(Log::DEBUG, "Layout predicts size {} for {}", predSize, m_self.lock()); @@ -2321,7 +2552,20 @@ void CWindow::commitWindow() { const auto PMONITOR = m_monitor.lock(); - g_pHyprRenderer->damageSurface(wlSurface()->resource(), m_realPosition->goal().x, m_realPosition->goal().y, m_isX11 ? 1.0 / m_X11SurfaceScaledBy : 1.0); + 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; + } if (!m_isX11) { m_subsurfaceHead->recheckDamageForSubsurfaces(); @@ -2355,21 +2599,21 @@ void CWindow::destroyWindow() { m_listeners = {}; - g_layoutManager->removeTarget(m_target); + g_pLayoutManager->getCurrentLayout()->onWindowRemoved(m_self.lock()); m_readyToDelete = true; m_xdgSurface.reset(); - 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 } + + m_listeners.unmap.reset(); + m_listeners.destroy.reset(); + m_listeners.map.reset(); + m_listeners.commit.reset(); } void CWindow::activateX11() { @@ -2385,7 +2629,7 @@ void CWindow::activateX11() { if (!m_xwaylandSurface->wantsFocus()) return; - Desktop::focusState()->fullWindowFocus(m_self.lock(), FOCUS_REASON_DESKTOP_STATE_CHANGE); + Desktop::focusState()->fullWindowFocus(m_self.lock()); return; } @@ -2417,19 +2661,21 @@ void CWindow::unmanagedSetGeometry() { const auto LOGICALPOS = g_pXWaylandManager->xwaylandToWaylandCoords(m_xwaylandSurface->m_geometry.pos()); - 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) { + 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) { 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) - LOGICALGEOSIZE.x) > 2 || abs(std::floor(SIZ.y) - LOGICALGEOSIZE.y) > 2) - m_realSize->setValueAndWarp(LOGICALGEOSIZE); + 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); + } + } m_position = m_realPosition->goal(); m_size = m_realSize->goal(); @@ -2483,26 +2729,3 @@ 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 d689ae3f..3c36283d 100644 --- a/src/desktop/view/Window.hpp +++ b/src/desktop/view/Window.hpp @@ -26,19 +26,8 @@ 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, @@ -49,7 +38,6 @@ 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 { @@ -73,11 +61,6 @@ 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; @@ -109,17 +92,11 @@ 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); @@ -135,21 +112,30 @@ 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, m_prevMonitor; + PHLMONITORREF m_monitor; bool m_isMapped = false; @@ -243,13 +229,15 @@ namespace Desktop::View { std::string m_initialWorkspaceToken = ""; // for groups - SP m_group; - uint16_t m_groupRules = Desktop::View::GROUP_NONE; + 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; - bool m_tearingHint = false; - - // Stable ID for ext_foreign_toplevel_list - const uint64_t m_stableID = 0x2137; + bool m_tearingHint = false; // ANR PHLANIMVAR m_notRespondingTint; @@ -312,6 +300,21 @@ 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(); @@ -325,6 +328,8 @@ namespace Desktop::View { PHLWINDOW getSwallower(); bool isX11OverrideRedirect(); bool isModal(); + Vector2D requestedMinSize(); + Vector2D requestedMaxSize(); Vector2D realToReportSize(); Vector2D realToReportPosition(); Vector2D xwaylandSizeToReal(Vector2D size); @@ -344,8 +349,6 @@ 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 deleted file mode 100644 index f06c3984..00000000 --- a/src/event/EventBus.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#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 deleted file mode 100644 index a30288f0..00000000 --- a/src/event/EventBus.hpp +++ /dev/null @@ -1,144 +0,0 @@ -#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 8c2c7cd7..4cef4252 100644 --- a/src/helpers/AsyncDialogBox.cpp +++ b/src/helpers/AsyncDialogBox.cpp @@ -4,8 +4,6 @@ #include #include #include "../managers/eventLoop/EventLoopManager.hpp" -#include "../desktop/rule/windowRule/WindowRule.hpp" -#include "../desktop/rule/Engine.hpp" using namespace Hyprutils::OS; @@ -121,9 +119,6 @@ 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); @@ -159,9 +154,3 @@ 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 1bdeba14..8db516ce 100644 --- a/src/helpers/AsyncDialogBox.hpp +++ b/src/helpers/AsyncDialogBox.hpp @@ -27,7 +27,6 @@ class CAsyncDialogBox { void kill(); bool isRunning() const; pid_t getPID() const; - void setExecRule(std::string&& s); SP lockSelf(); @@ -42,8 +41,7 @@ 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_execRuleToken = ""; + std::string m_stdout = ""; const std::string m_title; const std::string m_description; diff --git a/src/helpers/Drm.cpp b/src/helpers/Drm.cpp deleted file mode 100644 index 207b5e3d..00000000 --- a/src/helpers/Drm.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#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 deleted file mode 100644 index bc56b1ee..00000000 --- a/src/helpers/Drm.hpp +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -namespace DRM { - bool sameGpu(int fd1, int fd2); -} diff --git a/src/helpers/Format.cpp b/src/helpers/Format.cpp index 7660934e..37f77d78 100644 --- a/src/helpers/Format.cpp +++ b/src/helpers/Format.cpp @@ -6,186 +6,158 @@ #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, - .glInternalFormat = GL_RGBA8, - .glFormat = GL_RGBA, - .glType = GL_UNSIGNED_BYTE, - .withAlpha = true, - .alphaStripped = DRM_FORMAT_XRGB8888, - .bytesPerBlock = 4, - .swizzle = {SWIZZLE_BGRA}, + .drmFormat = DRM_FORMAT_ARGB8888, + .flipRB = true, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_BYTE, + .withAlpha = true, + .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_XRGB8888, + .flipRB = true, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_BYTE, + .withAlpha = false, + .alphaStripped = DRM_FORMAT_XRGB8888, + .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_XBGR8888, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_BYTE, + .withAlpha = false, + .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_ABGR8888, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_BYTE, + .withAlpha = true, + .alphaStripped = DRM_FORMAT_XBGR8888, + .bytesPerBlock = 4, }, { - .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_BGR888, + .glFormat = GL_RGB, + .glType = GL_UNSIGNED_BYTE, + .withAlpha = false, + .alphaStripped = DRM_FORMAT_BGR888, + .bytesPerBlock = 3, }, { - .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_RGBX4444, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_SHORT_4_4_4_4, + .withAlpha = false, + .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_RGBA4444, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_SHORT_4_4_4_4, + .withAlpha = true, + .alphaStripped = DRM_FORMAT_RGBX4444, + .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_RGBX5551, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_SHORT_5_5_5_1, + .withAlpha = false, + .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_RGBA5551, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_SHORT_5_5_5_1, + .withAlpha = true, + .alphaStripped = DRM_FORMAT_RGBX5551, + .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_RGB565, + .glFormat = GL_RGB, + .glType = GL_UNSIGNED_SHORT_5_6_5, + .withAlpha = false, + .alphaStripped = DRM_FORMAT_RGB565, + .bytesPerBlock = 2, }, { - .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_XBGR2101010, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_INT_2_10_10_10_REV, + .withAlpha = false, + .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_ABGR2101010, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_INT_2_10_10_10_REV, + .withAlpha = true, + .alphaStripped = DRM_FORMAT_XBGR2101010, + .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_XRGB2101010, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_INT_2_10_10_10_REV, + .withAlpha = false, + .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_ARGB2101010, + .glFormat = GL_RGBA, + .glType = GL_UNSIGNED_INT_2_10_10_10_REV, + .withAlpha = true, + .alphaStripped = DRM_FORMAT_XRGB2101010, + .bytesPerBlock = 4, }, { - .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_XBGR16161616F, + .glFormat = GL_RGBA, + .glType = GL_HALF_FLOAT, + .withAlpha = false, + .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_ABGR16161616F, + .glFormat = GL_RGBA, + .glType = GL_HALF_FLOAT, + .withAlpha = true, + .alphaStripped = DRM_FORMAT_XBGR16161616F, + .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_XBGR16161616, + .glFormat = GL_RGBA16UI, + .glType = GL_UNSIGNED_SHORT, + .withAlpha = false, + .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_ABGR16161616, + .glFormat = GL_RGBA16UI, + .glType = GL_UNSIGNED_SHORT, + .withAlpha = true, + .alphaStripped = DRM_FORMAT_XBGR16161616, + .bytesPerBlock = 8, }, { .drmFormat = DRM_FORMAT_YVYU, @@ -198,28 +170,24 @@ inline const std::vector GLES3_FORMATS = { .blockSize = {2, 1}, }, { - .drmFormat = DRM_FORMAT_R8, - .glInternalFormat = GL_R8, - .glFormat = GL_RED, - .glType = GL_UNSIGNED_BYTE, - .bytesPerBlock = 1, - .swizzle = {SWIZZLE_R001}, + .drmFormat = DRM_FORMAT_R8, + .bytesPerBlock = 1, }, { - .drmFormat = DRM_FORMAT_GR88, - .glInternalFormat = GL_RG8, - .glFormat = GL_RG, - .glType = GL_UNSIGNED_BYTE, - .bytesPerBlock = 2, - .swizzle = {SWIZZLE_RG01}, + .drmFormat = DRM_FORMAT_GR88, + .bytesPerBlock = 2, }, { - .drmFormat = DRM_FORMAT_RGB888, - .glInternalFormat = GL_RGB8, - .glFormat = GL_RGB, - .glType = GL_UNSIGNED_BYTE, - .bytesPerBlock = 3, - .swizzle = {SWIZZLE_BGR1}, + .drmFormat = DRM_FORMAT_RGB888, + .bytesPerBlock = 3, + }, + { + .drmFormat = DRM_FORMAT_BGR888, + .bytesPerBlock = 3, + }, + { + .drmFormat = DRM_FORMAT_RGBX4444, + .bytesPerBlock = 2, }, }; @@ -261,26 +229,6 @@ 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) @@ -297,38 +245,32 @@ 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); - - if (!n) - return "unknown"; - + auto n = drmGetFormatName(drm); 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); - - if (!n) - return "unknown"; - + auto n = drmGetFormatModifierName(mod); 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 02925e22..fe68f763 100644 --- a/src/helpers/Format.hpp +++ b/src/helpers/Format.hpp @@ -2,43 +2,22 @@ #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 */ - 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; + 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; }; using SDRMFormat = Aquamarine::SDRMFormat; @@ -49,11 +28,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 c7b5f910..8632d93b 100644 --- a/src/helpers/MainLoopExecutor.cpp +++ b/src/helpers/MainLoopExecutor.cpp @@ -11,7 +11,10 @@ static int onDataRead(int fd, uint32_t mask, void* data) { CMainLoopExecutor::CMainLoopExecutor(std::function&& callback) : m_fn(std::move(callback)) { int fds[2]; - RASSERT(pipe(fds) == 0, "CMainLoopExecutor: failed to open a pipe"); + pipe(fds); + + RASSERT(fds[0] != 0, "CMainLoopExecutor: failed to open a pipe"); + RASSERT(fds[1] != 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 07156ff1..5069f5d8 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -2,7 +2,6 @@ #include "MiscFunctions.hpp" #include "../macros.hpp" #include "SharedDefs.hpp" -#include "../helpers/TransferFunction.hpp" #include "math/Math.hpp" #include "../protocols/ColorManagement.hpp" #include "../Compositor.hpp" @@ -23,20 +22,16 @@ #include "../protocols/core/DataDevice.hpp" #include "../render/Renderer.hpp" #include "../managers/EventManager.hpp" -#include "../managers/screenshare/ScreenshareManager.hpp" +#include "../managers/LayoutManager.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" @@ -55,7 +50,7 @@ using namespace Hyprutils::OS; using enum NContentType::eContentType; using namespace NColorManagement; -CMonitor::CMonitor(SP output_) : m_state(this), m_output(output_), m_imageDescription(DEFAULT_IMAGE_DESCRIPTION) { +CMonitor::CMonitor(SP output_) : m_state(this), m_output(output_) { 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"); @@ -76,27 +71,23 @@ CMonitor::~CMonitor() { } void CMonitor::onConnect(bool noRule) { - Event::bus()->m_events.monitor.preAdded.emit(m_self.lock()); + EMIT_HOOK_EVENT("preMonitorAdded", m_self.lock()); CScopeGuard x = {[]() { g_pCompositor->arrangeMonitors(); }}; m_zoomAnimProgress->setValueAndWarp(0.F); m_zoomAnimFrameCounter = 0; - g_pEventLoopManager->doLater([] { - g_pConfigManager->ensurePersistentWorkspacesPresent(); - g_pCompositor->ensureWorkspacesOnAssignedMonitors(); - }); + g_pEventLoopManager->doLater([] { g_pConfigManager->ensurePersistentWorkspacesPresent(); }); m_listeners.frame = m_output->events.frame.listen([this] { if (m_frameScheduler) m_frameScheduler->onFrame(); }); m_listeners.commit = m_output->events.commit.listen([this] { - m_events.commit.emit(); - - // FIXME: E->state->committed & WLR_OUTPUT_STATE_BUFFER - if (true && Screenshare::mgr()) - Screenshare::mgr()->onOutputCommit(m_self.lock()); + if (true) { // FIXME: E->state->committed & WLR_OUTPUT_STATE_BUFFER + PROTO::screencopy->onOutputCommit(m_self.lock()); + PROTO::toplevelExport->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); }); @@ -123,12 +114,10 @@ void CMonitor::onConnect(bool noRule) { ts = nullptr; } - 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 (!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 (m_zoomAnimFrameCounter < 5) { m_zoomAnimFrameCounter++; @@ -293,16 +282,10 @@ void CMonitor::onConnect(bool noRule) { if (!valid(ws)) continue; - 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) { + if (ws->m_lastMonitor == m_name || g_pCompositor->m_monitors.size() == 1 /* avoid lost workspaces on recover */) { g_pCompositor->moveWorkspaceToMonitor(ws, m_self.lock()); g_pDesktopAnimationManager->startAnimation(ws, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); - if (RETURNING) - ws->m_lastMonitor = ""; + ws->m_lastMonitor = ""; } } @@ -320,7 +303,7 @@ void CMonitor::onConnect(bool noRule) { Desktop::focusState()->rawMonitorFocus(m_self.lock()); g_pHyprRenderer->arrangeLayersForMonitor(m_id); - g_layoutManager->recalculateMonitor(m_self.lock()); + g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id); // ensure VRR (will enable if necessary) g_pConfigManager->ensureVRR(m_self.lock()); @@ -358,17 +341,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)}); - Event::bus()->m_events.monitor.added.emit(m_self.lock()); + EMIT_HOOK_EVENT("monitorAdded", m_self.lock()); } void CMonitor::onDisconnect(bool destroy) { - Event::bus()->m_events.monitor.preRemoved.emit(m_self.lock()); + EMIT_HOOK_EVENT("preMonitorRemoved", 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)}); - Event::bus()->m_events.monitor.removed.emit(m_self.lock()); + EMIT_HOOK_EVENT("monitorRemoved", m_self.lock()); g_pCompositor->scheduleMonitorStateRecheck(); }}; @@ -428,6 +411,7 @@ 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) { @@ -438,24 +422,19 @@ 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); } @@ -484,7 +463,7 @@ void CMonitor::onDisconnect(bool destroy) { PHLMONITOR pMonitorMostHz = nullptr; for (auto const& m : g_pCompositor->m_monitors) { - if (m->m_refreshRate > mostHz && m != m_self) { + if (m->m_refreshRate > mostHz) { pMonitorMostHz = m; mostHz = m->m_refreshRate; } @@ -492,118 +471,78 @@ void CMonitor::onDisconnect(bool destroy) { g_pHyprRenderer->m_mostHzMonitor = pMonitorMostHz; } - - std::erase_if(g_pCompositor->m_monitors, [&](PHLMONITOR& el) { return el.get() == this; }); } -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(); +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); switch (cmType) { - case NCMType::CM_SRGB: - m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf, - .primariesNamed = NColorManagement::CM_PRIMARIES_SRGB, - .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_SRGB)}); - break; // assumes SImageDescription defaults to sRGB + case NCMType::CM_SRGB: m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf}); 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}); + m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf, + .primariesNameSet = true, + .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, + .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_BT2020)}); 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), - .masteringPrimaries = masteringPrimaries, - .masteringLuminances = masteringLuminances, - .maxCLL = maxCLL, - .maxFALL = maxFALL}); + m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf, + .primariesNameSet = true, + .primariesNamed = NColorManagement::CM_PRIMARIES_DCI_P3, + .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_DCI_P3)}); 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), - .masteringPrimaries = masteringPrimaries, - .masteringLuminances = masteringLuminances, - .maxCLL = maxCLL, - .maxFALL = maxFALL}); + m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf, + .primariesNameSet = true, + .primariesNamed = NColorManagement::CM_PRIMARIES_DISPLAY_P3, + .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_DISPLAY_P3)}); 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), - .masteringPrimaries = masteringPrimaries, - .masteringLuminances = masteringLuminances, - .maxCLL = maxCLL, - .maxFALL = maxFALL}); + m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf, + .primariesNameSet = true, + .primariesNamed = NColorManagement::CM_PRIMARIES_ADOBE_RGB, + .primaries = NColorManagement::getPrimaries(NColorManagement::CM_PRIMARIES_ADOBE_RGB)}); break; case NCMType::CM_EDID: - m_imageDescription = CImageDescription::from({.transferFunction = chosenSdrEotf, - .primariesNameSet = false, - .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, - .primaries = masteringPrimaries, - .masteringPrimaries = masteringPrimaries, - .masteringLuminances = masteringLuminances, - .maxCLL = maxCLL, - .maxFALL = maxFALL}); + 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}, + }}); 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() ? 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}); + 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}}); break; default: UNREACHABLE(); } - if ((m_minLuminance >= 0 || m_maxLuminance >= 0 || m_maxAvgLuminance >= 0) && (cmType == NCMType::CM_HDR || cmType == NCMType::CM_HDR_EDID)) + if (m_minLuminance >= 0 || m_maxLuminance >= 0 || m_maxAvgLuminance >= 0) 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_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_maxAvgLuminance >= 0 ? m_maxAvgLuminance : m_imageDescription->value().luminances.reference // }); if (oldImageDescription != m_imageDescription) { @@ -933,46 +872,29 @@ bool CMonitor::applyMonitorRule(SMonitorRule* pMonitorRule, bool force) { m_supportsWideColor = RULE->supportsHDR; m_supportsHDR = RULE->supportsHDR; - 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_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; + 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. @@ -1054,8 +976,6 @@ 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) { @@ -1072,7 +992,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)); - Event::bus()->m_events.monitor.layoutChanged.emit(); + EMIT_HOOK_EVENT("monitorLayoutChanged", nullptr); m_events.modeChanged.emit(); @@ -1106,10 +1026,8 @@ bool CMonitor::shouldSkipScheduleFrameOnMouseEvent() { static auto PMINRR = CConfigValue("cursor:min_refresh_rate"); // skip scheduling extra frames for fullsreen apps with vrr - 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; + const bool shouldSkip = inFullscreenMode() && (*PNOBREAK == 1 || (*PNOBREAK == 2 && m_activeWorkspace->getFullscreenWindow()->getContentType() == CONTENT_TYPE_GAME)) && + m_output->state->state().adaptiveSync; // keep requested minimum refresh rate if (shouldSkip && *PMINRR && m_lastPresentationTimer.getMillis() > 1000.0f / *PMINRR) { @@ -1178,7 +1096,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_layoutManager->recalculateMonitor(m_self.lock()); + g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id); g_pDesktopAnimationManager->startAnimation(PNEWWORKSPACE, CDesktopAnimationManager::ANIMATION_TYPE_IN, true, true); } else { if (newDefaultWorkspaceName.empty()) @@ -1362,7 +1280,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->layoutTarget()->assignToSpace(pWorkspace->m_space); + w->moveToWorkspace(pWorkspace); } if (!noFocus && !Desktop::focusState()->monitor()->m_activeSpecialWorkspace && @@ -1382,17 +1300,17 @@ void CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bo pWindow = pWorkspace->getFirstWindow(); } - Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_KEYBIND); + Desktop::focusState()->fullWindowFocus(pWindow); } if (!noMouseMove) g_pInputManager->simulateMouseMovement(); - g_layoutManager->recalculateMonitor(m_self.lock()); + g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id); g_pEventManager->postEvent(SHyprIPCEvent{"workspace", pWorkspace->m_name}); g_pEventManager->postEvent(SHyprIPCEvent{"workspacev2", std::format("{},{}", pWorkspace->m_id, pWorkspace->m_name)}); - Event::bus()->m_events.workspace.active.emit(pWorkspace); + EMIT_HOOK_EVENT("workspace", pWorkspace); } // set all LSes as not above fullscreen on workspace changes @@ -1439,23 +1357,17 @@ 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_layoutManager->recalculateMonitor(m_self.lock()); + g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id); 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::FOCUS_REASON_KEYBIND); + Desktop::focusState()->fullWindowFocus(PLAST); else g_pInputManager->refocus(); } @@ -1480,17 +1392,10 @@ 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_layoutManager->recalculateMonitor(PMWSOWNER); - g_pHyprRenderer->damageMonitor(PMWSOWNER); + g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PMWSOWNER->m_id); 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 : @@ -1504,12 +1409,6 @@ 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(); @@ -1540,16 +1439,17 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) { } else pos = pos - PMONFROMMIDDLE->m_position + m_position; - w->layoutTarget()->setPositionGlobal(CBox{pos, w->layoutTarget()->position().size()}); + *w->m_realPosition = pos; + w->m_position = pos; } } } - g_layoutManager->recalculateMonitor(m_self.lock()); + g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m_id); 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::FOCUS_REASON_KEYBIND); + Desktop::focusState()->fullWindowFocus(PLAST); else g_pInputManager->refocus(); } @@ -1579,20 +1479,10 @@ 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() { @@ -1796,10 +1686,6 @@ 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; @@ -1841,6 +1727,12 @@ 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) @@ -1879,27 +1771,21 @@ 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->isDMA() /* dmabuf */) { + if (!params.success || !PSURFACE->m_current.texture->m_eglImage /* dmabuf */) { reasons |= DS_BLOCK_DMA; if (!full) return reasons; } - 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)))) + 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)))) reasons |= DS_BLOCK_CM; return reasons; } bool CMonitor::attemptDirectScanout() { - 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(); + const auto blockedReason = isDSBlocked(); if (blockedReason) return false; @@ -1913,7 +1799,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 && *PSAME) { + if (PBUFFER == m_output->state->state().buffer) { PSURFACE->presentFeedback(Time::steadyNow(), m_self.lock()); if (m_scanoutNeedsCursorUpdate) { @@ -1932,7 +1818,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 && !m_tearingState.activelyTearing && *PSAMEFIFO) + if (PSURFACE->m_fifo) PSURFACE->m_stateQueue.unlockFirst(LOCK_REASON_FIFO); return true; @@ -1963,18 +1849,7 @@ bool CMonitor::attemptDirectScanout() { PSURFACE->presentFeedback(Time::steadyNow(), m_self.lock()); m_output->state->addDamage(PSURFACE->m_current.accumulateBufferDamage()); - - // 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(); + m_output->state->resetExplicitFences(); // no need to do explicit sync here as surface current can only ever be ready to read @@ -2129,14 +2004,6 @@ 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); } @@ -2150,52 +2017,22 @@ 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 = getFullscreenWindow(); + const auto FS_WINDOW = m_activeWorkspace->getFullscreenWindow(); if (!FS_WINDOW) - return {}; + return {}; // should be unreachable 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; @@ -2216,91 +2053,24 @@ bool CMonitor::canNoShaderCM() { const auto SRC_DESC_VALUE = SRC_DESC.value()->value(); - if (m_imageDescription->value().icc.present) - return false; + if (SRC_DESC_VALUE.icc.fd >= 0 || m_imageDescription->value().icc.fd >= 0) + return false; // no ICC support - const auto sdrEOTF = NTransferFunction::fromConfig(); + static auto PSDREOTF = CConfigValue("render:cm_sdr_eotf"); // only primaries differ - 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 - ); + 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); } 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) { ; } @@ -2328,7 +2098,7 @@ bool CMonitorState::commit() { if (!updateSwapchain()) return false; - Event::bus()->m_events.monitor.preCommit.emit(m_owner->m_self.lock()); + EMIT_HOOK_EVENT("preMonitorCommit", m_owner->m_self.lock()); ensureBufferPresent(); diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index 7467467a..98d672e6 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -16,15 +16,13 @@ #include "math/Math.hpp" #include "../desktop/reserved/ReservedArea.hpp" #include -#include "cm/ColorManagement.hpp" +#include "../protocols/types/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. @@ -52,11 +50,10 @@ struct SMonitorRule { std::string mirrorOf = ""; bool enable10bit = false; NCMType::eCMType cmType = NCMType::CM_SRGB; - NTransferFunction::eTF sdrEotf = NTransferFunction::TF_DEFAULT; + int sdrEotf = 0; 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 @@ -127,7 +124,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; @@ -140,7 +137,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; - NTransferFunction::eTF m_sdrEotf = NTransferFunction::TF_DEFAULT; + int m_sdrEotf = 0; float m_sdrSaturation = 1.0f; float m_sdrBrightness = 1.0f; float m_sdrMinLuminance = 0.2f; @@ -180,7 +177,6 @@ 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 @@ -211,7 +207,6 @@ class CMonitor { } m_tearingState; struct { - CSignalT<> commit; CSignalT<> destroy; CSignalT<> connect; CSignalT<> disconnect; @@ -237,8 +232,9 @@ class CMonitor { DS_BLOCK_SURFACE = (1 << 8), DS_BLOCK_TRANSFORM = (1 << 9), DS_BLOCK_DMA = (1 << 10), - DS_BLOCK_FAILED = (1 << 11), - DS_BLOCK_CM = (1 << 12), + DS_BLOCK_TEARING = (1 << 11), + DS_BLOCK_FAILED = (1 << 12), + DS_BLOCK_CM = (1 << 13), DS_CHECKS_COUNT = 14, }; @@ -279,15 +275,14 @@ class CMonitor { TC_SUPPORT = (1 << 4), TC_CANDIDATE = (1 << 5), TC_WINDOW = (1 << 6), - TC_HW_CURSOR = (1 << 7), - TC_CHECKS_COUNT = 8, + TC_CHECKS_COUNT = 7, }; // methods void onConnect(bool noRule); void onDisconnect(bool destroy = false); - void applyCMType(NCMType::eCMType cmType, NTransferFunction::eTF cmSdrEotf); + void applyCMType(NCMType::eCMType cmType, int cmSdrEotf); bool applyMonitorRule(SMonitorRule* pMonitorRule, bool force = false); void addDamage(const pixman_region32_t* rg); void addDamage(const CRegion& rg); @@ -303,6 +298,7 @@ class CMonitor { void setSpecialWorkspace(const WORKSPACEID& id); void moveTo(const Vector2D& pos); Vector2D middle(); + void updateMatrix(); WORKSPACEID activeWorkspaceID(); WORKSPACEID activeSpecialWorkspaceID(); CBox logicalBox(); @@ -325,29 +321,17 @@ 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(); - // - const Mat3x3& getTransformMatrix(); - const Mat3x3& getScaleMatrix(); + /// Has an active workspace with a real fullscreen window + bool inFullscreenMode(); + std::optional getFSImageDescription(); - /// 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(); + bool needsCM(); /// Can do CM without shader bool canNoShaderCM(); bool doesNoShaderCM(); @@ -358,8 +342,8 @@ class CMonitor { PHLWINDOWREF m_previousFSWindow; bool m_needsHDRupdate = false; - NColorManagement::PImageDescription m_imageDescription = NColorManagement::CImageDescription::from(NColorManagement::SImageDescription{}); - bool m_noShaderCTM = false; // sets drm CTM, restore needed + NColorManagement::PImageDescription m_imageDescription; + bool m_noShaderCTM = false; // sets drm CTM, restore needed // For the list lookup @@ -367,19 +351,12 @@ 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 648e6dec..804eec1e 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() && m_monitor && !m_monitor->m_directScanoutIsActive; + return *PENABLENEW && g_pHyprOpenGL->explicitSyncSupported(); } void CMonitorFrameScheduler::onSyncFired() { diff --git a/src/helpers/SdDaemon.cpp b/src/helpers/SdDaemon.cpp index b6c207d8..d914eecf 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; - struct sockaddr_un unixAddr = {0}; - - size_t addrLen = strnlen(addr, sizeof(unixAddr.sun_path) - 1); + // address length must be at most this; see man 7 unix + size_t addrLen = strnlen(addr, 107); + 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 deleted file mode 100644 index 074f4b19..00000000 --- a/src/helpers/TransferFunction.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#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 deleted file mode 100644 index ae575158..00000000 --- a/src/helpers/TransferFunction.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#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 deleted file mode 100644 index bac9f25a..00000000 --- a/src/helpers/cm/ColorManagement.cpp +++ /dev/null @@ -1,193 +0,0 @@ -#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/helpers/cm/ICC.cpp b/src/helpers/cm/ICC.cpp deleted file mode 100644 index 00140c62..00000000 --- a/src/helpers/cm/ICC.cpp +++ /dev/null @@ -1,278 +0,0 @@ -#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 deleted file mode 100644 index e69de29b..00000000 diff --git a/src/helpers/math/Direction.hpp b/src/helpers/math/Direction.hpp deleted file mode 100644 index 9905db4f..00000000 --- a/src/helpers/math/Direction.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#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 f454b784..791f5ea1 100644 --- a/src/helpers/time/Time.cpp +++ b/src/helpers/time/Time.cpp @@ -5,6 +5,7 @@ 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; @@ -12,7 +13,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 = (TIMESPEC_NSEC_PER_SEC + a.second) - b.second; + d.second = b.second - a.second; d.first -= 1; } @@ -45,9 +46,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 auto nsecdur = tp - chr::steady_clock::time_point(chr::seconds(sec)); - return {sec, chr::duration_cast(nsecdur).count()}; + 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()); } uint64_t Time::millis(const system_tp& tp) { @@ -55,9 +56,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 auto nsecdur = tp - chr::system_clock::time_point(chr::seconds(sec)); - return {sec, chr::duration_cast(nsecdur).count()}; + 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()); } // TODO: this is a mess, but C++ doesn't define what steady_clock is. @@ -68,12 +69,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) { - timespec mono{}, real{}; + struct timespec mono, real; clock_gettime(CLOCK_MONOTONIC, &mono); clock_gettime(CLOCK_REALTIME, &real); - auto now = Time::steadyNow(); - auto nowSys = Time::systemNow(); - s_ns stdSteady, stdReal; + Time::steady_tp now = Time::steadyNow(); + Time::system_tp nowSys = Time::systemNow(); + s_ns stdSteady, stdReal; stdSteady = Time::secNsec(now); stdReal = Time::secNsec(nowSys); @@ -83,7 +84,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 = TIMESPEC_NSEC_PER_SEC + real.tv_nsec - mono.tv_nsec; + diff.second = mono.tv_nsec - real.tv_nsec; diff.first -= 1; } @@ -103,7 +104,7 @@ Time::steady_tp Time::fromTimespec(const timespec* ts) { } struct timespec Time::toTimespec(const steady_tp& tp) { - timespec mono{}, real{}; + struct timespec mono, real; clock_gettime(CLOCK_MONOTONIC, &mono); clock_gettime(CLOCK_REALTIME, &real); Time::steady_tp now = Time::steadyNow(); @@ -118,7 +119,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 = TIMESPEC_NSEC_PER_SEC + real.tv_nsec - mono.tv_nsec; + diff.second = mono.tv_nsec - real.tv_nsec; diff.first -= 1; } @@ -136,10 +137,3 @@ 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 ce99982b..eb3b5771 100644 --- a/src/helpers/time/Time.hpp +++ b/src/helpers/time/Time.hpp @@ -17,7 +17,6 @@ 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 60bf0a78..c8125e5b 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 = Event::bus()->m_events.monitor.focused.listen([&](PHLMONITOR mon) { + static auto P = g_pHookSystem->hookDynamic("focusedMon", [&](void* self, SCallbackInfo& info, std::any param) { if (!m_isCreated) return; @@ -23,13 +23,15 @@ CHyprError::CHyprError() { m_monitorChanged = true; }); - static auto P2 = Event::bus()->m_events.render.pre.listen([&](PHLMONITOR mon) { + static auto P2 = g_pHookSystem->hookDynamic("preRender", [&](void* self, SCallbackInfo& info, std::any param) { 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) { @@ -38,8 +40,8 @@ void CHyprError::queueCreate(std::string message, const CHyprColor& color) { } void CHyprError::createQueued() { - if (m_isCreated && m_texture) - m_texture.reset(); + if (m_isCreated) + m_texture->destroyTexture(); m_fadeOpacity->setConfig(g_pConfigManager->getAnimationPropertyConfig("fadeIn")); @@ -81,8 +83,7 @@ void CHyprError::createQueued() { const double X = PAD; const double Y = TOPBAR ? PAD : PMONITOR->m_pixelSize.y - HEIGHT - PAD; - 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)}; + m_damageBox = {0, 0, sc(PMONITOR->m_pixelSize.x), sc(HEIGHT) + sc(PAD) * 2}; cairo_new_sub_path(CAIRO); cairo_arc(CAIRO, X + WIDTH - RADIUS, Y + RADIUS, RADIUS, -90 * DEGREES, 0 * DEGREES); @@ -110,8 +111,6 @@ 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; @@ -133,8 +132,9 @@ void CHyprError::createQueued() { pango_layout_set_text(layoutText, moreString.c_str(), -1); pango_cairo_show_layout(CAIRO, layoutText); } + m_queued = ""; - m_lastHeight = HEIGHT; + m_lastHeight = yoffset + PAD + 1 - (TOPBAR ? 0 : Y - PAD); pango_font_description_free(pangoFD); g_object_unref(layoutText); @@ -143,13 +143,12 @@ void CHyprError::createQueued() { // copy the data to an OpenGL texture we have const auto DATA = cairo_image_surface_get_data(CAIROSURFACE); - 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); + 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); @@ -167,8 +166,7 @@ void CHyprError::createQueued() { m->m_reservedArea.resetType(Desktop::RESERVED_DYNAMIC_TYPE_ERROR_BAR); } - 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}); + 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}); for (const auto& m : g_pCompositor->m_monitors) { g_pHyprRenderer->arrangeLayersForMonitor(m->m_id); @@ -186,14 +184,12 @@ void CHyprError::draw() { if (!m_fadeOpacity->isBeingAnimated()) { if (m_fadeOpacity->value() == 0.f) { m_queuedDestroy = false; - if (m_texture) - m_texture.reset(); + m_texture->destroyTexture(); 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; @@ -204,13 +200,12 @@ 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}; - 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)); + m_damageBox.x = sc(PMONITOR->m_position.x); + m_damageBox.y = sc(PMONITOR->m_position.y); if (m_fadeOpacity->isBeingAnimated() || m_monitorChanged) g_pHyprRenderer->damageBox(m_damageBox); @@ -218,7 +213,7 @@ void CHyprError::draw() { m_monitorChanged = false; CTexPassElement::SRenderData data; - data.tex = texture(); + data.tex = m_texture; data.box = monbox; data.a = m_fadeOpacity->value(); @@ -239,9 +234,3 @@ 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 48b9e805..f4bc43d8 100644 --- a/src/hyprerror/HyprError.hpp +++ b/src/hyprerror/HyprError.hpp @@ -18,16 +18,13 @@ 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 c68400eb..0325a3ce 100644 --- a/src/i18n/Engine.cpp +++ b/src/i18n/Engine.cpp @@ -60,57 +60,6 @@ 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?"); @@ -160,8 +109,6 @@ 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)"); @@ -1144,65 +1091,6 @@ 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, "التطبيق لا يستجيب"); @@ -1253,62 +1141,6 @@ 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Что вы хотите сделать?"); @@ -1347,17 +1179,6 @@ 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"); @@ -1596,53 +1417,6 @@ 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 79ec86f8..c3892546 100644 --- a/src/i18n/Engine.hpp +++ b/src/i18n/Engine.hpp @@ -16,7 +16,6 @@ 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, @@ -55,4 +54,4 @@ namespace I18n { }; SP i18nEngine(); -}; +}; \ No newline at end of file diff --git a/src/layout/DwindleLayout.cpp b/src/layout/DwindleLayout.cpp new file mode 100644 index 00000000..70d052ea --- /dev/null +++ b/src/layout/DwindleLayout.cpp @@ -0,0 +1,1190 @@ +#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 new file mode 100644 index 00000000..e704b8f4 --- /dev/null +++ b/src/layout/DwindleLayout.hpp @@ -0,0 +1,110 @@ +#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 new file mode 100644 index 00000000..f434b580 --- /dev/null +++ b/src/layout/IHyprLayout.cpp @@ -0,0 +1,1062 @@ +#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 new file mode 100644 index 00000000..0b23bc3b --- /dev/null +++ b/src/layout/IHyprLayout.hpp @@ -0,0 +1,248 @@ +#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 deleted file mode 100644 index 4a93809c..00000000 --- a/src/layout/LayoutManager.cpp +++ /dev/null @@ -1,345 +0,0 @@ -#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 deleted file mode 100644 index e99911d5..00000000 --- a/src/layout/LayoutManager.hpp +++ /dev/null @@ -1,87 +0,0 @@ -#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 new file mode 100644 index 00000000..8c6376ab --- /dev/null +++ b/src/layout/MasterLayout.cpp @@ -0,0 +1,1526 @@ +#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 new file mode 100644 index 00000000..a5968916 --- /dev/null +++ b/src/layout/MasterLayout.hpp @@ -0,0 +1,112 @@ +#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 deleted file mode 100644 index cfb5b7e3..00000000 --- a/src/layout/algorithm/Algorithm.cpp +++ /dev/null @@ -1,271 +0,0 @@ -#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 deleted file mode 100644 index 7df6c5c1..00000000 --- a/src/layout/algorithm/Algorithm.hpp +++ /dev/null @@ -1,66 +0,0 @@ -#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 deleted file mode 100644 index 058887bf..00000000 --- a/src/layout/algorithm/FloatingAlgorithm.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#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 deleted file mode 100644 index 40e53034..00000000 --- a/src/layout/algorithm/FloatingAlgorithm.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#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 deleted file mode 100644 index dea5bb17..00000000 --- a/src/layout/algorithm/ModeAlgorithm.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#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 deleted file mode 100644 index 0fedc3da..00000000 --- a/src/layout/algorithm/ModeAlgorithm.hpp +++ /dev/null @@ -1,57 +0,0 @@ -#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 deleted file mode 100644 index 99d1bd99..00000000 --- a/src/layout/algorithm/TiledAlgorithm.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#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 deleted file mode 100644 index 0d069e4f..00000000 --- a/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.cpp +++ /dev/null @@ -1,252 +0,0 @@ -#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 deleted file mode 100644 index 1e87fac1..00000000 --- a/src/layout/algorithm/floating/default/DefaultFloatingAlgorithm.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#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 deleted file mode 100644 index 7ef36753..00000000 --- a/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp +++ /dev/null @@ -1,845 +0,0 @@ -#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 deleted file mode 100644 index 41cbf8bb..00000000 --- a/src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#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 deleted file mode 100644 index 7c436b31..00000000 --- a/src/layout/algorithm/tiled/master/MasterAlgorithm.cpp +++ /dev/null @@ -1,1307 +0,0 @@ -#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 deleted file mode 100644 index 5cfa6b36..00000000 --- a/src/layout/algorithm/tiled/master/MasterAlgorithm.hpp +++ /dev/null @@ -1,76 +0,0 @@ -#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 deleted file mode 100644 index fe92f27c..00000000 --- a/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.cpp +++ /dev/null @@ -1,278 +0,0 @@ -#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 deleted file mode 100644 index b23f85be..00000000 --- a/src/layout/algorithm/tiled/monocle/MonocleAlgorithm.hpp +++ /dev/null @@ -1,52 +0,0 @@ -#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 deleted file mode 100644 index 93a7dac1..00000000 --- a/src/layout/algorithm/tiled/scrolling/ScrollTapeController.cpp +++ /dev/null @@ -1,298 +0,0 @@ -#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 deleted file mode 100644 index da2efbba..00000000 --- a/src/layout/algorithm/tiled/scrolling/ScrollTapeController.hpp +++ /dev/null @@ -1,83 +0,0 @@ -#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 deleted file mode 100644 index ae7c6ecc..00000000 --- a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp +++ /dev/null @@ -1,1518 +0,0 @@ -#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 deleted file mode 100644 index d95b3197..00000000 --- a/src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp +++ /dev/null @@ -1,145 +0,0 @@ -#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 deleted file mode 100644 index db3925f6..00000000 --- a/src/layout/space/Space.cpp +++ /dev/null @@ -1,197 +0,0 @@ -#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 deleted file mode 100644 index e29a6d8f..00000000 --- a/src/layout/space/Space.hpp +++ /dev/null @@ -1,71 +0,0 @@ -#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 deleted file mode 100644 index be70f4ac..00000000 --- a/src/layout/supplementary/DragController.cpp +++ /dev/null @@ -1,398 +0,0 @@ -#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 deleted file mode 100644 index 3a0d8071..00000000 --- a/src/layout/supplementary/DragController.hpp +++ /dev/null @@ -1,54 +0,0 @@ -#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 deleted file mode 100644 index b476c3a0..00000000 --- a/src/layout/supplementary/WorkspaceAlgoMatcher.cpp +++ /dev/null @@ -1,139 +0,0 @@ -#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 deleted file mode 100644 index d39e2998..00000000 --- a/src/layout/supplementary/WorkspaceAlgoMatcher.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#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 deleted file mode 100644 index e433c237..00000000 --- a/src/layout/target/Target.cpp +++ /dev/null @@ -1,146 +0,0 @@ -#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 deleted file mode 100644 index dcaefdb4..00000000 --- a/src/layout/target/Target.hpp +++ /dev/null @@ -1,79 +0,0 @@ -#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 deleted file mode 100644 index ae883751..00000000 --- a/src/layout/target/WindowGroupTarget.cpp +++ /dev/null @@ -1,92 +0,0 @@ -#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 deleted file mode 100644 index 3d4b85a0..00000000 --- a/src/layout/target/WindowGroupTarget.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#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 deleted file mode 100644 index db03a385..00000000 --- a/src/layout/target/WindowTarget.cpp +++ /dev/null @@ -1,382 +0,0 @@ -#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 deleted file mode 100644 index 2939fd74..00000000 --- a/src/layout/target/WindowTarget.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#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 fc109296..1b55bacd 100644 --- a/src/macros.hpp +++ b/src/macros.hpp @@ -96,13 +96,10 @@ #define GLCALL(__CALL__) \ { \ __CALL__; \ - 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); \ - } \ + 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 99e64675..a499bd48 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -31,8 +31,6 @@ 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 @@ -221,7 +219,7 @@ int main(int argc, char** argv) { if (safeMode) g_pCompositor->m_safeMode = true; - if (!watchdogOk && !verifyConfig) + if (!watchdogOk) 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 9f613df8..a9cff74f 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,7 +26,9 @@ CANRManager::CANRManager() { m_active = true; - static auto P = Event::bus()->m_events.window.open.listen([this](PHLWINDOW window) { + static auto P = g_pHookSystem->hookDynamic("openWindow", [this](void* self, SCallbackInfo& info, std::any data) { + auto window = std::any_cast(data); + for (const auto& d : m_data) { // Window is ANR dialog if (d->isRunning() && d->dialogBox->getPID() == window->getPID()) @@ -39,7 +41,9 @@ CANRManager::CANRManager() { m_data.emplace_back(makeShared(window)); }); - static auto P1 = Event::bus()->m_events.window.close.listen([this](PHLWINDOW window) { + static auto P1 = g_pHookSystem->hookDynamic("closeWindow", [this](void* self, SCallbackInfo& info, std::any data) { + auto window = std::any_cast(data); + for (const auto& d : m_data) { if (!d->fitsWindow(window)) continue; @@ -49,9 +53,8 @@ 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); @@ -185,25 +188,21 @@ 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, {}), 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 = + 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->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 7564ca75..8392db0a 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 = Event::bus()->m_events.monitor.layoutChanged.listen([this] { this->updateTheme(); }); + static auto P = g_pHookSystem->hookDynamic("monitorLayoutChanged", [this](void* self, SCallbackInfo& info, std::any param) { this->updateTheme(); }); } CCursorManager::~CCursorManager() { diff --git a/src/managers/HookSystemManager.cpp b/src/managers/HookSystemManager.cpp new file mode 100644 index 00000000..0aa2d93e --- /dev/null +++ b/src/managers/HookSystemManager.cpp @@ -0,0 +1,83 @@ +#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 new file mode 100644 index 00000000..647e9670 --- /dev/null +++ b/src/managers/HookSystemManager.hpp @@ -0,0 +1,60 @@ +#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 fd7c4e72..a709b0ca 100644 --- a/src/managers/KeybindManager.cpp +++ b/src/managers/KeybindManager.cpp @@ -16,22 +16,16 @@ #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 @@ -109,6 +103,9 @@ 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; @@ -200,7 +197,8 @@ CKeybindManager::CKeybindManager() { g_pEventLoopManager->addTimer(m_repeatKeyTimer); } - static auto P = Event::bus()->m_events.config.reloaded.listen([this] { + static auto P = g_pHookSystem->hookDynamic("configReloaded", [this](void* hk, SCallbackInfo& info, std::any param) { + // clear cuz realloc'd m_activeKeybinds.clear(); m_lastLongPressKeybind.reset(); m_pressedSpecialBinds.clear(); @@ -324,10 +322,10 @@ void CKeybindManager::updateXKBTranslationState() { } bool CKeybindManager::ensureMouseBindState() { - if (!g_layoutManager->dragController()->target()) + if (!g_pInputManager->m_currentlyDraggedWindow) return false; - if (g_layoutManager->dragController()->target()) { + if (!g_pInputManager->m_currentlyDraggedWindow.expired()) { changeMouseBindMode(MBIND_INVALID); return true; } @@ -370,7 +368,7 @@ bool CKeybindManager::tryMoveFocusToMonitor(PHLMONITOR monitor) { const auto PNEWWINDOW = PNEWWORKSPACE->getLastFocusedWindow(); if (PNEWWINDOW) { updateRelativeCursorCoords(); - Desktop::focusState()->fullWindowFocus(PNEWWINDOW, Desktop::FOCUS_REASON_KEYBIND); + Desktop::focusState()->fullWindowFocus(PNEWWINDOW); PNEWWINDOW->warpCursor(); if (*PNOWARPS == 0 || *PFOLLOWMOUSE < 2) { @@ -379,7 +377,7 @@ bool CKeybindManager::tryMoveFocusToMonitor(PHLMONITOR monitor) { g_pInputManager->m_forcedFocus.reset(); } } else { - Desktop::focusState()->rawWindowFocus(nullptr, Desktop::FOCUS_REASON_KEYBIND); + Desktop::focusState()->rawWindowFocus(nullptr); g_pCompositor->warpCursorTo(monitor->middle()); } Desktop::focusState()->rawMonitorFocus(monitor); @@ -400,10 +398,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, Desktop::FOCUS_REASON_KEYBIND, nullptr, forceFSCycle); + Desktop::focusState()->fullWindowFocus(PWINDOWTOCHANGETO, nullptr, forceFSCycle); else { updateRelativeCursorCoords(); - Desktop::focusState()->fullWindowFocus(PWINDOWTOCHANGETO, Desktop::FOCUS_REASON_KEYBIND, nullptr, forceFSCycle); + Desktop::focusState()->fullWindowFocus(PWINDOWTOCHANGETO, nullptr, forceFSCycle); PWINDOWTOCHANGETO->warpCursor(); // Move mouse focus to the new window if required by current follow_mouse and warp modes @@ -753,9 +751,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_layoutManager->dragController()->dragThresholdReached() || THRESHOLDREACHED)) + if (k->click && (g_pInputManager->m_dragThresholdReached || THRESHOLDREACHED)) continue; - else if (k->drag && !g_layoutManager->dragController()->dragThresholdReached() && !THRESHOLDREACHED) + else if (k->drag && !g_pInputManager->m_dragThresholdReached && !THRESHOLDREACHED) continue; } @@ -812,7 +810,7 @@ SDispatchResult CKeybindManager::handleKeybinds(const uint32_t modmask, const SP found = true; } - g_layoutManager->dragController()->resetDragThresholdReached(); + g_pInputManager->m_dragThresholdReached = false; // if keybind wasn't found (or dispatcher said to) then pass event res.passEvent |= !found; @@ -912,13 +910,11 @@ bool CKeybindManager::handleInternalKeybinds(xkb_keysym_t keysym) { // Dispatchers SDispatchResult CKeybindManager::spawn(std::string 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)}; + const uint64_t PROC = spawnWithRules(args, nullptr); + return {.success = PROC > 0, .error = std::format("Failed to start process {}", args)}; } -std::optional CKeybindManager::spawnWithRules(std::string args, PHLWORKSPACE pInitialWorkspace) { +uint64_t CKeybindManager::spawnWithRules(std::string args, PHLWORKSPACE pInitialWorkspace) { args = trim(args); @@ -926,29 +922,22 @@ std::optional CKeybindManager::spawnWithRules(std::string args, PHLWOR if (args[0] == '[') { // we have exec rules - 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); + RULES = args.substr(1, args.substr(1).find_first_of(']')); + args = args.substr(args.find_first_of(']') + 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)); - const auto TOKEN = g_pTokenManager->registerNewToken(nullptr, std::chrono::seconds(1)); + execToken = rule->execToken(); - 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; @@ -1123,16 +1112,32 @@ static SDispatchResult toggleActiveFloatingCore(std::string args, std::optional< return {}; // remove drag status - if (g_layoutManager->dragController()->target()) + if (!g_pInputManager->m_currentlyDraggedWindow.expired()) CKeybindManager::changeMouseBindMode(MBIND_INVALID); - g_layoutManager->changeFloatingMode(PWINDOW->layoutTarget()); + 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); + } if (PWINDOW->m_workspace) { PWINDOW->m_workspace->updateWindows(); PWINDOW->m_workspace->updateWindowData(); } + g_pLayoutManager->getCurrentLayout()->recalculateMonitor(PWINDOW->monitorID()); g_pCompositor->updateAllWindowsAnimatedDecorationValues(); return {}; @@ -1158,7 +1163,8 @@ SDispatchResult CKeybindManager::centerWindow(std::string args) { const auto PMONITOR = PWINDOW->m_monitor.lock(); - PWINDOW->layoutTarget()->setPositionGlobal(CBox{PMONITOR->logicalBoxMinusReserved().middle() - PWINDOW->m_realSize->goal() / 2.F, PWINDOW->layoutTarget()->position().size()}); + *PWINDOW->m_realPosition = PMONITOR->logicalBoxMinusReserved().middle() - PWINDOW->m_realSize->goal() / 2.f; + PWINDOW->m_position = PWINDOW->m_realPosition->goal(); return {}; } @@ -1174,7 +1180,10 @@ SDispatchResult CKeybindManager::toggleActivePseudo(std::string args) { if (!PWINDOW) return {.success = false, .error = "Window not found"}; - PWINDOW->layoutTarget()->setPseudo(!PWINDOW->layoutTarget()->isPseudo()); + PWINDOW->m_isPseudotiled = !PWINDOW->m_isPseudotiled; + + if (!PWINDOW->isFullscreen()) + g_pLayoutManager->getCurrentLayout()->recalculateWindow(PWINDOW); return {}; } @@ -1267,7 +1276,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::FOCUS_REASON_KEYBIND); + Desktop::focusState()->fullWindowFocus(PLAST); if (*PWORKSPACECENTERON == 1) middle = PLAST->middle(); } @@ -1412,7 +1421,7 @@ SDispatchResult CKeybindManager::moveActiveToWorkspace(std::string args) { pMonitor->changeWorkspace(pWorkspace); - Desktop::focusState()->fullWindowFocus(PWINDOW, Desktop::FOCUS_REASON_KEYBIND); + Desktop::focusState()->fullWindowFocus(PWINDOW); PWINDOW->warpCursor(); return {}; @@ -1456,7 +1465,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::FOCUS_REASON_KEYBIND); + Desktop::focusState()->fullWindowFocus(PATCOORDS); else g_pInputManager->refocus(); } @@ -1465,37 +1474,38 @@ SDispatchResult CKeybindManager::moveActiveToWorkspaceSilent(std::string args) { } SDispatchResult CKeybindManager::moveFocusTo(std::string args) { - 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]); + 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]; - 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])}; + 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)}; } const auto PLASTWINDOW = Desktop::focusState()->window(); if (!PLASTWINDOW || !PLASTWINDOW->aliveAndVisible()) { if (*PMONITORFALLBACK) - tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(dir)); + tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(arg)); + return {}; } const auto PWINDOWTOCHANGETO = *PFULLCYCLE && PLASTWINDOW->isFullscreen() ? - g_pCompositor->getWindowCycle(PLASTWINDOW, true, {}, false, dir != Math::DIRECTION_DOWN && dir != Math::DIRECTION_RIGHT) : - g_pCompositor->getWindowInDirection(PLASTWINDOW, dir); + g_pCompositor->getWindowCycle(PLASTWINDOW, true, {}, false, arg != 'd' && arg != 'b' && arg != 'r') : + g_pCompositor->getWindowInDirection(PLASTWINDOW, arg); // Prioritize focus change within groups if the window is a part of it. - if (*PGROUPCYCLE && PLASTWINDOW->m_group) { + if (*PGROUPCYCLE && PLASTWINDOW->m_groupData.pNextWindow) { auto isTheOnlyGroupOnWs = !PWINDOWTOCHANGETO && g_pCompositor->m_monitors.size() == 1; - if (dir == Math::DIRECTION_LEFT && (PLASTWINDOW != PLASTWINDOW->m_group->head() || isTheOnlyGroupOnWs)) { - PLASTWINDOW->m_group->moveCurrent(false); + if (arg == 'l' && (PLASTWINDOW != PLASTWINDOW->getGroupHead() || isTheOnlyGroupOnWs)) { + PLASTWINDOW->setGroupCurrent(PLASTWINDOW->getGroupPrevious()); return {}; } - else if (dir == Math::DIRECTION_RIGHT && (PLASTWINDOW != PLASTWINDOW->m_group->tail() || isTheOnlyGroupOnWs)) { - PLASTWINDOW->m_group->moveCurrent(true); + else if (arg == 'r' && (PLASTWINDOW != PLASTWINDOW->getGroupTail() || isTheOnlyGroupOnWs)) { + PLASTWINDOW->setGroupCurrent(PLASTWINDOW->m_groupData.pNextWindow.lock()); return {}; } } @@ -1506,51 +1516,52 @@ SDispatchResult CKeybindManager::moveFocusTo(std::string args) { return {}; } - Log::logger->log(Log::DEBUG, "No window found in direction {}, looking for a monitor", Math::toString(dir)); + Log::logger->log(Log::DEBUG, "No window found in direction {}, looking for a monitor", arg); - if (*PMONITORFALLBACK && tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(dir))) + if (*PMONITORFALLBACK && tryMoveFocusToMonitor(g_pCompositor->getMonitorInDirection(arg))) return {}; static auto PNOFALLBACK = CConfigValue("general:no_focus_fallback"); if (*PNOFALLBACK) - return {.success = false, .error = std::format("Nothing to focus to in direction {}", Math::toString(dir))}; + return {.success = false, .error = std::format("Nothing to focus to in direction {}", arg)}; - Log::logger->log(Log::DEBUG, "No monitor found in direction {}, getting the inverse edge", Math::toString(dir)); + Log::logger->log(Log::DEBUG, "No monitor found in direction {}, getting the inverse edge", arg); const auto PMONITOR = PLASTWINDOW->m_monitor.lock(); if (!PMONITOR) return {.success = false, .error = "last window has no monitor?"}; - if (dir == Math::DIRECTION_LEFT || dir == Math::DIRECTION_RIGHT) { + if (arg == 'l' || arg == 'r') { 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 (dir) { - case Math::DIRECTION_LEFT: + switch (arg) { + case 'l': box.x += box.w; box.w = 1; break; - case Math::DIRECTION_RIGHT: + case 'r': box.x -= 1; box.w = 1; break; - case Math::DIRECTION_UP: + case 'u': + case 't': box.y += box.h; box.h = 1; break; - case Math::DIRECTION_DOWN: + case 'd': + case 'b': 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, - dir, PLASTWINDOW, PLASTWINDOW->m_isFloating); + arg, PLASTWINDOW, PLASTWINDOW->m_isFloating); if (PWINDOWCANDIDATE) switchToWindow(PWINDOWCANDIDATE); @@ -1587,6 +1598,7 @@ 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; @@ -1596,10 +1608,9 @@ SDispatchResult CKeybindManager::swapActive(std::string args) { if (PLASTWINDOW->isFullscreen()) return {.success = false, .error = "Can't swap fullscreen window"}; - if (isDirection(args)) { - Math::eDirection dir = Math::fromChar(args[0]); - PWINDOWTOCHANGETO = g_pCompositor->getWindowInDirection(PLASTWINDOW, dir); - } else + if (isDirection(args)) + PWINDOWTOCHANGETO = g_pCompositor->getWindowInDirection(PLASTWINDOW, arg); + else PWINDOWTOCHANGETO = g_pCompositor->getWindowByRegex(args); if (!PWINDOWTOCHANGETO || PWINDOWTOCHANGETO == PLASTWINDOW) { @@ -1610,12 +1621,13 @@ SDispatchResult CKeybindManager::swapActive(std::string args) { Log::logger->log(Log::DEBUG, "Swapping active window with {}", args); updateRelativeCursorCoords(); - g_layoutManager->switchTargets(PLASTWINDOW->layoutTarget(), PWINDOWTOCHANGETO->layoutTarget(), true); + g_pLayoutManager->getCurrentLayout()->switchWindows(PLASTWINDOW, PWINDOWTOCHANGETO); 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); @@ -1633,10 +1645,9 @@ SDispatchResult CKeybindManager::moveActiveTo(std::string args) { return {}; } - 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])}; + 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)}; } const auto PLASTWINDOW = Desktop::focusState()->window(); @@ -1647,11 +1658,59 @@ SDispatchResult CKeybindManager::moveActiveTo(std::string args) { if (PLASTWINDOW->isFullscreen()) return {.success = false, .error = "Can't move fullscreen window"}; - updateRelativeCursorCoords(); + 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()); - g_layoutManager->moveInDirection(PLASTWINDOW->layoutTarget(), args, silent); - if (!silent) - PLASTWINDOW->warpCursor(); + 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()); return {}; } @@ -1665,10 +1724,10 @@ SDispatchResult CKeybindManager::toggleGroup(std::string args) { if (PWINDOW->isFullscreen()) g_pCompositor->setWindowFullscreenInternal(PWINDOW, FSMODE_NONE); - if (!PWINDOW->m_group) - PWINDOW->m_group = Desktop::View::CGroup::create({PWINDOW}); + if (PWINDOW->m_groupData.pNextWindow.expired()) + PWINDOW->createGroup(); else - PWINDOW->m_group->destroy(); + PWINDOW->destroyGroup(); return {}; } @@ -1679,29 +1738,87 @@ SDispatchResult CKeybindManager::changeGroupActive(std::string args) { if (!PWINDOW) return {.success = false, .error = "Window not found"}; - if (!PWINDOW->m_group) - return {.success = false, .error = "No group"}; + if (PWINDOW->m_groupData.pNextWindow.expired()) + return {.success = false, .error = "No next window in group"}; - if (PWINDOW->m_group->size() == 1) + if (PWINDOW->m_groupData.pNextWindow.lock() == PWINDOW) return {.success = false, .error = "Only one window in group"}; if (isNumber(args, false)) { // index starts from '1'; '0' means last window - 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"}; } - + 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)); return {}; } if (args != "b" && args != "prev") - PWINDOW->m_group->moveCurrent(true); + PWINDOW->setGroupCurrent(PWINDOW->m_groupData.pNextWindow.lock()); else - PWINDOW->m_group->moveCurrent(false); + 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); return {}; } @@ -1786,7 +1903,58 @@ SDispatchResult CKeybindManager::moveCursor(std::string args) { } SDispatchResult CKeybindManager::workspaceOpt(std::string args) { - return {.success = false, .error = "workspaceopt is deprecated"}; + + // 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 {}; } SDispatchResult CKeybindManager::renameWorkspace(std::string args) { @@ -2015,7 +2183,7 @@ SDispatchResult CKeybindManager::resizeActive(std::string args) { if (SIZ.x < 1 || SIZ.y < 1) return {.success = false, .error = "Invalid size provided"}; - g_layoutManager->resizeTarget(SIZ - PLASTWINDOW->m_realSize->goal(), PLASTWINDOW->layoutTarget()); + g_pLayoutManager->getCurrentLayout()->resizeActiveWindow(SIZ - PLASTWINDOW->m_realSize->goal()); if (PLASTWINDOW->m_realSize->goal().x > 1 && PLASTWINDOW->m_realSize->goal().y > 1) PLASTWINDOW->setHidden(false); @@ -2034,7 +2202,7 @@ SDispatchResult CKeybindManager::moveActive(std::string args) { const auto POS = g_pCompositor->parseWindowVectorArgsRelative(args, PLASTWINDOW->m_realPosition->goal()); - g_layoutManager->moveTarget(POS - PLASTWINDOW->m_realPosition->goal(), PLASTWINDOW->layoutTarget()); + g_pLayoutManager->getCurrentLayout()->moveActiveWindow(POS - PLASTWINDOW->m_realPosition->goal()); return {}; } @@ -2056,7 +2224,7 @@ SDispatchResult CKeybindManager::moveWindow(std::string args) { const auto POS = g_pCompositor->parseWindowVectorArgsRelative(MOVECMD, PWINDOW->m_realPosition->goal()); - g_layoutManager->moveTarget(POS - PWINDOW->m_realPosition->goal(), PWINDOW->layoutTarget()); + g_pLayoutManager->getCurrentLayout()->moveActiveWindow(POS - PWINDOW->m_realPosition->goal(), PWINDOW); return {}; } @@ -2081,7 +2249,7 @@ SDispatchResult CKeybindManager::resizeWindow(std::string args) { if (SIZ.x < 1 || SIZ.y < 1) return {.success = false, .error = "Invalid size provided"}; - g_layoutManager->resizeTarget(SIZ - PWINDOW->m_realSize->goal(), PWINDOW->layoutTarget(), Layout::CORNER_NONE); + g_pLayoutManager->getCurrentLayout()->resizeActiveWindow(SIZ - PWINDOW->m_realSize->goal(), CORNER_NONE, PWINDOW); if (PWINDOW->m_realSize->goal().x > 1 && PWINDOW->m_realSize->goal().y > 1) PWINDOW->setHidden(false); @@ -2103,32 +2271,14 @@ 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")) { - // 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")) + if (args.contains("tile") || args.contains("tiled")) + floatStatus = false; + else 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) : @@ -2161,7 +2311,7 @@ SDispatchResult CKeybindManager::focusWindow(std::string regexp) { changeworkspace(PWORKSPACE->getConfigName()); } - Desktop::focusState()->fullWindowFocus(PWINDOW, Desktop::FOCUS_REASON_KEYBIND, nullptr, false); + Desktop::focusState()->fullWindowFocus(PWINDOW, nullptr, false); PWINDOW->warpCursor(); @@ -2197,12 +2347,12 @@ SDispatchResult CKeybindManager::toggleSwallow(std::string args) { // Unswallow pWindow->m_swallowed->m_currentlySwallowed = false; pWindow->m_swallowed->setHidden(false); - g_layoutManager->newTarget(pWindow->m_swallowed->layoutTarget(), pWindow->m_workspace->m_space); + g_pLayoutManager->getCurrentLayout()->onWindowCreated(pWindow->m_swallowed.lock()); } else { // Reswallow pWindow->m_swallowed->m_currentlySwallowed = true; pWindow->m_swallowed->setHidden(true); - g_layoutManager->removeTarget(pWindow->m_swallowed->layoutTarget()); + g_pLayoutManager->getCurrentLayout()->onWindowRemoved(pWindow->m_swallowed.lock()); } return {}; @@ -2213,7 +2363,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", ""}); - Event::bus()->m_events.keybinds.submap.emit(m_currentSelectedSubmap.name); + EMIT_HOOK_EVENT("submap", m_currentSelectedSubmap.name); return {}; } @@ -2222,7 +2372,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}); - Event::bus()->m_events.keybinds.submap.emit(m_currentSelectedSubmap.name); + EMIT_HOOK_EVENT("submap", m_currentSelectedSubmap.name); return {}; } } @@ -2463,9 +2613,9 @@ SDispatchResult CKeybindManager::sendshortcut(std::string args) { } SDispatchResult CKeybindManager::layoutmsg(std::string msg) { - auto ret = g_layoutManager->layoutMsg(msg); - if (!ret) - return {.success = false, .error = ret.error()}; + SLayoutMessageHeader hd = {Desktop::focusState()->window()}; + g_pLayoutManager->getCurrentLayout()->layoutMessage(hd, msg); + return {}; } @@ -2519,11 +2669,11 @@ SDispatchResult CKeybindManager::swapnext(std::string arg) { if (toSwap == PLASTWINDOW) toSwap = g_pCompositor->getWindowCycle(PLASTWINDOW, true, std::nullopt, false, NEED_PREV); - g_layoutManager->switchTargets(PLASTWINDOW->layoutTarget(), toSwap->layoutTarget(), false); + g_pLayoutManager->getCurrentLayout()->switchWindows(PLASTWINDOW, toSwap); PLASTWINDOW->m_lastCycledWindow = toSwap; - Desktop::focusState()->fullWindowFocus(PLASTWINDOW, Desktop::FOCUS_REASON_KEYBIND); + Desktop::focusState()->fullWindowFocus(PLASTWINDOW); return {}; } @@ -2572,7 +2722,7 @@ SDispatchResult CKeybindManager::pinActive(std::string args) { return {.success = false, .error = "pin: window not found"}; } - PWINDOW->moveToWorkspace(PMONITOR->m_activeWorkspace); + PWINDOW->m_workspace = PMONITOR->m_activeWorkspace; PWINDOW->m_ruleApplicator->propertiesChanged(Desktop::Rule::RULE_PROP_PINNED); @@ -2582,9 +2732,7 @@ 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))}); - Event::bus()->m_events.window.pin.emit(PWINDOW); - - g_pHyprRenderer->damageWindow(PWINDOW, true); + EMIT_HOOK_EVENT("pin", PWINDOW); return {}; } @@ -2612,7 +2760,7 @@ SDispatchResult CKeybindManager::mouse(std::string args) { SDispatchResult CKeybindManager::changeMouseBindMode(const eMouseBindMode MODE) { if (MODE != MBIND_INVALID) { - if (g_layoutManager->dragController()->target()) + if (!g_pInputManager->m_currentlyDraggedWindow.expired() || g_pInputManager->m_dragMode != MBIND_INVALID) return {}; const auto MOUSECOORDS = g_pInputManager->getMouseCoordsInternal(); @@ -2621,17 +2769,21 @@ SDispatchResult CKeybindManager::changeMouseBindMode(const eMouseBindMode MODE) if (!PWINDOW) return SDispatchResult{.passEvent = true}; - if (!PWINDOW->isFullscreen() && MODE == MBIND_MOVE) { - if (PWINDOW->checkInputOnDecos(INPUT_TYPE_DRAG_START, MOUSECOORDS)) - return SDispatchResult{.passEvent = false}; - } + if (!PWINDOW->isFullscreen() && MODE == MBIND_MOVE) + PWINDOW->checkInputOnDecos(INPUT_TYPE_DRAG_START, MOUSECOORDS); - g_layoutManager->beginDragTarget(PWINDOW->layoutTarget(), MODE); + if (g_pInputManager->m_currentlyDraggedWindow.expired()) + g_pInputManager->m_currentlyDraggedWindow = PWINDOW; + + g_pInputManager->m_dragMode = MODE; + + g_pLayoutManager->getCurrentLayout()->onBeginDragWindow(); } else { - if (!g_layoutManager->dragController()->target()) + if (g_pInputManager->m_currentlyDraggedWindow.expired() || g_pInputManager->m_dragMode == MBIND_INVALID) return {}; - g_layoutManager->endDragTarget(); + g_pLayoutManager->getCurrentLayout()->onEndDragWindow(); + g_pInputManager->m_dragMode = MODE; } return {}; @@ -2693,15 +2845,17 @@ SDispatchResult CKeybindManager::lockActiveGroup(std::string args) { if (!PWINDOW) return {.success = false, .error = "No window found"}; - if (!PWINDOW->m_group) + if (!PWINDOW->m_groupData.pNextWindow.lock()) return {.success = false, .error = "Not a group"}; + const auto PHEAD = PWINDOW->getGroupHead(); + if (args == "lock") - PWINDOW->m_group->setLocked(true); + PHEAD->m_groupData.locked = true; else if (args == "toggle") - PWINDOW->m_group->setLocked(!PWINDOW->m_group->locked()); + PHEAD->m_groupData.locked = !PHEAD->m_groupData.locked; else - PWINDOW->m_group->setLocked(false); + PHEAD->m_groupData.locked = false; PWINDOW->updateDecorationValues(); @@ -2709,21 +2863,25 @@ SDispatchResult CKeybindManager::lockActiveGroup(std::string args) { } void CKeybindManager::moveWindowIntoGroup(PHLWINDOW pWindow, PHLWINDOW pWindowInDirection) { - if (!pWindowInDirection->m_group || pWindowInDirection->m_group->denied()) + if (pWindow->m_groupData.deny) 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; } - pWindowInDirection->m_group->add(pWindow); + static auto USECURRPOS = CConfigValue("group:insert_after_current"); + (*USECURRPOS ? pWindowInDirection : pWindowInDirection->getGroupTail())->insertWindowToGroup(pWindow); - pWindowInDirection->m_group->setCurrent(pWindow); + pWindowInDirection->setGroupCurrent(pWindow); pWindow->updateWindowDecos(); - Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_KEYBIND); + g_pLayoutManager->getCurrentLayout()->recalculateWindow(pWindow); + Desktop::focusState()->fullWindowFocus(pWindow); pWindow->warpCursor(); g_pEventManager->postEvent(SHyprIPCEvent{"moveintogroup", std::format("{:x}", rc(pWindow.get()))}); @@ -2731,53 +2889,70 @@ 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; - if (!pWindow->m_group) - return; + 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; + } - WP group = pWindow->m_group; + updateRelativeCursorCoords(); - const auto direction = !dir.empty() ? Math::fromChar(dir[0]) : Math::DIRECTION_DEFAULT; + if (pWindow->m_groupData.pNextWindow.lock() == pWindow) { + pWindow->destroyGroup(); + } else { + g_pLayoutManager->getCurrentLayout()->onWindowRemoved(pWindow); - pWindow->m_group->remove(pWindow, direction); + const auto GROUPSLOCKEDPREV = g_pKeybindManager->m_groupsLocked; + g_pKeybindManager->m_groupsLocked = true; - if (*BFOCUSREMOVEDWINDOW || !group) { - Desktop::focusState()->fullWindowFocus(pWindow, Desktop::FOCUS_REASON_KEYBIND); + g_pLayoutManager->getCurrentLayout()->onWindowCreated(pWindow, direction); + + g_pKeybindManager->m_groupsLocked = GROUPSLOCKEDPREV; + } + + if (*BFOCUSREMOVEDWINDOW) { + Desktop::focusState()->fullWindowFocus(pWindow); pWindow->warpCursor(); } else { - Desktop::focusState()->fullWindowFocus(group->current(), Desktop::FOCUS_REASON_KEYBIND); - group->current()->warpCursor(); + Desktop::focusState()->fullWindowFocus(PWINDOWPREV); + PWINDOWPREV->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 {}; - 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])}; + 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)}; } const auto PWINDOW = Desktop::focusState()->window(); - if (!PWINDOW) + if (!PWINDOW || PWINDOW->m_groupData.deny) return {}; - auto PWINDOWINDIR = g_pCompositor->getWindowInDirection(PWINDOW, dir); + auto PWINDOWINDIR = g_pCompositor->getWindowInDirection(PWINDOW, arg); - if (!PWINDOWINDIR || !PWINDOWINDIR->m_group) + if (!PWINDOWINDIR || !PWINDOWINDIR->m_groupData.pNextWindow.lock()) return {}; - const auto GROUP = PWINDOWINDIR->m_group; - // Do not move window into locked group if binds:ignore_group_lock is false - if (!*PIGNOREGROUPLOCK && (GROUP->locked() || (PWINDOW->m_group && PWINDOW->m_group->locked()))) + if (!*PIGNOREGROUPLOCK && (PWINDOWINDIR->getGroupHead()->m_groupData.locked || (PWINDOW->m_groupData.pNextWindow.lock() && PWINDOW->getGroupHead()->m_groupData.locked))) return {}; moveWindowIntoGroup(PWINDOW, PWINDOWINDIR); @@ -2801,7 +2976,7 @@ SDispatchResult CKeybindManager::moveOutOfGroup(std::string args) { if (!PWINDOW) return {.success = false, .error = "No window found"}; - if (!PWINDOW->m_group) + if (!PWINDOW->m_groupData.pNextWindow.lock()) return {.success = false, .error = "Window not in a group"}; moveWindowOutOfGroup(PWINDOW); @@ -2810,12 +2985,13 @@ SDispatchResult CKeybindManager::moveOutOfGroup(std::string args) { } SDispatchResult CKeybindManager::moveWindowOrGroup(std::string args) { - static auto PIGNOREGROUPLOCK = CConfigValue("binds:ignore_group_lock"); + char arg = args[0]; - 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])}; + 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)}; } const auto PWINDOW = Desktop::focusState()->window(); @@ -2826,36 +3002,35 @@ SDispatchResult CKeybindManager::moveWindowOrGroup(std::string args) { return {}; if (!*PIGNOREGROUPLOCK && g_pKeybindManager->m_groupsLocked) { - g_layoutManager->moveInDirection(PWINDOW->layoutTarget(), args); + g_pLayoutManager->getCurrentLayout()->moveWindowTo(PWINDOW, args); return {}; } - const auto PWINDOWINDIR = g_pCompositor->getWindowInDirection(PWINDOW, dir); + const auto PWINDOWINDIR = g_pCompositor->getWindowInDirection(PWINDOW, arg); - 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(); + 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; updateRelativeCursorCoords(); // note: PWINDOWINDIR is not null implies !PWINDOW->m_isFloating - if (PWINDOWINDIR && PWINDOWINDIR->m_group) { // target is group - if (!*PIGNOREGROUPLOCK && (PWINDOWINDIR->m_group->locked() || ISWINDOWGROUPLOCKED || ISWINDOWGROUPDENIED)) { - g_layoutManager->moveInDirection(PWINDOW->layoutTarget(), args); + 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); 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_layoutManager->moveInDirection(PWINDOW->layoutTarget(), args); + g_pLayoutManager->getCurrentLayout()->moveWindowTo(PWINDOW, 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_layoutManager->moveInDirection(PWINDOW->layoutTarget(), args); + g_pLayoutManager->getCurrentLayout()->moveWindowTo(PWINDOW, args); PWINDOW->warpCursor(); } @@ -2879,13 +3054,13 @@ SDispatchResult CKeybindManager::setIgnoreGroupLock(std::string args) { SDispatchResult CKeybindManager::denyWindowFromGroup(std::string args) { const auto PWINDOW = Desktop::focusState()->window(); - if (!PWINDOW || (PWINDOW && PWINDOW->m_group)) + if (!PWINDOW || (PWINDOW && PWINDOW->m_groupData.pNextWindow.lock())) return {}; if (args == "toggle") - PWINDOW->m_group->setDenied(!PWINDOW->m_group->denied()); + PWINDOW->m_groupData.deny = !PWINDOW->m_groupData.deny; else - PWINDOW->m_group->setDenied(args == "on"); + PWINDOW->m_groupData.deny = args == "on"; PWINDOW->updateDecorationValues(); @@ -2915,15 +3090,16 @@ SDispatchResult CKeybindManager::moveGroupWindow(std::string args) { if (!PLASTWINDOW) return {.success = false, .error = "No window found"}; - if (!PLASTWINDOW->m_group) + if (!PLASTWINDOW->m_groupData.pNextWindow.lock()) return {.success = false, .error = "Window not in a group"}; - const auto GROUP = PLASTWINDOW->m_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()); - if (BACK) - GROUP->swapWithLast(); - else - GROUP->swapWithNext(); + PLASTWINDOW->updateWindowDecos(); return {}; } @@ -2988,22 +3164,12 @@ SDispatchResult CKeybindManager::setProp(std::string args) { try { if (PROP == "max_size") { - 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->m_ruleApplicator->maxSizeOverride(Desktop::Types::COverridableVar(configStringToVector2D(VAL), Desktop::Types::PRIORITY_SET_PROP)); PWINDOW->clampWindowSize(std::nullopt, PWINDOW->m_ruleApplicator->maxSize().value()); PWINDOW->setHidden(false); } else if (PROP == "min_size") { - 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->m_ruleApplicator->minSizeOverride(Desktop::Types::COverridableVar(configStringToVector2D(VAL), Desktop::Types::PRIORITY_SET_PROP)); + PWINDOW->clampWindowSize(std::nullopt, PWINDOW->m_ruleApplicator->minSize().value()); PWINDOW->setHidden(false); } else if (PROP == "active_border_color" || PROP == "inactive_border_color") { CGradientValueData colorData = {}; @@ -3122,18 +3288,16 @@ 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::FOCUS_REASON_KEYBIND); - Desktop::focusState()->fullWindowFocus(PWINDOW, Desktop::FOCUS_REASON_KEYBIND); - Desktop::focusState()->fullWindowFocus(PLASTWINDOW, Desktop::FOCUS_REASON_KEYBIND); + Desktop::focusState()->rawWindowFocus(nullptr); + Desktop::focusState()->fullWindowFocus(PWINDOW); + Desktop::focusState()->fullWindowFocus(PLASTWINDOW); } if (PROP == "no_vrr") g_pConfigManager->ensureVRR(PWINDOW->m_monitor.lock()); - for (auto const& m : g_pCompositor->m_monitors) { - if (m->m_activeWorkspace) - m->m_activeWorkspace->m_space->recalculate(); - } + for (auto const& m : g_pCompositor->m_monitors) + g_pLayoutManager->getCurrentLayout()->recalculateMonitor(m->m_id); return {}; } diff --git a/src/managers/KeybindManager.hpp b/src/managers/KeybindManager.hpp index 1f013606..d4b1bf66 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 std::optional spawnWithRules(std::string, PHLWORKSPACE pInitialWorkspace); + static uint64_t spawnWithRules(std::string, PHLWORKSPACE pInitialWorkspace); // -------------- Dispatchers -------------- // static SDispatchResult closeActive(std::string); @@ -194,7 +194,10 @@ 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 new file mode 100644 index 00000000..050f1d50 --- /dev/null +++ b/src/managers/LayoutManager.cpp @@ -0,0 +1,60 @@ +#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 new file mode 100644 index 00000000..80c522fb --- /dev/null +++ b/src/managers/LayoutManager.hpp @@ -0,0 +1,31 @@ +#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 7256e176..d2065d69 100644 --- a/src/managers/PointerManager.cpp +++ b/src/managers/PointerManager.cpp @@ -8,41 +8,38 @@ #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 = Event::bus()->m_events.monitor.added.listen([this](PHLMONITOR monitor) { + m_hooks.monitorAdded = g_pHookSystem->hookDynamic("monitorAdded", [this](void* self, SCallbackInfo& info, std::any data) { + auto PMONITOR = std::any_cast(data); + onMonitorLayoutChange(); - 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] { + 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] { if (g_pCompositor && !g_pCompositor->m_isShuttingDown) std::erase_if(m_monitorStates, [](const auto& other) { return other->monitor.expired(); }); }); }); - m_hooks.monitorPreRender = Event::bus()->m_events.monitor.preCommit.listen([this](PHLMONITOR monitor) { - auto state = stateFor(monitor); + m_hooks.monitorPreRender = g_pHookSystem->hookDynamic("preMonitorCommit", [this](void* self, SCallbackInfo& info, std::any data) { + auto state = stateFor(std::any_cast(data)); if (!state) return; @@ -75,10 +72,8 @@ 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(); @@ -86,22 +81,13 @@ void CPointerManager::unlockSoftwareForMonitor(PHLMONITOR mon) { bool CPointerManager::softwareLockedFor(PHLMONITOR mon) { auto const state = stateFor(mon); - 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(); + return state->softwareLocks > 0 || state->hardwareFailed; } Vector2D CPointerManager::position() { return m_pointerPos; } -Vector2D CPointerManager::hotspot() { - return m_currentCursorImage.hotspot; -} - bool CPointerManager::hasCursor() { return m_currentCursorImage.pBuffer || m_currentCursorImage.surface; } @@ -121,7 +107,6 @@ void CPointerManager::setCursorBuffer(SP buf, const Vector2 m_currentCursorImage.scale = scale; updateCursorBackend(); damageIfSoftware(); - m_events.cursorChanged.emit(); } return; @@ -139,7 +124,6 @@ void CPointerManager::setCursorBuffer(SP buf, const Vector2 updateCursorBackend(); damageIfSoftware(); - m_events.cursorChanged.emit(); } void CPointerManager::setCursorSurface(SP surf, const Vector2D& hotspot) { @@ -151,7 +135,6 @@ 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; @@ -173,7 +156,6 @@ void CPointerManager::setCursorSurface(SP surf, const recheckEnteredOutputs(); updateCursorBackend(); damageIfSoftware(); - m_events.cursorChanged.emit(); }); if (surf->resource()->m_current.texture) { @@ -187,7 +169,6 @@ void CPointerManager::setCursorSurface(SP surf, const recheckEnteredOutputs(); updateCursorBackend(); damageIfSoftware(); - m_events.cursorChanged.emit(); } void CPointerManager::recheckEnteredOutputs() { @@ -272,8 +253,6 @@ void CPointerManager::resetCursorImage(bool apply) { ms->cursorFrontBuffer = nullptr; } } - - m_events.cursorChanged.emit(); } void CPointerManager::updateCursorBackend() { @@ -410,7 +389,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; @@ -454,7 +433,7 @@ SP CPointerManager::renderHWCursorBuffer(SPmonitor->m_output->getBackend()->preferredAllocator()->drmFD(), g_pCompositor->m_drm.fd); + options.multigpu = 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) @@ -543,23 +522,24 @@ SP CPointerManager::renderHWCursorBuffer(SPm_size / (m_currentCursorImage.size / m_currentCursorImage.scale * state->monitor->m_scale); - const auto SX = SCALE.x, SY = SCALE.y; - const auto BW = sc(DMABUF.size.x), BH = sc(DMABUF.size.y); + cairo_matrix_scale(&matrixPre, SCALE.x, SCALE.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; + 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_set_matrix(PATTERNPRE, &matrixPre); @@ -592,18 +572,20 @@ SP CPointerManager::renderHWCursorBuffer(SPbind(); - 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. + 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}); 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, {.noCM = true}); + g_pHyprOpenGL->renderTexture(texture, xbox, {}); g_pHyprOpenGL->end(); g_pHyprOpenGL->m_renderData.pMonitor.reset(); + g_pHyprRenderer->onRenderbufferDestroy(RBO.get()); + return buf; } @@ -756,26 +738,17 @@ 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) { - auto monitor = mw->monitor.lock(); - if (!monitor || !monitor->m_output || monitor->isMirror()) + if (mw->monitor.expired() || !mw->monitor->m_output) continue; - 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); + 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; + } } } @@ -899,17 +872,13 @@ void CPointerManager::onMonitorLayoutChange() { damageIfSoftware(); } -const CPointerManager::SCursorImage& CPointerManager::currentCursorImage() { - return m_currentCursorImage; -} - -SP CPointerManager::getCurrentCursorTexture() { +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 = g_pHyprRenderer->createTexture(m_currentCursorImage.pBuffer, true); + m_currentCursorImage.bufferTex = makeShared(m_currentCursorImage.pBuffer, true); return m_currentCursorImage.bufferTex; } @@ -951,11 +920,24 @@ 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([] { g_pInputManager->onPointerFrame(); }); + + 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->swipeBegin = pointer->m_pointerEvents.swipeBegin.listen([](const IPointer::SSwipeBeginEvent& event) { g_pInputManager->onSwipeBegin(event); @@ -1108,7 +1090,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, bool skipFrameSchedule) { +void CPointerManager::damageCursor(PHLMONITOR pMonitor) { for (auto const& mw : m_monitorStates) { if (mw->monitor != pMonitor) continue; @@ -1118,7 +1100,7 @@ void CPointerManager::damageCursor(PHLMONITOR pMonitor, bool skipFrameSchedule) if (b.empty()) return; - g_pHyprRenderer->damageBox(b, skipFrameSchedule); + g_pHyprRenderer->damageBox(b); return; } @@ -1127,3 +1109,22 @@ void CPointerManager::damageCursor(PHLMONITOR pMonitor, bool skipFrameSchedule) 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 a4fe1971..e7294fd4 100644 --- a/src/managers/PointerManager.hpp +++ b/src/managers/PointerManager.hpp @@ -7,12 +7,11 @@ #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 ITexture; +class CTexture; AQUAMARINE_FORWARD(IBuffer); @@ -49,7 +48,6 @@ 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); @@ -57,38 +55,17 @@ 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, bool skipFrameSchedule = false); + void damageCursor(PHLMONITOR pMonitor); // 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(); @@ -104,9 +81,13 @@ 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); + CBox getCursorBoxLogicalForMonitor(PHLMONITOR pMonitor); + // returns the thing in global coords + CBox getCursorBoxGlobal(); - Vector2D transformedHotspot(PHLMONITOR pMonitor); + Vector2D transformedHotspot(PHLMONITOR pMonitor); + + SP getCurrentCursorTexture(); struct SPointerListener { CHyprSignalListener destroy; @@ -158,9 +139,24 @@ class CPointerManager { std::vector monitorBoxes; } m_currentMonitorLayout; - SCursorImage m_currentCursorImage; // TODO: support various sizes per-output so we can have pixel-perfect cursors + struct { + SP pBuffer; + SP bufferTex; + WP surface; - Vector2D m_pointerPos = {0, 0}; + 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}; struct SMonitorPointerState { SMonitorPointerState(const PHLMONITOR& m) : monitor(m) {} @@ -181,12 +177,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 { - CHyprSignalListener monitorAdded; - CHyprSignalListener monitorPreRender; + SP monitorAdded; + SP monitorPreRender; } m_hooks; }; diff --git a/src/managers/ProtocolManager.cpp b/src/managers/ProtocolManager.cpp index c13e6e48..ce77e2fe 100644 --- a/src/managers/ProtocolManager.cpp +++ b/src/managers/ProtocolManager.cpp @@ -50,8 +50,6 @@ #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" @@ -69,7 +67,6 @@ #include "../protocols/CommitTiming.hpp" #include "../helpers/Monitor.hpp" -#include "../event/EventBus.hpp" #include "../render/Renderer.hpp" #include "../Compositor.hpp" #include "content-type-v1.hpp" @@ -110,10 +107,10 @@ CProtocolManager::CProtocolManager() { static const auto PENABLECM = CConfigValue("render:cm_enabled"); static const auto PDEBUGCM = CConfigValue("debug:full_cm_proto"); - 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) { + static auto P = g_pHookSystem->hookDynamic("monitorAdded", [this](void* self, SCallbackInfo& info, std::any param) { + auto M = std::any_cast(param); + // 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 @@ -130,7 +127,8 @@ CProtocolManager::CProtocolManager() { m_modeChangeListeners[M->m_name] = M->m_events.modeChanged.listen([this, M] { onMonitorModeChange(M); }); }); - static auto P2 = Event::bus()->m_events.monitor.removed.listen([this](PHLMONITOR M) { + static auto P2 = g_pHookSystem->hookDynamic("monitorRemoved", [this](void* self, SCallbackInfo& info, std::any param) { + auto M = std::any_cast(param); if (!PROTO::outputs.contains(M->m_name)) return; PROTO::outputs.at(M->m_name)->remove(); @@ -142,7 +140,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, 2, "WLSHM"); + PROTO::shm = makeUnique(&wl_shm_interface, 1, "WLSHM"); // Extensions PROTO::viewport = makeUnique(&wp_viewporter_interface, 1, "Viewporter"); @@ -180,6 +178,8 @@ 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,15 +194,7 @@ 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"); - - 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"); + PROTO::commitTiming = makeUnique(&wp_commit_timing_manager_v1_interface, 1, "CommitTiming"); if (*PENABLECM) PROTO::colorManagement = makeUnique(&wp_color_manager_v1_interface, 1, "ColorManagement", *PDEBUGCM); @@ -302,7 +294,6 @@ CProtocolManager::~CProtocolManager() { PROTO::pointerWarp.reset(); PROTO::fifo.reset(); PROTO::commitTiming.reset(); - PROTO::imageCaptureSource.reset(); for (auto& [_, lease] : PROTO::lease) { lease.reset(); @@ -345,6 +336,9 @@ 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 a107dced..f40c55e3 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,9 +618,8 @@ void CSeatManager::setGrab(SP grab) { if (m_seatGrab) { auto oldGrab = m_seatGrab; - // Try to find the parent window or layer surface from the grab + // Try to find the parent window 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; @@ -646,11 +645,8 @@ 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()); - } } } } @@ -658,22 +654,18 @@ void CSeatManager::setGrab(SP grab) { m_seatGrab.reset(); - 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(); + 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, 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); + // 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); } else - g_pInputManager->refocus(); - } + g_pInputManager->refocusLastWindow(PMONITOR); + } else + g_pInputManager->refocus(); auto currentFocus = m_state.keyboardFocus.lock(); auto refocus = !currentFocus; @@ -701,7 +693,7 @@ void CSeatManager::setGrab(SP grab) { auto candidate = Desktop::focusState()->window(); if (candidate) - Desktop::focusState()->rawWindowFocus(candidate, Desktop::FOCUS_REASON_FFM); + Desktop::focusState()->rawWindowFocus(candidate); } if (oldGrab->m_onEnd) diff --git a/src/managers/SeatManager.hpp b/src/managers/SeatManager.hpp index 21736e3a..fe11f930 100644 --- a/src/managers/SeatManager.hpp +++ b/src/managers/SeatManager.hpp @@ -127,6 +127,9 @@ 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 b1d167fa..6f94fbe5 100644 --- a/src/managers/VersionKeeperManager.cpp +++ b/src/managers/VersionKeeperManager.cpp @@ -34,8 +34,8 @@ CVersionKeeperManager::CVersionKeeperManager() { return; } - if (!isMajorVersionOlderThanRunning(*LASTVER)) { - Log::logger->log(Log::DEBUG, "CVersionKeeperManager: Read version {} matches or is older than running major.", *LASTVER); + if (!isVersionOlderThanRunning(*LASTVER)) { + Log::logger->log(Log::DEBUG, "CVersionKeeperManager: Read version {} matches or is older than running.", *LASTVER); return; } @@ -59,21 +59,25 @@ CVersionKeeperManager::CVersionKeeperManager() { }); } -bool CVersionKeeperManager::isMajorVersionOlderThanRunning(const std::string& ver) { +bool CVersionKeeperManager::isVersionOlderThanRunning(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 11bfb7df..15821879 100644 --- a/src/managers/VersionKeeperManager.hpp +++ b/src/managers/VersionKeeperManager.hpp @@ -10,7 +10,7 @@ class CVersionKeeperManager { bool fired(); private: - bool isMajorVersionOlderThanRunning(const std::string& ver); + bool isVersionOlderThanRunning(const std::string& ver); bool m_fired = false; }; diff --git a/src/managers/WelcomeManager.cpp b/src/managers/WelcomeManager.cpp index 6faf58c3..7a0b8f7f 100644 --- a/src/managers/WelcomeManager.cpp +++ b/src/managers/WelcomeManager.cpp @@ -1,5 +1,4 @@ #include "WelcomeManager.hpp" -#include "../Compositor.hpp" #include "../debug/log/Logger.hpp" #include "../config/ConfigValue.hpp" #include "../helpers/fs/FsUtils.hpp" @@ -16,11 +15,6 @@ 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 1fca293a..c3c4f901 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).clamp(MINSIZE + Vector2D{1, 1}); + Vector2D MAXSIZE = pWindow->maxSize().value_or(Math::VECTOR2D_MAX); Vector2D oldSize = box.size(); box.w = std::clamp(box.w, MINSIZE.x, MAXSIZE.x); @@ -129,8 +129,7 @@ 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 > 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))) + (SIZEHINTS && (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 c4b921cb..9a3fc157 100644 --- a/src/managers/animation/AnimationManager.cpp +++ b/src/managers/animation/AnimationManager.cpp @@ -1,5 +1,6 @@ #include "AnimationManager.hpp" #include "../../Compositor.hpp" +#include "../HookSystemManager.hpp" #include "../../config/ConfigManager.hpp" #include "../../desktop/DesktopTypes.hpp" #include "../../helpers/AnimatedVariable.hpp" @@ -10,7 +11,6 @@ #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 && !PMONITOR->inFullscreenMode()) + if (PMONITOR) g_pCompositor->scheduleFrameForMonitor(PMONITOR, Aquamarine::IOutput::AQ_SCHEDULE_ANIMATION); } @@ -216,9 +216,6 @@ 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(); @@ -252,8 +249,8 @@ void CHyprAnimationManager::frameTick() { if (!shouldTickForNext()) return; - 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; })) + 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; })) return; if (!m_lastTickValid || m_lastTickTimer.getMillis() >= 1.0f) { @@ -261,7 +258,7 @@ void CHyprAnimationManager::frameTick() { m_lastTickValid = true; tick(); - Event::bus()->m_events.tick.emit(); + EMIT_HOOK_EVENT("tick", nullptr); } if (shouldTickForNext()) @@ -286,11 +283,6 @@ 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 35bb1e8a..b8acc53e 100644 --- a/src/managers/animation/AnimationManager.hpp +++ b/src/managers/animation/AnimationManager.hpp @@ -18,9 +18,6 @@ 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 2c450add..333df7e7 100644 --- a/src/managers/animation/DesktopAnimationManager.cpp +++ b/src/managers/animation/DesktopAnimationManager.cpp @@ -1,15 +1,11 @@ #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) { @@ -410,26 +406,32 @@ void CDesktopAnimationManager::animationSlide(PHLWINDOW pWindow, std::string for } const auto MIDPOINT = GOALPOS + GOALSIZE / 2.f; - const auto MONBOX = PMONITOR->logicalBox(); - // 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, // - }; + // 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); - 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 (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); + } if (!close) pWindow->m_realPosition->setValue(posOffset); @@ -470,7 +472,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->m_group && FSWINDOW->m_group->has(w); + const bool IS_IN_GROUP_OF_FS = FSWINDOW && FSWINDOW->hasInGroup(w); *w->m_alpha = !CREATED_OVER_FS && !IS_IN_GROUP_OF_FS ? 0.f : 1.f; } } @@ -486,13 +488,6 @@ 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 fa86425e..f27f09d2 100644 --- a/src/managers/animation/DesktopAnimationManager.hpp +++ b/src/managers/animation/DesktopAnimationManager.hpp @@ -16,7 +16,6 @@ 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 e38474aa..496cbb83 100644 --- a/src/managers/eventLoop/EventLoopManager.cpp +++ b/src/managers/eventLoop/EventLoopManager.cpp @@ -26,7 +26,10 @@ CEventLoopManager::~CEventLoopManager() { wl_event_source_remove(eventSourceData.eventSource); } - m_readableWaiters.clear(); + for (auto const& w : m_readableWaiters) { + if (w->source != nullptr) + wl_event_source_remove(w->source); + } if (m_wayland.eventSource) wl_event_source_remove(m_wayland.eventSource); @@ -37,21 +40,14 @@ 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 0; + return 1; } static int aquamarineFDWrite(int fd, uint32_t mask, void* data) { auto POLLFD = sc(data); POLLFD->onSignal(); - return 0; + return 1; } static int configWatcherWrite(int fd, uint32_t mask, void* data) { @@ -60,21 +56,13 @@ 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(waiter); + g_pEventLoopManager->onFdReadable(sc(data)); return 0; } @@ -86,11 +74,6 @@ 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); @@ -98,16 +81,6 @@ 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); @@ -209,12 +182,12 @@ void CEventLoopManager::doLater(const std::function& fn) { m_wayland.loop, [](void* data) { auto IDLE = sc(data); - auto fns = std::move(IDLE->fns); + auto cpy = IDLE->fns; IDLE->fns.clear(); IDLE->eventSource = nullptr; - for (auto& f : fns) { - if (f) - f(); + for (auto const& c : cpy) { + if (c) + c(); } }, &m_idle); diff --git a/src/managers/eventLoop/EventLoopManager.hpp b/src/managers/eventLoop/EventLoopManager.hpp index 7999dc59..7a3b4314 100644 --- a/src/managers/eventLoop/EventLoopManager.hpp +++ b/src/managers/eventLoop/EventLoopManager.hpp @@ -65,7 +65,6 @@ 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 9195536f..73da6df4 100644 --- a/src/managers/input/InputManager.cpp +++ b/src/managers/input/InputManager.cpp @@ -35,16 +35,14 @@ #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" @@ -132,10 +130,16 @@ 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); @@ -147,8 +151,6 @@ void CInputManager::onMouseMoved(IPointer::SMotionEvent e) { if (e.mouse) m_lastMousePos = getMouseCoordsInternal(); - - g_pSeatManager->sendPointerFrame(); } void CInputManager::onMouseWarp(IPointer::SMotionAbsoluteEvent e) { @@ -193,7 +195,7 @@ void CInputManager::sendMotionEventsToFocused() { m_emptyFocusCursorSet = false; - g_pSeatManager->setPointerFocus(Desktop::focusState()->surface(), getMouseCoordsInternal().floor() - BOX->pos()); + g_pSeatManager->setPointerFocus(Desktop::focusState()->surface(), m_lastCursorPosFloored - BOX->pos()); } void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, std::optional overridePos) { @@ -232,12 +234,8 @@ 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; - Event::SCallbackInfo info; - Event::bus()->m_events.input.mouse.move.emit(MOUSECOORDSFLOORED, info); - if (info.cancelled) - return; + EMIT_HOOK_EVENT_CANCELLABLE("mouseMove", MOUSECOORDSFLOORED); m_lastCursorPosFloored = MOUSECOORDSFLOORED; @@ -371,7 +369,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st } } - g_layoutManager->moveMouse(getMouseCoordsInternal()); + g_pLayoutManager->getCurrentLayout()->onMouseMove(getMouseCoordsInternal()); // forced above all if (!g_pInputManager->m_exclusiveLSes.empty()) { @@ -528,7 +526,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, FOCUS_REASON); + Desktop::focusState()->rawWindowFocus(nullptr); return; } @@ -556,7 +554,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st m_foundSurfaceToFocus = foundSurface; } - if (g_layoutManager->dragController()->target() && pFoundWindow != g_layoutManager->dragController()->target()) { + if (m_currentlyDraggedWindow.lock() && pFoundWindow != m_currentlyDraggedWindow) { g_pSeatManager->setPointerFocus(foundSurface, surfaceLocal); return; } @@ -588,7 +586,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, FOCUS_REASON, foundSurface); + Desktop::focusState()->rawWindowFocus(pFoundWindow, foundSurface); g_pSeatManager->setPointerFocus(foundSurface, surfaceLocal); } else if (FOLLOWMOUSE == 2 || FOLLOWMOUSE == 3) g_pSeatManager->setPointerFocus(foundSurface, surfaceLocal); @@ -616,7 +614,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, FOCUS_REASON, foundSurface); + Desktop::focusState()->rawWindowFocus(pFoundWindow, foundSurface); } } else Desktop::focusState()->rawSurfaceFocus(foundSurface, pFoundWindow); @@ -625,7 +623,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st } if (g_pSeatManager->m_state.keyboardFocus == nullptr) - Desktop::focusState()->rawWindowFocus(pFoundWindow, FOCUS_REASON, foundSurface); + Desktop::focusState()->rawWindowFocus(pFoundWindow, foundSurface); m_lastFocusOnLS = false; } else { @@ -648,10 +646,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus, bool mouse, st } void CInputManager::onMouseButton(IPointer::SButtonEvent e) { - Event::SCallbackInfo info; - Event::bus()->m_events.input.mouse.button.emit(e, info); - if (info.cancelled) - return; + EMIT_HOOK_EVENT_CANCELLABLE("mouseButton", e); if (e.mouse) recheckMouseWarpOnMouseInput(); @@ -681,8 +676,6 @@ void CInputManager::onMouseButton(IPointer::SButtonEvent e) { m_focusHeldByButtons = false; m_refocusHeldByButtons = false; } - - g_pSeatManager->sendPointerFrame(); } void CInputManager::processMouseRequest(const CSeatManager::SSetCursorEvent& event) { @@ -813,10 +806,8 @@ void CInputManager::processMouseDownNormal(const IPointer::SButtonEvent& e) { auto HLSurf = Desktop::View::CWLSurface::fromResource(g_pSeatManager->m_state.pointerFocus.lock()); - // 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); + if (HLSurf && HLSurf->view()->type() == Desktop::View::VIEW_TYPE_WINDOW) + g_pCompositor->changeWindowZOrder(dynamicPointerCast(HLSurf->view()), true); break; } @@ -847,9 +838,6 @@ 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; @@ -876,10 +864,8 @@ void CInputManager::onMouseWheel(IPointer::SAxisEvent e, SP pointer) { if (pointer && pointer->m_scrollFactor.has_value()) factor = *pointer->m_scrollFactor; - Event::SCallbackInfo info; - Event::bus()->m_events.input.mouse.axis.emit(e, info); - if (info.cancelled) - return; + const auto EMAP = std::unordered_map{{"event", e}}; + EMIT_HOOK_EVENT_CANCELLABLE("mouseAxis", EMAP); if (e.mouse) recheckMouseWarpOnMouseInput(); @@ -966,23 +952,6 @@ 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() { @@ -1068,7 +1037,7 @@ void CInputManager::setupKeyboard(SP keeb) { } g_pEventManager->postEvent(SHyprIPCEvent{"activelayout", PKEEB->m_hlName + "," + LAYOUT}); - Event::bus()->m_events.input.keyboard.layout.emit(PKEEB, LAYOUT); + EMIT_HOOK_EVENT("activeLayout", (std::vector{PKEEB, LAYOUT})); }); disableAllKeyboards(false); @@ -1165,7 +1134,7 @@ void CInputManager::applyConfigToKeyboard(SP pKeyboard) { const auto LAYOUTSTR = pKeyboard->getActiveLayout(); g_pEventManager->postEvent(SHyprIPCEvent{"activelayout", pKeyboard->m_hlName + "," + LAYOUTSTR}); - Event::bus()->m_events.input.keyboard.layout.emit(pKeyboard, LAYOUTSTR); + EMIT_HOOK_EVENT("activeLayout", (std::vector{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); @@ -1487,16 +1456,14 @@ 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; - Event::SCallbackInfo info; - Event::bus()->m_events.input.keyboard.key.emit(event, info); - if (info.cancelled) - return; + const auto EMAP = std::unordered_map{{"keyboard", pKeyboard}, {"event", event}}; + EMIT_HOOK_EVENT_CANCELLABLE("keyPress", EMAP); bool passEvent = DISALLOWACTION; @@ -1585,7 +1552,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}); - Event::bus()->m_events.input.keyboard.layout.emit(pKeyboard, LAYOUT); + EMIT_HOOK_EVENT("activeLayout", (std::vector{pKeyboard, LAYOUT})); } } @@ -1645,13 +1612,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::FOCUS_REASON_FFM); + Desktop::focusState()->fullWindowFocus(PLASTWINDOW); } 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::FOCUS_REASON_FFM); + Desktop::focusState()->fullWindowFocus(PLASTWINDOW); } refocus(); @@ -1967,7 +1934,7 @@ void CInputManager::setCursorIconOnBorder(PHLWINDOW w) { if (w->hasPopupAt(mouseCoords)) direction = BORDERICON_NONE; - else if (!boxFullGrabInput.containsPoint(mouseCoords) || (!m_currentlyHeldButtons.empty() && !g_layoutManager->dragController()->target())) + else if (!boxFullGrabInput.containsPoint(mouseCoords) || (!m_currentlyHeldButtons.empty() && m_currentlyDraggedWindow.expired())) direction = BORDERICON_NONE; else { @@ -2053,10 +2020,7 @@ void CInputManager::recheckMouseWarpOnMouseInput() { } void CInputManager::onSwipeBegin(IPointer::SSwipeBeginEvent e) { - Event::SCallbackInfo info; - Event::bus()->m_events.gesture.swipe.begin.emit(e, info); - if (info.cancelled) - return; + EMIT_HOOK_EVENT_CANCELLABLE("swipeBegin", e); g_pTrackpadGestures->gestureBegin(e); @@ -2064,10 +2028,7 @@ void CInputManager::onSwipeBegin(IPointer::SSwipeBeginEvent e) { } void CInputManager::onSwipeUpdate(IPointer::SSwipeUpdateEvent e) { - Event::SCallbackInfo info; - Event::bus()->m_events.gesture.swipe.update.emit(e, info); - if (info.cancelled) - return; + EMIT_HOOK_EVENT_CANCELLABLE("swipeUpdate", e); g_pTrackpadGestures->gestureUpdate(e); @@ -2075,10 +2036,7 @@ void CInputManager::onSwipeUpdate(IPointer::SSwipeUpdateEvent e) { } void CInputManager::onSwipeEnd(IPointer::SSwipeEndEvent e) { - Event::SCallbackInfo info; - Event::bus()->m_events.gesture.swipe.end.emit(e, info); - if (info.cancelled) - return; + EMIT_HOOK_EVENT_CANCELLABLE("swipeEnd", e); g_pTrackpadGestures->gestureEnd(e); @@ -2086,10 +2044,7 @@ void CInputManager::onSwipeEnd(IPointer::SSwipeEndEvent e) { } void CInputManager::onPinchBegin(IPointer::SPinchBeginEvent e) { - Event::SCallbackInfo info; - Event::bus()->m_events.gesture.pinch.begin.emit(e, info); - if (info.cancelled) - return; + EMIT_HOOK_EVENT_CANCELLABLE("pinchBegin", e); g_pTrackpadGestures->gestureBegin(e); @@ -2097,10 +2052,7 @@ void CInputManager::onPinchBegin(IPointer::SPinchBeginEvent e) { } void CInputManager::onPinchUpdate(IPointer::SPinchUpdateEvent e) { - Event::SCallbackInfo info; - Event::bus()->m_events.gesture.pinch.update.emit(e, info); - if (info.cancelled) - return; + EMIT_HOOK_EVENT_CANCELLABLE("pinchUpdate", e); g_pTrackpadGestures->gestureUpdate(e); @@ -2108,10 +2060,7 @@ void CInputManager::onPinchUpdate(IPointer::SPinchUpdateEvent e) { } void CInputManager::onPinchEnd(IPointer::SPinchEndEvent e) { - Event::SCallbackInfo info; - Event::bus()->m_events.gesture.pinch.end.emit(e, info); - if (info.cancelled) - return; + EMIT_HOOK_EVENT_CANCELLABLE("pinchEnd", e); g_pTrackpadGestures->gestureEnd(e); diff --git a/src/managers/input/InputManager.hpp b/src/managers/input/InputManager.hpp index ca235a23..239f6140 100644 --- a/src/managers/input/InputManager.hpp +++ b/src/managers/input/InputManager.hpp @@ -91,7 +91,6 @@ 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); @@ -154,6 +153,12 @@ 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; @@ -294,7 +299,6 @@ 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 27fd80b6..15dd249e 100644 --- a/src/managers/input/InputMethodRelay.cpp +++ b/src/managers/input/InputMethodRelay.cpp @@ -1,13 +1,14 @@ #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 = Event::bus()->m_events.input.keyboard.focus.listen([&](SP surf) { onKeyboardFocus(surf); }); + static auto P = + g_pHookSystem->hookDynamic("keyboardFocus", [&](void* self, SCallbackInfo& info, std::any param) { onKeyboardFocus(std::any_cast>(param)); }); 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); }); @@ -74,11 +75,6 @@ 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 a2fec15c..52be6eee 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,11 +107,6 @@ 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); @@ -176,10 +171,7 @@ void CInputManager::onTabletAxis(CTablet::SAxisEvent e) { } void CInputManager::onTabletTip(CTablet::STipEvent e) { - Event::SCallbackInfo info; - Event::bus()->m_events.input.tablet.tip.emit(e, info); - if (info.cancelled) - return; + EMIT_HOOK_EVENT_CANCELLABLE("tabletTip", e); const auto PTAB = e.tablet; const auto PTOOL = ensureTabletToolPresent(e.tool); @@ -204,11 +196,6 @@ 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) @@ -223,11 +210,6 @@ 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 be9a5d29..4475b5ee 100644 --- a/src/managers/input/TextInput.cpp +++ b/src/managers/input/TextInput.cpp @@ -22,7 +22,13 @@ 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] { destroy(); }); + 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); + }); if (Desktop::focusState()->surface() && Desktop::focusState()->surface()->client() == INPUT->client()) enter(Desktop::focusState()->surface()); @@ -33,20 +39,16 @@ 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] { destroy(); }); + 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); + }); } } -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"); @@ -303,7 +305,3 @@ 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 798f31e9..fd24dbfa 100644 --- a/src/managers/input/TextInput.hpp +++ b/src/managers/input/TextInput.hpp @@ -29,7 +29,6 @@ class CTextInput { void onCommit(); void onReset(); - bool isEnabled(); bool hasCursorRectangle(); CBox cursorBox(); @@ -39,8 +38,6 @@ 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 e45bfd28..6136cb3f 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,14 +19,10 @@ 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"); - - Event::SCallbackInfo info; - Event::bus()->m_events.input.touch.down.emit(e, info); - if (info.cancelled) - return; + 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 PMONITOR = g_pCompositor->getMonitorFromName(!e.device->m_boundOutput.empty() ? e.device->m_boundOutput : ""); @@ -113,11 +109,7 @@ void CInputManager::onTouchDown(ITouch::SDownEvent e) { void CInputManager::onTouchUp(ITouch::SUpEvent e) { m_lastInputTouch = true; - Event::SCallbackInfo info; - Event::bus()->m_events.input.touch.up.emit(e, info); - if (info.cancelled) - return; - + EMIT_HOOK_EVENT_CANCELLABLE("touchUp", e); if (g_pUnifiedWorkspaceSwipe->isGestureInProgress()) { // If there was a swipe from this finger, end it. if (e.touchID == g_pUnifiedWorkspaceSwipe->m_touchID) @@ -134,11 +126,7 @@ void CInputManager::onTouchMove(ITouch::SMotionEvent e) { m_lastCursorMovement.reset(); - Event::SCallbackInfo info; - Event::bus()->m_events.input.touch.motion.emit(e, info); - if (info.cancelled) - return; - + EMIT_HOOK_EVENT_CANCELLABLE("touchMove", e); 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 e054c2f9..d41b8ede 100644 --- a/src/managers/input/trackpad/TrackpadGestures.cpp +++ b/src/managers/input/trackpad/TrackpadGestures.cpp @@ -1,8 +1,6 @@ #include "TrackpadGestures.hpp" #include "../InputManager.hpp" -#include "../../../config/ConfigValue.hpp" -#include "../../../protocols/ShortcutsInhibit.hpp" #include @@ -56,7 +54,7 @@ const char* CTrackpadGestures::stringForDir(eTrackpadGestureDirection dir) { } std::expected CTrackpadGestures::addGesture(UP&& gesture, size_t fingerCount, eTrackpadGestureDirection direction, uint32_t modMask, - float deltaScale, bool disableInhibit) { + float deltaScale) { for (const auto& g : m_gestures) { if (g->fingerCount != fingerCount) continue; @@ -86,16 +84,14 @@ std::expected CTrackpadGestures::addGesture(UP(std::move(gesture), fingerCount, modMask, direction, deltaScale, disableInhibit)); + m_gestures.emplace_back(makeShared(std::move(gesture), fingerCount, modMask, direction, deltaScale)); return {}; } -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; - }); +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; }); if (IT == m_gestures.end()) return std::unexpected("Can't remove a non-existent gesture"); @@ -118,8 +114,6 @@ 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; @@ -154,9 +148,6 @@ 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}); @@ -193,8 +184,6 @@ 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; @@ -222,9 +211,6 @@ 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 ecf11c40..7f96761f 100644 --- a/src/managers/input/trackpad/TrackpadGestures.hpp +++ b/src/managers/input/trackpad/TrackpadGestures.hpp @@ -11,9 +11,8 @@ class CTrackpadGestures { public: void clearGestures(); - 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); + 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); void gestureBegin(const IPointer::SSwipeBeginEvent& e); void gestureUpdate(const IPointer::SSwipeUpdateEvent& e); @@ -33,7 +32,6 @@ 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 0c37ee36..7beba563 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; - window->layoutTarget()->recalc(); + g_pLayoutManager->getCurrentLayout()->recalculateWindow(window.lock()); 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 deleted file mode 100644 index 97dfe158..00000000 --- a/src/managers/input/trackpad/gestures/CursorZoomGesture.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#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 deleted file mode 100644 index b53c81e9..00000000 --- a/src/managers/input/trackpad/gestures/CursorZoomGesture.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#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 0849bfac..b2f451f1 100644 --- a/src/managers/input/trackpad/gestures/FloatGesture.cpp +++ b/src/managers/input/trackpad/gestures/FloatGesture.cpp @@ -1,10 +1,9 @@ #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; @@ -41,7 +40,8 @@ void CFloatTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& return; } - g_layoutManager->changeFloatingMode(m_window->layoutTarget()); + m_window->m_isFloating = !m_window->m_isFloating; + g_pLayoutManager->getCurrentLayout()->changeWindowFloatingMode(m_window.lock()); m_posFrom = m_window->m_realPosition->begun(); m_sizeFrom = m_window->m_realSize->begun(); @@ -79,7 +79,8 @@ void CFloatTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) if (COMPLETION < 0.2F) { // revert the animation g_pHyprRenderer->damageWindow(m_window.lock()); - g_layoutManager->changeFloatingMode(m_window->layoutTarget()); + m_window->m_isFloating = !m_window->m_isFloating; + g_pLayoutManager->getCurrentLayout()->changeWindowFloatingMode(m_window.lock()); return; } diff --git a/src/managers/input/trackpad/gestures/FullscreenGesture.cpp b/src/managers/input/trackpad/gestures/FullscreenGesture.cpp index a219b685..31592f63 100644 --- a/src/managers/input/trackpad/gestures/FullscreenGesture.cpp +++ b/src/managers/input/trackpad/gestures/FullscreenGesture.cpp @@ -77,6 +77,7 @@ 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 0dcc310f..034d88fb 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_layoutManager->moveTarget(DELTA, m_window->layoutTarget()); + g_pLayoutManager->getCurrentLayout()->moveActiveWindow(DELTA, m_window.lock()); 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_layoutManager->moveInDirection(m_window->layoutTarget(), m_lastDelta.x > 0 ? "r" : "l"); + g_pLayoutManager->getCurrentLayout()->moveWindowTo(m_window.lock(), m_lastDelta.x > 0 ? "r" : "l"); } else { // vertical - g_layoutManager->moveInDirection(m_window->layoutTarget(), m_lastDelta.y > 0 ? "b" : "t"); + g_pLayoutManager->getCurrentLayout()->moveWindowTo(m_window.lock(), 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 ffc7704b..33f018cd 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_layoutManager->resizeTarget((e.swipe ? e.swipe->delta : e.pinch->delta), m_window->layoutTarget(), - Layout::cornerFromBox(m_window->getWindowMainSurfaceBox(), g_pInputManager->getMouseCoordsInternal())); + g_pLayoutManager->getCurrentLayout()->resizeActiveWindow((e.swipe ? e.swipe->delta : e.pinch->delta), + cornerFromBox(m_window->getWindowMainSurfaceBox(), g_pInputManager->getMouseCoordsInternal()), m_window.lock()); 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 d63a72a0..0e92ed8e 100644 --- a/src/managers/permissions/DynamicPermissionManager.cpp +++ b/src/managers/permissions/DynamicPermissionManager.cpp @@ -53,7 +53,6 @@ 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"; @@ -252,8 +251,7 @@ 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_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_PLUGIN: description = I18n::i18nEngine()->localize(I18n::TXT_KEY_PERMISSION_REQUEST_SCREENCOPY, {{"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 423596c3..4de1eb32 100644 --- a/src/managers/permissions/DynamicPermissionManager.hpp +++ b/src/managers/permissions/DynamicPermissionManager.hpp @@ -18,7 +18,6 @@ enum eDynamicPermissionType : uint8_t { PERMISSION_TYPE_SCREENCOPY, PERMISSION_TYPE_PLUGIN, PERMISSION_TYPE_KEYBOARD, - PERMISSION_TYPE_CURSOR_POS, }; enum eDynamicPermissionRuleSource : uint8_t { @@ -105,4 +104,4 @@ class CDynamicPermissionManager { std::vector> m_rules; }; -inline UP g_pDynamicPermissionManager; +inline UP g_pDynamicPermissionManager; \ No newline at end of file diff --git a/src/managers/screenshare/CursorshareSession.cpp b/src/managers/screenshare/CursorshareSession.cpp deleted file mode 100644 index 703832ab..00000000 --- a/src/managers/screenshare/CursorshareSession.cpp +++ /dev/null @@ -1,240 +0,0 @@ -#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 deleted file mode 100644 index d747ecee..00000000 --- a/src/managers/screenshare/ScreenshareFrame.cpp +++ /dev/null @@ -1,497 +0,0 @@ -#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 deleted file mode 100644 index 63f2bbbc..00000000 --- a/src/managers/screenshare/ScreenshareManager.cpp +++ /dev/null @@ -1,165 +0,0 @@ -#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 deleted file mode 100644 index 5a4ada5e..00000000 --- a/src/managers/screenshare/ScreenshareManager.hpp +++ /dev/null @@ -1,251 +0,0 @@ -#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 deleted file mode 100644 index 2fddc431..00000000 --- a/src/managers/screenshare/ScreenshareSession.cpp +++ /dev/null @@ -1,166 +0,0 @@ -#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 5f89da53..1d6586aa 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 nullptr; + auto PFN = g_pHookSystem->hookDynamic(event, fn, handle); + PLUGIN->m_registeredCallbacks.emplace_back(std::make_pair<>(event, WP(PFN))); + return PFN; } 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,44 +62,25 @@ APICALL std::string HyprlandAPI::invokeHyprctlCommand(const std::string& call, c } APICALL bool HyprlandAPI::addLayout(HANDLE handle, const std::string& name, IHyprLayout* layout) { - return false; + auto* const PLUGIN = g_pPluginSystem->getPluginByHandle(handle); + + if (!PLUGIN) + return false; + + PLUGIN->m_registeredLayouts.push_back(layout); + + return g_pLayoutManager->addLayout(name, layout); } 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; - PLUGIN->m_registeredAlgos.emplace_back(name); + std::erase(PLUGIN->m_registeredLayouts, 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); + return g_pLayoutManager->removeLayout(layout); } APICALL bool HyprlandAPI::reloadConfig() { @@ -149,7 +130,7 @@ APICALL bool HyprlandAPI::addWindowDecoration(HANDLE handle, PHLWINDOW pWindow, pWindow->addWindowDeco(std::move(pDecoration)); - pWindow->layoutTarget()->recalc(); + g_pLayoutManager->getCurrentLayout()->recalculateWindow(pWindow); return true; } @@ -426,7 +407,7 @@ APICALL bool HyprlandAPI::unregisterHyprCtlCommand(HANDLE handle, SPm_registeredHyprctlCommands, [&](const auto& other) { return !other || other == cmd; }); + std::erase(PLUGIN->m_registeredHyprctlCommands, cmd); g_pHyprCtl->unregisterCommand(cmd); return true; diff --git a/src/plugins/PluginAPI.hpp b/src/plugins/PluginAPI.hpp index 77bb9926..47a695db 100644 --- a/src/plugins/PluginAPI.hpp +++ b/src/plugins/PluginAPI.hpp @@ -70,14 +70,6 @@ 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 @@ -151,8 +143,6 @@ 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(). @@ -160,7 +150,7 @@ namespace HyprlandAPI { WARNING: Losing this pointer will unregister the callback! */ - APICALL [[deprecated]] [[nodiscard]] SP registerCallbackDynamic(HANDLE handle, const std::string& event, HOOK_CALLBACK_FN fn); + APICALL [[nodiscard]] SP registerCallbackDynamic(HANDLE handle, const std::string& event, HOOK_CALLBACK_FN fn); /* Unregisters a callback. If the callback was dynamic, frees the memory. @@ -182,26 +172,15 @@ namespace HyprlandAPI { Adds a layout to Hyprland. returns: true on success. False otherwise. - - deprecated: addTiledAlgo, addFloatingAlgo */ - APICALL [[deprecated]] bool addLayout(HANDLE handle, const std::string& name, IHyprLayout* layout); + APICALL 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 [[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); + APICALL bool removeLayout(HANDLE handle, IHyprLayout* layout); /* Queues a config reload. Does not take effect immediately. diff --git a/src/plugins/PluginSystem.cpp b/src/plugins/PluginSystem.cpp index 3bd8f473..53b05bc8 100644 --- a/src/plugins/PluginSystem.cpp +++ b/src/plugins/PluginSystem.cpp @@ -4,10 +4,11 @@ #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() { @@ -150,15 +151,15 @@ 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 (const auto& l : plugin->m_registeredAlgos) { - Layout::Supplementary::algoMatcher()->unregisterAlgo(l); + 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); + g_pFunctionHookSystem->removeAllHooksFrom(plugin->m_handle); const auto rd = plugin->m_registeredDecorations; @@ -170,10 +171,8 @@ 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) { - if (const auto sp = c.lock()) - HyprlandAPI::unregisterHyprCtlCommand(plugin->m_handle, sp); - } + for (auto const& c : rhc) + HyprlandAPI::unregisterHyprCtlCommand(plugin->m_handle, c); g_pConfigManager->removePluginConfig(plugin->m_handle); diff --git a/src/plugins/PluginSystem.hpp b/src/plugins/PluginSystem.hpp index 85afaefa..ed421960 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_registeredDecorations; - //std::vector>> m_registeredCallbacks; - std::vector m_registeredDispatchers; - std::vector> m_registeredHyprctlCommands; - std::vector m_registeredAlgos; + std::vector m_registeredLayouts; + std::vector m_registeredDecorations; + std::vector>> m_registeredCallbacks; + std::vector m_registeredDispatchers; + std::vector> m_registeredHyprctlCommands; }; class CPluginSystem { diff --git a/src/protocols/ColorManagement.cpp b/src/protocols/ColorManagement.cpp index b9c3143b..afab5a20 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 "../helpers/cm/ColorManagement.hpp" +#include "types/ColorManagement.hpp" #include using namespace NColorManagement; @@ -388,6 +388,12 @@ 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); }); @@ -423,7 +429,7 @@ CColorManagementIccCreator::CColorManagementIccCreator(SPerror(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_INCOMPLETE_SET, "Missing required settings"); return; } @@ -437,10 +443,10 @@ CColorManagementIccCreator::CColorManagementIccCreator(SPresource()->sendFailed(WP_IMAGE_DESCRIPTION_V1_CAUSE_UNSUPPORTED, "unsupported"); return; } @@ -453,9 +459,9 @@ CColorManagementIccCreator::CColorManagementIccCreator(SPsetSetIccFile([this](CWpImageDescriptionCreatorIccV1* r, int fd, uint32_t offset, uint32_t length) { - m_icc.fd = fd; - m_icc.offset = offset; - m_icc.length = length; + m_settings.icc.fd = fd; + m_settings.icc.offset = offset; + m_settings.icc.length = length; }); } @@ -499,14 +505,6 @@ CColorManagementParametricCreator::CColorManagementParametricCreator(SPm_self = RESOURCE; RESOURCE->m_settings = CImageDescription::from(m_settings); RESOURCE->resource()->sendReady(RESOURCE->m_settings->id()); @@ -725,47 +723,26 @@ CColorManagementImageDescriptionInfo::CColorManagementImageDescriptionInfo(SP(std::round(value * PRIMARIES_SCALE)); }; - // FIXME: - // if (m_icc.fd >= 0) - // m_resource->sendIccFile(m_icc.fd, m_icc.length); + if (m_settings.icc.fd >= 0) + m_resource->sendIccFile(m_settings.icc.fd, m_settings.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); - 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); - } + // 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); m_resource->sendDone(); } diff --git a/src/protocols/ColorManagement.hpp b/src/protocols/ColorManagement.hpp index 7cdab37d..d43d5c12 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 "../helpers/cm/ColorManagement.hpp" +#include "types/ColorManagement.hpp" class CColorManager; class CColorManagementOutput; @@ -109,14 +109,6 @@ 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 9cc2da83..2fcd8e9c 100644 --- a/src/protocols/CommitTiming.cpp +++ b/src/protocols/CommitTiming.cpp @@ -12,48 +12,61 @@ 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_surface->m_pending.pendingTimeout.has_value()) { + if (m_pendingTimeout.has_value()) { r->error(WP_COMMIT_TIMER_V1_ERROR_TIMESTAMP_EXISTS, "Timestamp is already set"); return; } - const auto delay = Time::till({.tv_sec = (((uint64_t)tvHi) << 32) | (uint64_t)tvLo, .tv_nsec = tvNsec}); + timespec ts; + ts.tv_sec = (((uint64_t)tvHi) << 32) | (uint64_t)tvLo; + ts.tv_nsec = tvNsec; - if (delay.count() <= 0) { - m_surface->m_pending.pendingTimeout.reset(); + 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(); } else - m_surface->m_pending.pendingTimeout = delay; + m_pendingTimeout = TIME - TIME_NOW; }); m_listeners.surfaceStateCommit = m_surface->m_events.stateCommit2.listen([this](auto state) { - if (!state || !state->pendingTimeout.has_value() || !m_surface || m_surface->isTearing()) + if (!m_pendingTimeout.has_value()) return; m_surface->m_stateQueue.lock(state, LOCK_REASON_TIMER); - if (!state->timer) { - state->timer = makeShared( - state->pendingTimeout, - [surface = m_surface, state](SP self, void* data) { - if (!surface || !state) + if (!m_timerPresent) { + m_timerPresent = true; + timer = makeShared( + m_pendingTimeout, + [this](SP self, void* data) { + if (!m_surface) return; - surface->m_stateQueue.unlock(state, LOCK_REASON_TIMER); + m_surface->m_stateQueue.unlockFirst(LOCK_REASON_TIMER); }, nullptr); - g_pEventLoopManager->addTimer(state->timer); } else - state->timer->updateTimeout(state->pendingTimeout); + timer->updateTimeout(m_pendingTimeout); - state->pendingTimeout.reset(); + m_pendingTimeout.reset(); }); } +CCommitTimerResource::~CCommitTimerResource() { + ; +} + bool CCommitTimerResource::good() { return m_resource->resource(); } diff --git a/src/protocols/CommitTiming.hpp b/src/protocols/CommitTiming.hpp index b5a1de93..e79face8 100644 --- a/src/protocols/CommitTiming.hpp +++ b/src/protocols/CommitTiming.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include "WaylandProtocol.hpp" #include "commit-timing-v1.hpp" @@ -13,12 +14,16 @@ class CEventLoopTimer; class CCommitTimerResource { public: CCommitTimerResource(UP&& resource_, SP surface); + ~CCommitTimerResource(); bool good(); private: - UP m_resource; - WP m_surface; + UP m_resource; + WP m_surface; + bool m_timerPresent = false; + std::optional m_pendingTimeout; + SP timer; struct { CHyprSignalListener surfaceStateCommit; diff --git a/src/protocols/ContentType.cpp b/src/protocols/ContentType.cpp index acae218c..7c8fdbc3 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([this](CWpContentTypeManagerV1* r) { PROTO::contentType->destroyResource(this); }); + m_resource->setDestroy([](CWpContentTypeManagerV1* r) {}); 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_contentType) { + if (SURF->m_colorManagement) { 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 4fa3152d..2af72a4d 100644 --- a/src/protocols/ExtWorkspace.cpp +++ b/src/protocols/ExtWorkspace.cpp @@ -1,8 +1,9 @@ #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" @@ -26,11 +27,8 @@ CExtWorkspaceGroupResource::CExtWorkspaceGroupResource(WPm_name); - if (auto resources = output->outputResourcesFrom(m_resource->client()); !resources.empty()) { - for (const auto& r : resources) { - m_resource->sendOutputEnter(r->getResource()->resource()); - } - } + if (auto resource = output->outputResourceFrom(m_resource->client())) + m_resource->sendOutputEnter(resource->getResource()->resource()); m_listeners.outputBound = output->m_events.outputBound.listen([this](const SP& output) { if (output->client() == m_resource->client()) @@ -296,13 +294,17 @@ 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 = Event::bus()->m_events.workspace.created.listen([this](PHLWORKSPACEREF workspace) { + static auto P1 = g_pHookSystem->hookDynamic("createWorkspace", [this](void* self, SCallbackInfo& info, std::any data) { + auto workspace = std::any_cast(data)->m_self.lock(); + for (auto const& m : m_managers) { - m->onWorkspaceCreated(workspace.lock()); + m->onWorkspaceCreated(workspace); } }); - static auto P2 = Event::bus()->m_events.monitor.added.listen([this](PHLMONITOR monitor) { + static auto P2 = g_pHookSystem->hookDynamic("monitorAdded", [this](void* self, SCallbackInfo& info, std::any data) { + auto monitor = std::any_cast(data); + for (auto const& m : m_managers) { m->onMonitorCreated(monitor); } diff --git a/src/protocols/Fifo.cpp b/src/protocols/Fifo.cpp index 355644d9..d9f873c9 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,8 +18,7 @@ CFifoResource::CFifoResource(UP&& resource_, SP s return; } - m_surface->m_pending.barrierSet = true; - m_surface->m_pending.updated.bits.fifo = true; + m_pending.barrierSet = true; }); m_resource->setWaitBarrier([this](CWpFifoV1* r) { @@ -28,37 +27,49 @@ CFifoResource::CFifoResource(UP&& resource_, SP s 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); - + if (!m_pending.barrierSet) return; - } - m_surface->m_pending.surfaceLocked = true; + m_pending.surfaceLocked = true; }); m_listeners.surfaceStateCommit = m_surface->m_events.stateCommit.listen([this](auto state) { - if (!state || !state->surfaceLocked) + if (!m_pending.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. - // unlock on timeout instead? - if (!state->fifoScheduled) - state->fifoScheduled = checkMonitors(*PPEND); + if (m_surface->m_enteredOutputs.empty() && m_surface->m_hlSurface) { + for (auto& m : g_pCompositor->m_monitors) { + if (!m || !m->m_enabled) + continue; - if (!state->fifoScheduled) - return; + 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); + } + } // only lock once its mapped. if (m_surface->m_mapped) m_surface->m_stateQueue.lock(state, LOCK_REASON_FIFO); + + m_pending = {}; }); } @@ -71,41 +82,9 @@ 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; @@ -153,7 +132,9 @@ bool CFifoManagerResource::good() { } CFifoProtocol::CFifoProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { - static auto P = Event::bus()->m_events.monitor.added.listen([this](PHLMONITOR M) { + static auto P = g_pHookSystem->hookDynamic("monitorAdded", [this](void* self, SCallbackInfo& info, std::any param) { + auto M = std::any_cast(param); + 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 5b143f79..8551e78c 100644 --- a/src/protocols/Fifo.hpp +++ b/src/protocols/Fifo.hpp @@ -21,12 +21,18 @@ 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 baabda7c..5515d2fb 100644 --- a/src/protocols/ForeignToplevel.cpp +++ b/src/protocols/ForeignToplevel.cpp @@ -1,6 +1,6 @@ #include "ForeignToplevel.hpp" #include "../Compositor.hpp" -#include "../event/EventBus.hpp" +#include "../managers/HookSystemManager.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(); }); } - auto newHandle = PROTO::foreignToplevel->m_handles.emplace_back( + const 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("{:x}", pWindow->m_stableID); + const auto IDENTIFIER = std::format("{:08x}->{:016x}", sc(rc(this) & 0xFFFFFFFF), rc(pWindow.get())); 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.emplace_back(std::move(newHandle)); + m_handles.push_back(NEWHANDLE); } SP CForeignToplevelList::handleForWindow(PHLWINDOW pWindow) { @@ -123,7 +123,9 @@ bool CForeignToplevelList::good() { } CForeignToplevelProtocol::CForeignToplevelProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { - static auto P = Event::bus()->m_events.window.open.listen([this](PHLWINDOW window) { + static auto P = g_pHookSystem->hookDynamic("openWindow", [this](void* self, SCallbackInfo& info, std::any data) { + auto window = std::any_cast(data); + if (!windowValidForForeign(window)) return; @@ -132,7 +134,9 @@ CForeignToplevelProtocol::CForeignToplevelProtocol(const wl_interface* iface, co } }); - static auto P1 = Event::bus()->m_events.window.close.listen([this](PHLWINDOW window) { + static auto P1 = g_pHookSystem->hookDynamic("closeWindow", [this](void* self, SCallbackInfo& info, std::any data) { + auto window = std::any_cast(data); + if (!windowValidForForeign(window)) return; @@ -141,7 +145,9 @@ CForeignToplevelProtocol::CForeignToplevelProtocol(const wl_interface* iface, co } }); - static auto P2 = Event::bus()->m_events.window.title.listen([this](PHLWINDOW window) { + static auto P2 = g_pHookSystem->hookDynamic("windowTitle", [this](void* self, SCallbackInfo& info, std::any data) { + auto window = std::any_cast(data); + if (!windowValidForForeign(window)) return; diff --git a/src/protocols/ForeignToplevel.hpp b/src/protocols/ForeignToplevel.hpp index 0ff74e75..f0188292 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 56591261..5e18483d 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,23 +154,17 @@ 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 OLDRESOURCES = PROTO::outputs.at(PLASTMONITOR->m_name)->outputResourcesFrom(CLIENT); + const auto OLDRESOURCE = PROTO::outputs.at(PLASTMONITOR->m_name)->outputResourceFrom(CLIENT); - if LIKELY (!OLDRESOURCES.empty()) { - for (const auto& r : OLDRESOURCES) { - m_resource->sendOutputLeave(r->getResource()->resource()); - } - } + if LIKELY (OLDRESOURCE) + m_resource->sendOutputLeave(OLDRESOURCE->getResource()->resource()); } if (PROTO::outputs.contains(pMonitor->m_name)) { - const auto NEWRESOURCES = PROTO::outputs.at(pMonitor->m_name)->outputResourcesFrom(CLIENT); + const auto NEWRESOURCE = PROTO::outputs.at(pMonitor->m_name)->outputResourceFrom(CLIENT); - if LIKELY (!NEWRESOURCES.empty()) { - for (const auto& r : NEWRESOURCES) { - m_resource->sendOutputEnter(r->getResource()->resource()); - } - } + if LIKELY (NEWRESOURCE) + m_resource->sendOutputEnter(NEWRESOURCE->getResource()->resource()); } m_lastMonitorID = pMonitor->m_id; @@ -343,57 +337,70 @@ bool CForeignToplevelWlrManager::good() { } CForeignToplevelWlrProtocol::CForeignToplevelWlrProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { - static auto P = Event::bus()->m_events.window.open.listen([this](PHLWINDOW window) { - if (!windowValidForForeign(window)) + 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)) return; for (auto const& m : m_managers) { - m->onMap(window); + m->onMap(PWINDOW); } }); - static auto P1 = Event::bus()->m_events.window.close.listen([this](PHLWINDOW window) { - if (!windowValidForForeign(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)) return; for (auto const& m : m_managers) { - m->onUnmap(window); + m->onUnmap(PWINDOW); } }); - static auto P2 = Event::bus()->m_events.window.title.listen([this](PHLWINDOW window) { - if (!windowValidForForeign(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)) return; for (auto const& m : m_managers) { - m->onTitle(window); + m->onTitle(PWINDOW); } }); - static auto P3 = Event::bus()->m_events.window.active.listen([this](PHLWINDOW window, Desktop::eFocusReason reason) { - if (window && !windowValidForForeign(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)) return; for (auto const& m : m_managers) { - m->onNewFocus(window); + m->onNewFocus(PWINDOW); } }); - static auto P4 = Event::bus()->m_events.window.moveToWorkspace.listen([this](PHLWINDOW window, PHLWORKSPACE ws) { - if (!ws) + 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) return; for (auto const& m : m_managers) { - m->onMoveMonitor(window, ws->m_monitor.lock()); + m->onMoveMonitor(PWINDOW, PWORKSPACE->m_monitor.lock()); } }); - static auto P5 = Event::bus()->m_events.window.fullscreen.listen([this](PHLWINDOW window) { - if (!windowValidForForeign(window)) + 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)) return; for (auto const& m : m_managers) { - m->onFullscreen(window); + m->onFullscreen(PWINDOW); } }); } diff --git a/src/protocols/GammaControl.cpp b/src/protocols/GammaControl.cpp index c28b881f..2517c754 100644 --- a/src/protocols/GammaControl.cpp +++ b/src/protocols/GammaControl.cpp @@ -56,12 +56,6 @@ 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 deleted file mode 100644 index 9f54533e..00000000 --- a/src/protocols/ImageCaptureSource.cpp +++ /dev/null @@ -1,135 +0,0 @@ -#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 deleted file mode 100644 index 47580dd2..00000000 --- a/src/protocols/ImageCaptureSource.hpp +++ /dev/null @@ -1,73 +0,0 @@ -#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 deleted file mode 100644 index eca3c939..00000000 --- a/src/protocols/ImageCopyCapture.cpp +++ /dev/null @@ -1,516 +0,0 @@ -#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 deleted file mode 100644 index b8cfa1e8..00000000 --- a/src/protocols/ImageCopyCapture.hpp +++ /dev/null @@ -1,133 +0,0 @@ -#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 f16c8c56..4f59e4b3 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,8 +26,6 @@ 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; @@ -37,17 +35,6 @@ 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) { @@ -69,9 +56,6 @@ 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; @@ -163,17 +147,10 @@ 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 = modifier; + m_attrs->modifier = (sc(modHi) << 32) | modLo; }); m_resource->setCreate([this](CZwpLinuxBufferParamsV1* r, int32_t w, int32_t h, uint32_t fmt, zwpLinuxBufferParamsV1Flags flags) { @@ -188,13 +165,6 @@ 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); @@ -412,7 +382,8 @@ CLinuxDMABUFResource::CLinuxDMABUFResource(UP&& resource_) : } }); - sendMods(); + if (m_resource->version() < 4) + sendMods(); } bool CLinuxDMABUFResource::good() { @@ -421,20 +392,22 @@ bool CLinuxDMABUFResource::good() { void CLinuxDMABUFResource::sendMods() { for (auto const& fmt : PROTO::linuxDma->m_formatTable->m_rendererTranche.formats) { - 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); + 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; } + + // 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 = Event::bus()->m_events.ready.listen([this] { + static auto P = g_pHookSystem->hookDynamic("ready", [this](void* self, SCallbackInfo& info, std::any d) { int rendererFD = g_pCompositor->m_drmRenderNode.fd >= 0 ? g_pCompositor->m_drmRenderNode.fd : g_pCompositor->m_drm.fd; auto dev = devIDFromFD(rendererFD); @@ -467,29 +440,22 @@ CLinuxDMABufV1Protocol::CLinuxDMABufV1Protocol(const wl_interface* iface, const tches.emplace_back(std::make_pair<>(mon, tranche)); } - 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(), + 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(), }; - m_formatTable->m_monitorTranches.emplace_back(std::make_pair<>(mon, tranche)); + m_formatTable->m_monitorTranches.emplace_back(std::make_pair<>(pMonitor, tranche)); resetFormatTable(); }); - 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; }); + 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; }); 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); @@ -641,7 +607,3 @@ 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 b1d59155..296ef04d 100644 --- a/src/protocols/LinuxDMABUF.hpp +++ b/src/protocols/LinuxDMABUF.hpp @@ -113,7 +113,6 @@ 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 f85578e2..57d39371 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 = Event::bus()->m_events.monitor.layoutChanged.listen([this] { + static auto P = g_pHookSystem->hookDynamic("monitorLayoutChanged", [this](void* self, SCallbackInfo& info, std::any param) { updateAllOutputs(); sendPendingSuccessEvents(); }); diff --git a/src/protocols/PresentationTime.cpp b/src/protocols/PresentationTime.cpp index 456ad724..82c4b1eb 100644 --- a/src/protocols/PresentationTime.cpp +++ b/src/protocols/PresentationTime.cpp @@ -1,7 +1,7 @@ #include "PresentationTime.hpp" #include #include "../helpers/Monitor.hpp" -#include "../event/EventBus.hpp" +#include "../managers/HookSystemManager.hpp" #include "core/Compositor.hpp" #include "core/Output.hpp" #include @@ -40,45 +40,46 @@ bool CPresentationFeedback::good() { return m_resource->resource(); } -void CPresentationFeedback::sendQueued(WP data, const timespec& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags) { +void CPresentationFeedback::sendQueued(WP data, const Time::steady_tp& 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 outputResources = PROTO::outputs.at(data->m_monitor->m_name)->outputResourcesFrom(client); !outputResources.empty()) { - for (const auto& r : outputResources) { - m_resource->sendSyncOutput(r->getResource()->resource()); - } - } + if LIKELY (auto outputResource = PROTO::outputs.at(data->m_monitor->m_name)->outputResourceFrom(client); outputResource) + m_resource->sendSyncOutput(outputResource->getResource()->resource()); } - 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; + 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; - time_t tv_sec = 0; - if (sizeof(time_t) > 4) - tv_sec = when.tv_sec >> 32; + const auto TIMESPEC = Time::toTimespec(when); - uint32_t refreshNs = m_resource->version() == 1 && data->m_monitor->m_vrrActive && data->m_monitor->m_output->vrrCapable ? 0 : untilRefreshNs; + time_t tv_sec = 0; + if (sizeof(time_t) > 4) + tv_sec = TIMESPEC.tv_sec >> 32; - m_resource->sendPresented(sc(tv_sec), sc(when.tv_sec & 0xFFFFFFFF), sc(when.tv_nsec), refreshNs, sc(seq >> 32), + 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), 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 = 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; }); }); + 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; }); + }); } void CPresentationProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) { @@ -87,7 +88,6 @@ 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 timespec& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags) { +void CPresentationProtocol::onPresented(PHLMONITOR pMonitor, const Time::steady_tp& 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 caf63ace..c348c175 100644 --- a/src/protocols/PresentationTime.hpp +++ b/src/protocols/PresentationTime.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include "WaylandProtocol.hpp" @@ -38,7 +37,7 @@ class CPresentationFeedback { bool good(); - void sendQueued(WP data, const timespec& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags); + void sendQueued(WP data, const Time::steady_tp& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags); private: UP m_resource; @@ -54,7 +53,7 @@ class CPresentationProtocol : public IWaylandProtocol { virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id); - void onPresented(PHLMONITOR pMonitor, const timespec& when, uint32_t untilRefreshNs, uint64_t seq, uint32_t reportedFlags); + void onPresented(PHLMONITOR pMonitor, const Time::steady_tp& 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 825939ef..c02b759c 100644 --- a/src/protocols/Screencopy.cpp +++ b/src/protocols/Screencopy.cpp @@ -1,12 +1,431 @@ #include "Screencopy.hpp" -#include "../managers/screenshare/ScreenshareManager.hpp" -#include "core/Output.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 "core/Output.hpp" +#include "types/WLBuffer.hpp" #include "types/Buffer.hpp" +#include "ColorManagement.hpp" #include "../helpers/Format.hpp" #include "../helpers/time/Time.hpp" +#include "XDGShell.hpp" -using namespace Screenshare; +#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); +} CScreencopyClient::CScreencopyClient(SP resource_) : m_resource(resource_) { if UNLIKELY (!good()) @@ -15,27 +434,18 @@ 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) { captureOutput(frame, overlayCursor, output, {}); }); + [this](CZwlrScreencopyManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, wl_resource* output) { this->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) { captureOutput(frame, overlayCursor, output, {x, y, w, h}); }); + int32_t h) { this->captureOutput(frame, overlayCursor, output, {x, y, w, h}); }); - m_savedClient = m_resource->client(); + m_lastMeasure.reset(); + m_lastFrame.reset(); + m_tickCallback = g_pHookSystem->hookDynamic("tick", [&](void* self, SCallbackInfo& info, std::any data) { onTick(); }); } 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), session, !!overlayCursor_)); + makeShared(makeShared(m_resource->client(), m_resource->version(), frame), overlayCursor_, output, box)); if (!FRAME->good()) { LOGM(Log::ERR, "Couldn't alloc frame for sharing! (no memory)"); @@ -44,122 +454,38 @@ void CScreencopyClient::captureOutput(uint32_t frame, int32_t overlayCursor_, wl return; } - FRAME->m_client = m_self; 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; + } } bool CScreencopyClient::good() { - return m_resource && m_resource->resource(); + return m_resource->resource(); } -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(); +wl_client* CScreencopyClient::client() { + return m_resource ? m_resource->client() : nullptr; } CScreencopyProtocol::CScreencopyProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { @@ -182,10 +508,64 @@ void CScreencopyProtocol::bindManager(wl_client* client, void* data, uint32_t ve } void CScreencopyProtocol::destroyResource(CScreencopyClient* client) { - std::erase_if(m_frames, [&](const auto& other) { return other->m_client.get() == 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; }); } 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 b73c090d..54b0d28c 100644 --- a/src/protocols/Screencopy.hpp +++ b/src/protocols/Screencopy.hpp @@ -1,32 +1,50 @@ #pragma once -#include "WaylandProtocol.hpp" -#include "wlr-screencopy-unstable-v1.hpp" - -#include "../helpers/time/Time.hpp" +#include "../defines.hpp" #include "./types/Buffer.hpp" -#include +#include "wlr-screencopy-unstable-v1.hpp" +#include "WaylandProtocol.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 class CMonitor; class IHLBuffer; -namespace Screenshare { - class CScreenshareSession; - class CScreenshareFrame; + +enum eClientOwners { + CLIENT_SCREENCOPY = 0, + CLIENT_TOPLEVEL_EXPORT }; class CScreencopyClient { public: CScreencopyClient(SP resource_); + ~CScreencopyClient(); - bool good(); + bool good(); + wl_client* client(); + + WP m_self; + eClientOwners m_clientOwner = CLIENT_SCREENCOPY; + + CTimer m_lastFrame; + int m_frameCounter = 0; private: SP m_resource; - WP m_self; - wl_client* m_savedClient = nullptr; + int m_framesInLastHalfSecond = 0; + CTimer m_lastMeasure; + bool m_sentScreencast = false; + + SP m_tickCallback; + void onTick(); void captureOutput(uint32_t frame, int32_t overlayCursor, wl_resource* output, CBox box); @@ -35,27 +53,38 @@ class CScreencopyClient { class CScreencopyFrame { public: - CScreencopyFrame(SP resource, WP session, bool overlayCursor); + CScreencopyFrame(SP resource, int32_t overlay_cursor, wl_resource* output, CBox box); - bool good(); + bool good(); + + WP m_self; + WP m_client; private: - SP m_resource; - WP m_self; - WP m_client; + SP m_resource; - WP m_session; - UP m_frame; + PHLMONITORREF m_monitor; + bool m_overlayCursor = false; + bool m_withDamage = false; - CHLBufferReference m_buffer; - Time::steady_tp m_timestamp; - bool m_overlayCursor = true; + CHLBufferReference m_buffer; + bool m_bufferDMA = false; + uint32_t m_shmFormat = 0; + uint32_t m_dmabufFormat = 0; + int m_shmStride = 0; + CBox m_box = {}; - // - void shareFrame(CZwlrScreencopyFrameV1* pFrame, wl_resource* buffer, bool withDamage); + // 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(); friend class CScreencopyProtocol; - friend class CScreencopyClient; }; class CScreencopyProtocol : public IWaylandProtocol { @@ -66,10 +95,21 @@ 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 c32379a3..51c3551c 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 = g_pHyprRenderer->createTexture(DRM_FORMAT_ARGB8888, rc(&m_color), 4, Vector2D{1, 1}); + m_texture = makeShared(DRM_FORMAT_ARGB8888, rc(&m_color), 4, Vector2D{1, 1}); m_resource = CWLBufferResource::create(makeShared(client, 1, id)); - m_success = m_texture->ok(); + m_success = m_texture->m_texID; size = {1, 1}; diff --git a/src/protocols/TearingControl.cpp b/src/protocols/TearingControl.cpp index 3fd346a7..685c84a7 100644 --- a/src/protocols/TearingControl.cpp +++ b/src/protocols/TearingControl.cpp @@ -1,12 +1,13 @@ #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 = Event::bus()->m_events.window.destroy.listen([this](PHLWINDOW window) { onWindowDestroy(window); }); + static auto P = + g_pHookSystem->hookDynamic("destroyWindow", [this](void* self, SCallbackInfo& info, std::any param) { this->onWindowDestroy(std::any_cast(param)); }); } 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 d7ba7519..9a97f934 100644 --- a/src/protocols/ToplevelExport.cpp +++ b/src/protocols/ToplevelExport.cpp @@ -1,14 +1,19 @@ #include "ToplevelExport.hpp" #include "../Compositor.hpp" #include "ForeignToplevelWlr.hpp" -#include "../managers/screenshare/ScreenshareManager.hpp" +#include "../managers/PointerManager.hpp" +#include "../managers/SeatManager.hpp" +#include "types/WLBuffer.hpp" +#include "types/Buffer.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; @@ -16,21 +21,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) { - captureToplevel(frame, overlayCursor, g_pCompositor->getWindowFromHandle(handle)); + this->captureToplevel(pMgr, frame, overlayCursor, g_pCompositor->getWindowFromHandle(handle)); }); m_resource->setCaptureToplevelWithWlrToplevelHandle([this](CHyprlandToplevelExportManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, wl_resource* handle) { - captureToplevel(frame, overlayCursor, PROTO::foreignToplevelWlr->windowFromHandleResource(handle)); + this->captureToplevel(pMgr, frame, overlayCursor, PROTO::foreignToplevelWlr->windowFromHandleResource(handle)); }); - m_savedClient = m_resource->client(); + m_lastMeasure.reset(); + m_lastFrame.reset(); + m_tickCallback = g_pHookSystem->hookDynamic("tick", [&](void* self, SCallbackInfo& info, std::any data) { onTick(); }); } -void CToplevelExportClient::captureToplevel(uint32_t frame, int32_t overlayCursor_, PHLWINDOW handle) { - auto session = Screenshare::mgr()->getManagedSession(m_resource->client(), handle); - +void CToplevelExportClient::captureToplevel(CHyprlandToplevelExportManagerV1* pMgr, uint32_t frame, int32_t overlayCursor_, PHLWINDOW handle) { // create a frame const auto FRAME = PROTO::toplevelExport->m_frames.emplace_back( - makeShared(makeShared(m_resource->client(), m_resource->version(), frame), session, !!overlayCursor_)); + makeShared(makeShared(m_resource->client(), m_resource->version(), frame), overlayCursor_, handle)); if UNLIKELY (!FRAME->good()) { LOGM(Log::ERR, "Couldn't alloc frame for sharing! (no memory)"); @@ -39,116 +44,349 @@ void CToplevelExportClient::captureToplevel(uint32_t frame, int32_t overlayCurso return; } - FRAME->m_client = m_self; 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; + } } bool CToplevelExportClient::good() { - return m_resource && m_resource->resource(); + return m_resource->resource(); } -CToplevelExportFrame::CToplevelExportFrame(SP resource_, WP session, bool overlayCursor) : - m_resource(resource_), m_session(session) { +CToplevelExportFrame::CToplevelExportFrame(SP resource_, int32_t overlayCursor_, PHLWINDOW pWindow_) : m_resource(resource_), m_window(pWindow_) { if UNLIKELY (!good()) 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) { shareFrame(res, !!ignoreDamage); }); + m_cursorOverlayRequested = !!overlayCursor_; - m_frame = m_session->nextFrame(overlayCursor); - - auto formats = m_session->allowedFormats(); - if (formats.empty()) { - LOGM(Log::ERR, "No format supported by renderer in toplevel export protocol"); + if UNLIKELY (!m_window) { + LOGM(Log::ERR, "Client requested sharing of window handle {:x} which does not exist!", m_window); m_resource->sendFailed(); return; } - DRMFormat format = formats.at(0); - auto bufSize = m_frame->bufferSize(); + 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; + } - 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_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); }); - if LIKELY (format != DRM_FORMAT_INVALID) - m_resource->sendLinuxDmabuf(format, bufSize.x, bufSize.y); + const auto PMONITOR = m_window->m_monitor.lock(); + + 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"); + 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; + } + + m_dmabufFormat = g_pHyprOpenGL->getPreferredReadFormat(PMONITOR); + + 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); m_resource->sendBufferDone(); } -bool CToplevelExportFrame::good() { - return m_resource && m_resource->resource(); -} - -void CToplevelExportFrame::shareFrame(wl_resource* buffer, bool ignoreDamage) { +void CToplevelExportFrame::copy(CHyprlandToplevelExportFrameV1* pFrame, wl_resource* buffer_, int32_t ignoreDamage) { if UNLIKELY (!good()) { - LOGM(Log::ERR, "No frame in shareFrame??"); + LOGM(Log::ERR, "No frame in copyFrame??"); return; } - if UNLIKELY (m_session.expired() || !m_session->monitor()) { - LOGM(Log::ERR, "Session stopped for frame {:x}", (uintptr_t)this); + if UNLIKELY (!validMapped(m_window)) { + LOGM(Log::ERR, "Client requested sharing of window handle {:x} which is gone!", 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 (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"); - m_resource->sendFailed(); + PROTO::toplevelExport->destroyResource(this); 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(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer"); - m_resource->sendFailed(); - return; - } + if (auto attrs = PBUFFER->m_buffer->dmabuf(); attrs.success) { + m_bufferDMA = true; - const auto& PBUFFER = PBUFFERRES->m_buffer.lock(); - - if (ignoreDamage) - g_pHyprRenderer->damageMonitor(m_session->monitor()); - - auto error = m_frame->share(PBUFFER, {}, [this, ignoreDamage, self = m_self](eScreenshareResult result) { - if (self.expired() || !good()) + if (attrs.format != m_dmabufFormat) { + m_resource->error(HYPRLAND_TOPLEVEL_EXPORT_FRAME_V1_ERROR_INVALID_BUFFER, "invalid buffer format"); + PROTO::toplevelExport->destroyResource(this); 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; + } 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); + return; } + + m_buffer = CHLBufferReference(PBUFFER->m_buffer.lock()); + + m_ignoreDamage = ignoreDamage; + + 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(); + return; + } + } + + 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) { @@ -167,10 +405,69 @@ void CToplevelExportProtocol::bindManager(wl_client* client, void* data, uint32_ } void CToplevelExportProtocol::destroyResource(CToplevelExportClient* client) { - std::erase_if(m_frames, [&](const auto& other) { return other->m_client.get() == 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; }); } 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 5d30f09b..44704d84 100644 --- a/src/protocols/ToplevelExport.hpp +++ b/src/protocols/ToplevelExport.hpp @@ -1,59 +1,72 @@ #pragma once -#include "WaylandProtocol.hpp" +#include "../defines.hpp" #include "hyprland-toplevel-export-v1.hpp" - +#include "WaylandProtocol.hpp" +#include "Screencopy.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(); + bool good(); + + WP m_self; + eClientOwners m_clientOwner = CLIENT_TOPLEVEL_EXPORT; + + CTimer m_lastFrame; + int m_frameCounter = 0; private: SP m_resource; - WP m_self; - wl_client* m_savedClient = nullptr; + int m_framesInLastHalfSecond = 0; + CTimer m_lastMeasure; + bool m_sentScreencast = false; - void captureToplevel(uint32_t frame, int32_t overlayCursor, PHLWINDOW handle); + SP m_tickCallback; + void onTick(); + + void captureToplevel(CHyprlandToplevelExportManagerV1* pMgr, uint32_t frame, int32_t overlayCursor, PHLWINDOW handle); friend class CToplevelExportProtocol; }; class CToplevelExportFrame { public: - CToplevelExportFrame(SP resource, WP session, bool overlayCursor); + CToplevelExportFrame(SP resource_, int32_t overlayCursor, PHLWINDOW pWindow); - bool good(); + bool good(); + + WP m_self; + WP m_client; private: - SP m_resource; - WP m_self; - WP m_client; + SP m_resource; - WP m_session; - UP m_frame; + PHLWINDOW m_window; + bool m_cursorOverlayRequested = false; + bool m_ignoreDamage = false; - CHLBufferReference m_buffer; - Time::steady_tp m_timestamp; + CHLBufferReference m_buffer; + bool m_bufferDMA = false; + uint32_t m_shmFormat = 0; + uint32_t m_dmabufFormat = 0; + int m_shmStride = 0; + CBox m_box = {}; - // - void shareFrame(wl_resource* buffer, bool ignoreDamage); + 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; friend class CToplevelExportProtocol; - friend class CToplevelExportClient; }; class CToplevelExportProtocol : IWaylandProtocol { @@ -69,9 +82,15 @@ 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 5c187c7d..5f1c9798 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_v == 1 && std::is_same_v) { \ + if constexpr (std::tuple_size::value == 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 3553a74b..8835d4b5 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 "../event/EventBus.hpp" +#include "../managers/HookSystemManager.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 = Event::bus()->m_events.monitor.layoutChanged.listen([this] { updateAllOutputs(); }); - static auto P2 = Event::bus()->m_events.config.reloaded.listen([this] { updateAllOutputs(); }); + 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(); }); } 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 06f430b9..b9e677af 100644 --- a/src/protocols/core/Compositor.cpp +++ b/src/protocols/core/Compositor.cpp @@ -286,20 +286,16 @@ void CWLSurfaceResource::enter(PHLMONITOR monitor) { return; } - auto outputs = PROTO::outputs.at(monitor->m_name)->outputResourcesFrom(m_client); + auto output = PROTO::outputs.at(monitor->m_name)->outputResourceFrom(m_client); - if UNLIKELY (outputs.empty() || std::ranges::all_of(outputs, [](const auto& o) { return !o->getResource() || !o->getResource()->resource(); })) { + if UNLIKELY (!output || !output->getResource() || !output->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); - for (const auto& o : outputs) { - if (!o->getResource() || !o->getResource()->resource()) - continue; - m_resource->sendEnter(o->getResource().get()); - } + m_resource->sendEnter(output->getResource().get()); m_events.enter.emit(monitor); } @@ -307,20 +303,16 @@ void CWLSurfaceResource::leave(PHLMONITOR monitor) { if UNLIKELY (std::ranges::find(m_enteredOutputs, monitor) == m_enteredOutputs.end()) return; - auto outputs = PROTO::outputs.at(monitor->m_name)->outputResourcesFrom(m_client); + auto output = PROTO::outputs.at(monitor->m_name)->outputResourceFrom(m_client); - if UNLIKELY (outputs.empty() || std::ranges::all_of(outputs, [](const auto& o) { return !o->getResource() || !o->getResource()->resource(); })) { + if UNLIKELY (!output) { 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); - for (const auto& o : outputs) { - if (!o->getResource() || !o->getResource()->resource()) - continue; - m_resource->sendLeave(o->getResource().get()); - } + m_resource->sendLeave(output->getResource().get()); m_events.leave.emit(monitor); } @@ -508,32 +500,20 @@ void CWLSurfaceResource::scheduleState(WP state) { if (state->updated.bits.acquire) { // wait on acquire point for this surface, from explicit sync protocol - 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); - } + state->acquire.addWaiter([state, whenReadable]() { whenReadable(state, LOCK_REASON_FENCE); }); } else if (state->buffer && state->buffer->isSynchronous()) { // synchronous (shm) buffers can be read immediately - m_stateQueue.unlock(state, LOCK_REASON_FENCE); + m_stateQueue.unlock(state); } 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.tryProcess(); + m_stateQueue.unlock(state); } } 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); @@ -634,30 +614,6 @@ 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; @@ -702,14 +658,8 @@ 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 37ca51b7..89bfb31b 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 "../../helpers/cm/ColorManagement.hpp" +#include "../types/ColorManagement.hpp" #include "../types/SurfaceRole.hpp" #include "../types/SurfaceState.hpp" @@ -128,7 +128,6 @@ 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 22ccae6c..41f07273 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,26 +586,29 @@ void CWLDataDeviceProtocol::initiateDrag(WP currentSource }); } - 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) { + 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) { LOGM(Log::DEBUG, "Dropping drag on mouseUp"); dropDrag(); } }); - m_dnd.touchUp = Event::bus()->m_events.input.touch.up.listen([this](ITouch::SUpEvent e, Event::SCallbackInfo&) { + m_dnd.touchUp = g_pHookSystem->hookDynamic("touchUp", [this](void* self, SCallbackInfo& info, std::any e) { LOGM(Log::DEBUG, "Dropping drag on touchUp"); dropDrag(); }); - m_dnd.tabletTip = Event::bus()->m_events.input.tablet.tip.listen([this](CTablet::STipEvent e, Event::SCallbackInfo&) { - if (!e.in) { + m_dnd.tabletTip = g_pHookSystem->hookDynamic("tabletTip", [this](void* self, SCallbackInfo& info, std::any e) { + auto E = std::any_cast(e); + if (!E.in) { LOGM(Log::DEBUG, "Dropping drag on tablet tipUp"); dropDrag(); } }); - m_dnd.mouseMove = Event::bus()->m_events.input.mouse.move.listen([this](Vector2D pos, Event::SCallbackInfo&) { + m_dnd.mouseMove = g_pHookSystem->hookDynamic("mouseMove", [this](void* self, SCallbackInfo& info, std::any e) { + auto V = std::any_cast(e); if (m_dnd.focusedDevice && g_pSeatManager->m_state.dndPointerFocus) { auto surf = Desktop::View::CWLSurface::fromResource(g_pSeatManager->m_state.dndPointerFocus.lock()); @@ -617,12 +620,13 @@ void CWLDataDeviceProtocol::initiateDrag(WP currentSource if (!box.has_value()) return; - m_dnd.focusedDevice->sendMotion(Time::millis(Time::steadyNow()), pos - box->pos()); - LOGM(Log::DEBUG, "Drag motion {}", pos - box->pos()); + m_dnd.focusedDevice->sendMotion(Time::millis(Time::steadyNow()), V - box->pos()); + LOGM(Log::DEBUG, "Drag motion {}", V - box->pos()); } }); - m_dnd.touchMove = Event::bus()->m_events.input.touch.motion.listen([this](ITouch::SMotionEvent e, Event::SCallbackInfo&) { + m_dnd.touchMove = g_pHookSystem->hookDynamic("touchMove", [this](void* self, SCallbackInfo& info, std::any e) { + auto E = std::any_cast(e); if (m_dnd.focusedDevice && g_pSeatManager->m_state.dndPointerFocus) { auto surf = Desktop::View::CWLSurface::fromResource(g_pSeatManager->m_state.dndPointerFocus.lock()); @@ -634,8 +638,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 f3717f78..b4ad378f 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 - CHyprSignalListener mouseMove; - CHyprSignalListener mouseButton; - CHyprSignalListener touchUp; - CHyprSignalListener touchMove; - CHyprSignalListener tabletTip; + SP mouseMove; + SP mouseButton; + SP touchUp; + SP touchMove; + SP tabletTip; } m_dnd; void abortDrag(); diff --git a/src/protocols/core/Output.cpp b/src/protocols/core/Output.cpp index dd9c3166..755470e4 100644 --- a/src/protocols/core/Output.cpp +++ b/src/protocols/core/Output.cpp @@ -117,17 +117,15 @@ void CWLOutputProtocol::destroyResource(CWLOutputResource* resource) { PROTO::outputs.erase(m_name); } -std::vector> CWLOutputProtocol::outputResourcesFrom(wl_client* client) { - std::vector> ret; - +SP CWLOutputProtocol::outputResourceFrom(wl_client* client) { for (auto const& r : m_outputs) { if (r->client() != client) continue; - ret.emplace_back(r); + return r; } - return ret; + return nullptr; } void CWLOutputProtocol::remove() { diff --git a/src/protocols/core/Output.hpp b/src/protocols/core/Output.hpp index cf266685..d0c6fb13 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); - std::vector> outputResourcesFrom(wl_client* client); - void sendDone(); + SP outputResourceFrom(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 52015fd8..bfe70a75 100644 --- a/src/protocols/core/Seat.cpp +++ b/src/protocols/core/Seat.cpp @@ -141,10 +141,6 @@ 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 85dc5c39..c30bbd71 100644 --- a/src/protocols/core/Seat.hpp +++ b/src/protocols/core/Seat.hpp @@ -71,7 +71,6 @@ class CWLTouchResource { class CWLPointerResource { public: CWLPointerResource(SP resource_, SP owner_); - ~CWLPointerResource(); bool good(); int version(); @@ -89,10 +88,6 @@ 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 7cca3814..476b58e3 100644 --- a/src/protocols/core/Shm.cpp +++ b/src/protocols/core/Shm.cpp @@ -176,7 +176,6 @@ 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 93bd5d20..ad19cb32 100644 --- a/src/protocols/types/Buffer.cpp +++ b/src/protocols/types/Buffer.cpp @@ -59,10 +59,6 @@ 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(); @@ -74,9 +70,6 @@ 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) @@ -85,16 +78,6 @@ 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 afff11a5..f85670ef 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,13 +49,10 @@ 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 new file mode 100644 index 00000000..5d23d1c9 --- /dev/null +++ b/src/protocols/types/ColorManagement.cpp @@ -0,0 +1,110 @@ +#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/helpers/cm/ColorManagement.hpp b/src/protocols/types/ColorManagement.hpp similarity index 78% rename from src/helpers/cm/ColorManagement.hpp rename to src/protocols/types/ColorManagement.hpp index 0103e2a4..01077704 100644 --- a/src/helpers/cm/ColorManagement.hpp +++ b/src/protocols/types/ColorManagement.hpp @@ -3,11 +3,6 @@ #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 @@ -17,8 +12,6 @@ #define HDR_REF_LUMINANCE 203.0 #define HLG_MAX_LUMINANCE 1000.0 -class ITexture; - namespace NColorManagement { enum eNoShader : uint8_t { CM_NS_DISABLE = 0, @@ -74,6 +67,7 @@ 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}, @@ -82,8 +76,6 @@ 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}, @@ -148,16 +140,7 @@ namespace NColorManagement { }; } - 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); + const SPCPRimaries& getPrimaries(ePrimaries name); class CPrimaries { public: @@ -180,17 +163,22 @@ namespace NColorManagement { }; struct SImageDescription { - static std::expected fromICC(const std::filesystem::path& file); + struct SIccFile { + int fd = -1; + uint32_t length = 0; + uint32_t offset = 0; + bool operator==(const SIccFile& i2) const { + return fd == i2.fd; + } + } icc; - // - std::vector rawICC; + bool windowsScRGB = false; - eTransferFunction transferFunction = CM_TRANSFER_FUNCTION_GAMMA22; - float transferFunctionPower = 1.0f; - bool windowsScRGB = false; + eTransferFunction transferFunction = CM_TRANSFER_FUNCTION_GAMMA22; + float transferFunctionPower = 1.0f; - 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 @@ -214,23 +202,11 @@ 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 { - if (icc.present || d2.icc.present) - return false; - - return windowsScRGB == d2.windowsScRGB && transferFunction == d2.transferFunction && transferFunctionPower == d2.transferFunctionPower && + return icc == d2.icc && 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; @@ -247,9 +223,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: @@ -267,9 +243,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: @@ -286,9 +262,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: @@ -304,48 +280,39 @@ namespace NColorManagement { class CImageDescription { public: static WP from(const SImageDescription& imageDescription); - static WP from(const uint32_t imageDescriptionId); + static WP from(const uint imageDescriptionId); WP with(const SImageDescription::SPCLuminances& luminances) const; const SImageDescription& value() const; - uint32_t id() const; + uint id() const; WP getPrimaries() const; private: CImageDescription(const SImageDescription& imageDescription, const uint imageDescriptionId); - uint32_t m_id = 0; - uint32_t m_primariesId = 0; + uint m_id; + uint m_primariesId; SImageDescription m_imageDescription; }; using PImageDescription = WP; - 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 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 SCRGB_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{ - .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_EXT_LINEAR, .windowsScRGB = true, + .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_EXT_LINEAR, .primariesNameSet = true, .primariesNamed = NColorManagement::CM_PRIMARIES_SRGB, .primaries = NColorPrimaries::BT709, .luminances = {.reference = 203}, }); + ; - static const auto LINEAR_IMAGE_DESCRIPTION = SCRGB_IMAGE_DESCRIPTION; // TODO any reason to use something different? -} +} \ No newline at end of file diff --git a/src/protocols/types/DMABuffer.cpp b/src/protocols/types/DMABuffer.cpp index 86db8ca6..f3c3e067 100644 --- a/src/protocols/types/DMABuffer.cpp +++ b/src/protocols/types/DMABuffer.cpp @@ -13,28 +13,31 @@ 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)); - m_opaque = NFormatUtils::isFormatOpaque(m_attrs.format); - m_texture = g_pHyprRenderer->createTexture(m_attrs, m_opaque); // texture takes ownership of the eglImage + size = m_attrs.size; + m_resource = CWLBufferResource::create(makeShared(client, 1, id)); + auto eglImage = g_pHyprOpenGL->createEGLImage(m_attrs); - if UNLIKELY (!m_texture) { + if UNLIKELY (!eglImage) { Log::logger->log(Log::ERR, "CDMABuffer: failed to import EGLImage, retrying as implicit"); m_attrs.modifier = DRM_FORMAT_MOD_INVALID; - m_texture = g_pHyprRenderer->createTexture(m_attrs, m_opaque); + eglImage = g_pHyprOpenGL->createEGLImage(m_attrs); - if UNLIKELY (!m_texture) { + if UNLIKELY (!eglImage) { Log::logger->log(Log::ERR, "CDMABuffer: failed to import EGLImage"); return; } } - m_success = m_texture->ok(); + 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; 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 da98d3fb..ecead008 100644 --- a/src/protocols/types/SurfaceState.cpp +++ b/src/protocols/types/SurfaceState.cpp @@ -1,7 +1,6 @@ #include "SurfaceState.hpp" #include "helpers/Format.hpp" #include "protocols/types/Buffer.hpp" -#include "render/Renderer.hpp" #include "render/Texture.hpp" Vector2D SSurfaceState::sourceSize() { @@ -35,7 +34,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); @@ -44,7 +43,7 @@ void SSurfaceState::updateSynchronousTexture(SP lastTexture) { texture = lastTexture; texture->update(drmFmt, dataPtr, stride, accumulateBufferDamage()); } else - texture = g_pHyprRenderer->createTexture(drmFmt, dataPtr, stride, bufferSize); + texture = makeShared(drmFmt, dataPtr, stride, bufferSize); } buffer->endDataPtr(); } @@ -64,13 +63,6 @@ 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) { @@ -120,7 +112,4 @@ 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 d5b7e4b9..dd767962 100644 --- a/src/protocols/types/SurfaceState.hpp +++ b/src/protocols/types/SurfaceState.hpp @@ -1,12 +1,10 @@ #pragma once #include "../../helpers/math/Math.hpp" -#include "../../helpers/time/Time.hpp" -#include "../../managers/eventLoop/EventLoopTimer.hpp" #include "../WaylandProtocol.hpp" #include "./Buffer.hpp" -class ITexture; +class CTexture; class CDRMSyncPointState; class CWLCallbackResource; @@ -50,7 +48,6 @@ struct SSurfaceState { bool acquire : 1; bool acked : 1; bool frame : 1; - bool fifo : 1; } bits; } updated; @@ -88,17 +85,8 @@ struct SSurfaceState { eLockReason lockMask = LOCK_REASON_NONE; // texture of surface content, used for rendering - SP texture; - void updateSynchronousTexture(SP lastTexture); - - // fifo - bool barrierSet = false; - bool surfaceLocked = false; - bool fifoScheduled = false; - - // commit timing - std::optional pendingTimeout; - SP timer; + SP texture; + void updateSynchronousTexture(SP lastTexture); // 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 82a04878..c10b556f 100644 --- a/src/protocols/types/SurfaceStateQueue.cpp +++ b/src/protocols/types/SurfaceStateQueue.cpp @@ -21,7 +21,6 @@ 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; @@ -30,7 +29,6 @@ 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; @@ -40,7 +38,6 @@ 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; @@ -68,9 +65,6 @@ 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 8d9db7a5..1841ed20 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); + void unlock(const WP& state, eLockReason reason = LOCK_REASON_NONE); void unlockFirst(eLockReason reason); void tryProcess(); diff --git a/src/render/Framebuffer.cpp b/src/render/Framebuffer.cpp index b2ff7e68..98947297 100644 --- a/src/render/Framebuffer.cpp +++ b/src/render/Framebuffer.cpp @@ -1,30 +1,127 @@ #include "Framebuffer.hpp" +#include "OpenGL.hpp" -IFramebuffer::IFramebuffer(const std::string& name) : m_name(name) {} - -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); - - const bool sizeChanged = (m_size != Vector2D(w, h)); - const bool formatChanged = (format != m_drmFormat); - - if (m_fbAllocated && !sizeChanged && !formatChanged) - return true; - - m_size = {w, h}; - m_drmFormat = format; - m_fbAllocated = internalAlloc(w, h, format); - return m_fbAllocated; +CFramebuffer::CFramebuffer() { + ; } -bool IFramebuffer::isAllocated() { +bool CFramebuffer::alloc(int w, int h, uint32_t drmFormat) { + bool firstAlloc = false; + 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); + + if (drmFormat != m_drmFormat || m_size != Vector2D{w, h}) + release(); + + 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; +} + +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() { return m_fbAllocated && m_tex; } -SP IFramebuffer::getTexture() { +SP CFramebuffer::getTexture() { return m_tex; } -SP IFramebuffer::getStencilTex() { +GLuint CFramebuffer::getFBID() { + return m_fbAllocated ? m_fb : 0; +} + +SP CFramebuffer::getStencilTex() { return m_stencilTex; } diff --git a/src/render/Framebuffer.hpp b/src/render/Framebuffer.hpp index 7e33f227..0e18df5f 100644 --- a/src/render/Framebuffer.hpp +++ b/src/render/Framebuffer.hpp @@ -3,38 +3,32 @@ #include "../defines.hpp" #include "../helpers/Format.hpp" #include "Texture.hpp" -#include -#include -class CHLBufferReference; - -class IFramebuffer { +class CFramebuffer { public: - 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; + CFramebuffer(); + ~CFramebuffer(); + 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(); - - virtual void addStencil(SP tex) = 0; + SP getTexture(); + SP getStencilTex(); + GLuint getFBID(); Vector2D m_size; - DRMFormat m_drmFormat = DRM_FORMAT_INVALID; + DRMFormat m_drmFormat = 0 /* DRM_FORMAT_INVALID */; - protected: - virtual bool internalAlloc(int w, int h, uint32_t format = DRM_FORMAT_ARGB8888) = 0; - - SP m_tex; + private: + SP m_tex; + GLuint m_fb = -1; bool m_fbAllocated = false; - SP m_stencilTex; - std::string m_name; // name for logging + SP m_stencilTex; + + friend class CRenderbuffer; }; diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index 83ad05ca..33e380e1 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -11,7 +10,6 @@ #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" @@ -20,7 +18,8 @@ #include "../protocols/LayerShell.hpp" #include "../protocols/core/Compositor.hpp" #include "../protocols/ColorManagement.hpp" -#include "../helpers/cm/ColorManagement.hpp" +#include "../protocols/types/ColorManagement.hpp" +#include "../managers/HookSystemManager.hpp" #include "../managers/input/InputManager.hpp" #include "../managers/eventLoop/EventLoopManager.hpp" #include "../managers/CursorManager.hpp" @@ -28,11 +27,8 @@ #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" @@ -46,16 +42,19 @@ #include #include #include -#include -#include "ShaderLoader.hpp" -#include "Texture.hpp" #include -#include "gl/GLFramebuffer.hpp" -#include "gl/GLTexture.hpp" +#include "./shaders/Shaders.hpp" using namespace Hyprutils::OS; using namespace NColorManagement; -using namespace Render; + +const std::vector ASSET_PATHS = { +#ifdef DATAROOTDIR + DATAROOTDIR, +#endif + "/usr/share", + "/usr/local/share", +}; static inline void loadGLProc(void* pProc, const char* name) { void* proc = rc(eglGetProcAddress(name)); @@ -160,7 +159,6 @@ 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"); @@ -174,12 +172,6 @@ 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); @@ -304,8 +296,7 @@ CHyprOpenGLImpl::CHyprOpenGLImpl() : m_drmFD(g_pCompositor->m_drmRenderNode.fd > loadGLProc(&m_proc.eglQueryDisplayAttribEXT, "eglQueryDisplayAttribEXT"); } - static const auto GLDEBUG = CConfigValue("debug:gl_debugging"); - if (EGLEXTENSIONS.contains("EGL_KHR_debug") && *GLDEBUG) { + if (EGLEXTENSIONS.contains("EGL_KHR_debug")) { 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, @@ -391,7 +382,7 @@ CHyprOpenGLImpl::CHyprOpenGLImpl() : m_drmFD(g_pCompositor->m_drmRenderNode.fd > initAssets(); - static auto P = Event::bus()->m_events.render.pre.listen([&](PHLMONITOR mon) { preRender(mon); }); + static auto P = g_pHookSystem->hookDynamic("preRender", [&](void* self, SCallbackInfo& info, std::any data) { preRender(std::any_cast(data)); }); RASSERT(eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT), "Couldn't unset current EGL!"); @@ -422,24 +413,26 @@ CHyprOpenGLImpl::CHyprOpenGLImpl() : m_drmFD(g_pCompositor->m_drmRenderNode.fd > #endif }; - static auto P2 = Event::bus()->m_events.input.mouse.button.listen([](IPointer::SButtonEvent e, Event::SCallbackInfo&) { - if (e.state != WL_POINTER_BUTTON_STATE_PRESSED) + 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) return; addLastPressToHistory(g_pInputManager->getMouseCoordsInternal(), g_pInputManager->getClickMode() == CLICKMODE_KILL, false); }); - 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 : ""); + 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 : ""); 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() { @@ -646,7 +639,95 @@ EGLImageKHR CHyprOpenGLImpl::createEGLImage(const Aquamarine::SDMABUFAttrs& attr return image; } -void CHyprOpenGLImpl::beginSimple(PHLMONITOR pMonitor, const CRegion& damage, SP rb, SP fb) { +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) { m_renderData.pMonitor = pMonitor; const GLenum RESETSTATUS = glGetGraphicsResetStatus(); @@ -681,7 +762,6 @@ 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); @@ -699,7 +779,7 @@ void CHyprOpenGLImpl::beginSimple(PHLMONITOR pMonitor, const CRegion& damage, SP pushMonitorTransformEnabled(false); } -void CHyprOpenGLImpl::begin(PHLMONITOR pMonitor, const CRegion& damage_, SP fb, std::optional finalDamage) { +void CHyprOpenGLImpl::begin(PHLMONITOR pMonitor, const CRegion& damage_, CFramebuffer* fb, std::optional finalDamage) { m_renderData.pMonitor = pMonitor; const GLenum RESETSTATUS = glGetGraphicsResetStatus(); @@ -723,8 +803,7 @@ void CHyprOpenGLImpl::begin(PHLMONITOR pMonitor, const CRegion& damage_, SPm_projMatrix; - if (m_monitorRenderResources.contains(pMonitor) && - (!m_monitorRenderResources.at(pMonitor).offloadFB || m_monitorRenderResources.at(pMonitor).offloadFB->m_size != pMonitor->m_pixelSize)) + if (m_monitorRenderResources.contains(pMonitor) && m_monitorRenderResources.at(pMonitor).offloadFB.m_size != pMonitor->m_pixelSize) destroyMonitorResources(pMonitor); m_renderData.pCurrentMonData = &m_monitorRenderResources[pMonitor]; @@ -735,40 +814,23 @@ void CHyprOpenGLImpl::begin(PHLMONITOR pMonitor, const CRegion& damage_, SPm_drmFormat : pMonitor->m_output->state->state().drmFormat; // ensure a framebuffer for the monitor exists - 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(); + if (m_renderData.pCurrentMonData->offloadFB.m_size != pMonitor->m_pixelSize || DRM_FORMAT != m_renderData.pCurrentMonData->offloadFB.m_drmFormat) { + m_renderData.pCurrentMonData->stencilTex->allocate(); - 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); - 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); } - const bool HAS_MIRROR_FB = m_renderData.pCurrentMonData->monitorMirrorFB && m_renderData.pCurrentMonData->monitorMirrorFB->isAllocated(); - const bool NEEDS_COPY_FB = needsACopyFB(m_renderData.pMonitor.lock()); + if (m_renderData.pCurrentMonData->monitorMirrorFB.isAllocated() && m_renderData.pMonitor->m_mirrors.empty()) + m_renderData.pCurrentMonData->monitorMirrorFB.release(); - 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_renderData.damage.set(damage_); + m_renderData.finalDamage.set(finalDamage.value_or(damage_)); m_fakeFrame = fb; @@ -778,8 +840,8 @@ void CHyprOpenGLImpl::begin(PHLMONITOR pMonitor, const CRegion& damage_, SPoffloadFB->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; @@ -793,42 +855,33 @@ 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 LIKELY (m_offloadedFramebuffer) { + if (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 LIKELY (g_pHyprRenderer->m_renderMode == RENDER_MODE_NORMAL && m_renderData.mouseZoomFactor == 1.0f) + if (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 UNLIKELY (m_renderData.mouseZoomFactor != 1.F && m_renderData.mouseZoomUseMouse && *PZOOMDISABLEAA) + if (m_renderData.mouseZoomUseMouse && *PZOOMDISABLEAA) m_renderData.useNearestNeighbor = true; // copy the damaged areas into the mirror buffer - // we can't use the offloadFB for mirroring / ss, as it contains artifacts from blurring - if UNLIKELY (needsACopyFB(m_renderData.pMonitor.lock()) && !m_fakeFrame) + // we can't use the offloadFB for mirroring, as it contains artifacts from blurring + if (!m_renderData.pMonitor->m_mirrors.empty() && !m_fakeFrame) saveBufferForMirror(monbox); m_renderData.outFB->bind(); blend(false); - 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}); + if (m_finalScreenShader.program < 1 && !g_pHyprRenderer->m_crashingInProgress) + renderTexturePrimitive(m_renderData.pCurrentMonData->offloadFB.getTexture(), monbox); + else + renderTexture(m_renderData.pCurrentMonData->offloadFB.getTexture(), monbox, {}); blend(true); @@ -837,24 +890,6 @@ 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; @@ -868,22 +903,14 @@ 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 UNLIKELY (m_renderData.pCurrentMonData->offMainFB && m_renderData.pCurrentMonData->offMainFB->isAllocated()) - m_renderData.pCurrentMonData->offMainFB->release(); + if (m_renderData.pCurrentMonData->offMainFB.isAllocated()) + m_renderData.pCurrentMonData->offMainFB.release(); - static const auto GLDEBUG = CConfigValue("debug:gl_debugging"); + // check for gl errors + const GLenum ERR = glGetError(); - 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); + 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."); } void CHyprOpenGLImpl::setDamage(const CRegion& damage_, std::optional finalDamage) { @@ -891,30 +918,324 @@ void CHyprOpenGLImpl::setDamage(const CRegion& damage_, std::optional f m_renderData.finalDamage.set(finalDamage.value_or(damage_)); } -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", -}; +// 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)); +} -// 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 loadShaderInclude(const std::string& filename, std::map& includes) { + includes.insert({filename, loadShader(filename)}); +} -bool CHyprOpenGLImpl::initShaders(const std::string& path) { - auto shaders = makeShared(); - static const auto PCM = CConfigValue("render:cm_enabled"); +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"); try { - auto shaderLoader = makeUnique(SHADER_INCLUDES, FRAG_SHADERS, path); + std::map includes; + loadShaderInclude("rounding.glsl", includes); + loadShaderInclude("CM.glsl", includes); - shaders->TEXVERTSRC = shaderLoader->process("tex300.vert"); - shaders->TEXVERTSRC320 = shaderLoader->process("tex320.vert"); + shaders->TEXVERTSRC = processShader("tex300.vert", includes); + shaders->TEXVERTSRC320 = processShader("tex320.vert", includes); - m_cmSupported = *PCM; + GLuint prog; - g_pShaderLoader = std::move(shaderLoader); + 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(); } catch (const std::exception& e) { if (!m_shadersInitialized) @@ -935,7 +1256,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; @@ -949,23 +1270,47 @@ void CHyprOpenGLImpl::applyScreenShader(const std::string& path) { std::string fragmentShader((std::istreambuf_iterator(infile)), (std::istreambuf_iterator())); - if (!m_finalScreenShader->createProgram( // - fragmentShader.starts_with("#version 320 es") // do not break existing custom shaders - ? - m_shaders->TEXVERTSRC320 : - m_shaders->TEXVERTSRC, - fragmentShader, true)) { + 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) { // Error will have been sent by now by the underlying cause return; } - if (m_finalScreenShader->getUniformLocation(SHADER_TIME) != -1) - m_finalScreenShader->setInitialTime(m_globalTimer.getSeconds()); + 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"); static auto uniformRequireNoDamage = [this](eShaderUniform uniform, const std::string& name) { if (*PDT == 0) return; - if (m_finalScreenShader->getUniformLocation(uniform) == -1) + if (m_finalScreenShader.uniformLocations[uniform] == -1) return; // The screen shader uses the uniform @@ -989,6 +1334,8 @@ 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) { @@ -996,20 +1343,22 @@ void CHyprOpenGLImpl::clear(const CHyprColor& color) { TRACY_GPU_ZONE("RenderClear"); - GLCALL(glClearColor(color.r, color.g, color.b, color.a)); + glClearColor(color.r, color.g, color.b, color.a); if (!m_renderData.damage.empty()) { m_renderData.damage.forEachRect([this](const auto& RECT) { - scissor(&RECT, m_renderData.transformDamage); + scissor(&RECT); glClear(GL_COLOR_BUFFER_BIT); }); } + + scissor(nullptr); } void CHyprOpenGLImpl::blend(bool enabled) { if (enabled) { setCapStatus(GL_BLEND, true); - GLCALL(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); // everything is premultiplied + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // everything is premultiplied } else setCapStatus(GL_BLEND, false); @@ -1028,7 +1377,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) { - GLCALL(glScissor(box.x, box.y, box.width, box.height)); + glScissor(box.x, box.y, box.width, box.height); m_lastScissorBox = box; } @@ -1037,7 +1386,7 @@ void CHyprOpenGLImpl::scissor(const CBox& originalBox, bool transform) { } if (originalBox != m_lastScissorBox) { - GLCALL(glScissor(originalBox.x, originalBox.y, originalBox.width, originalBox.height)); + glScissor(originalBox.x, originalBox.y, originalBox.width, originalBox.height); m_lastScissorBox = originalBox; } @@ -1079,19 +1428,44 @@ void CHyprOpenGLImpl::renderRectWithBlurInternal(const CBox& box, const CHyprCol CRegion damage{m_renderData.damage}; damage.intersect(box); - auto POUTFB = data.xray ? m_renderData.pCurrentMonData->blurFB : blurMainFramebufferWithDamage(data.blurA, &damage); + CFramebuffer* 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 = data.round, .roundingPower = 2.F, .allowCustomUV = false, .allowDim = false, .noAA = false}); + STextureRenderData{.damage = &damage, .a = data.blurA, .round = 0, .roundingPower = 2.0f, .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); } @@ -1108,11 +1482,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); - auto shader = useShader(getShaderVariant(SH_FRAG_QUAD, data.round > 0 ? SH_FEAT_ROUNDING : 0)); - shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); + useProgram(m_shaders->m_shQUAD.program); + m_shaders->m_shQUAD.setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); // premultiply the color as well as we don't work with straight alpha - shader->setUniformFloat4(SHADER_COLOR, col.r * col.a, col.g * col.a, col.b * col.a, col.a); + m_shaders->m_shQUAD.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, @@ -1122,12 +1496,12 @@ void CHyprOpenGLImpl::renderRectWithDamageInternal(const CBox& box, const CHyprC const auto FULLSIZE = Vector2D(transformedBox.width, transformedBox.height); // Rounded corners - 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); + 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); - glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); + glBindVertexArray(m_shaders->m_shQUAD.uniformLocations[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}; @@ -1135,13 +1509,13 @@ void CHyprOpenGLImpl::renderRectWithDamageInternal(const CBox& box, const CHyprC if (!damageClip.empty()) { damageClip.forEachRect([this](const auto& RECT) { - scissor(&RECT, m_renderData.transformDamage); + scissor(&RECT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } } else { data.damage->forEachRect([this](const auto& RECT) { - scissor(&RECT, m_renderData.transformDamage); + scissor(&RECT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } @@ -1150,7 +1524,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) { @@ -1186,83 +1560,192 @@ static bool isHDR2SDR(const NColorManagement::SImageDescription& imageDescriptio targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22); } -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); +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"); - 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); + 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); - 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]); + shader.setUniformInt(SHADER_TARGET_TF, targetImageDescription->value().transferFunction); - const auto mat = settings.dstPrimaries2XYZ; - const std::array glTargetPrimariesXYZ = { + 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 = { 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], // }; - 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)); + primariesConversionCache.insert(std::make_pair(cacheKey, glConvertMatrix)); } + shader.setUniformMatrix3fv(SHADER_CONVERT_MATRIX, 1, false, primariesConversionCache[cacheKey]); } -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::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); } -WP CHyprOpenGLImpl::renderToOutputInternal() { +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); + 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"); - WP shader = - g_pHyprRenderer->m_crashingInProgress ? getShaderVariant(SH_FRAG_GLITCH) : (m_finalScreenShader->program() ? m_finalScreenShader : getShaderVariant(SH_FRAG_PASSTHRURGBA)); + // 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; - shader = useShader(shader); + if (m_monitorTransformEnabled) + TRANSFORM = Math::composeTransform(MONITOR_INVERTED, TRANSFORM); - if (*PDT == 0 || g_pHyprRenderer->m_crashingInProgress) - shader->setUniformFloat(SHADER_TIME, m_globalTimer.getSeconds() - shader->getInitialTime()); - else + 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) shader->setUniformFloat(SHADER_TIME, 0.f); - 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) { + 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 (*PDT == 0) { + if (usingFinalShader && *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); @@ -1288,7 +1771,7 @@ WP CHyprOpenGLImpl::renderToOutputInternal() { shader->setUniformFloat(SHADER_POINTER_LAST_ACTIVE, g_pInputManager->m_lastCursorMovement.getSeconds()); shader->setUniformFloat(SHADER_POINTER_SWITCH_TIME, g_pHyprRenderer->m_lastCursorData.switchedTimer.getSeconds()); - } else { + } else if (usingFinalShader) { shader->setUniformFloat2(SHADER_POINTER, 0.f, 0.f); static const std::vector pressedPosDefault(POINTER_PRESSED_HISTORY_LENGTH * 2uz, 0.f); @@ -1302,249 +1785,68 @@ WP CHyprOpenGLImpl::renderToOutputInternal() { shader->setUniformFloat(SHADER_POINTER_SWITCH_TIME, 0.f); } - if (g_pHyprRenderer->m_crashingInProgress) { + if (CRASHING) { 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); } - return shader; -} + if (!usingFinalShader) { + shader->setUniformFloat(SHADER_ALPHA, alpha); -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 (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); } } - 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 (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); + 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); } else shader->setUniformInt(SHADER_APPLY_TINT, 0); - } else - shader->setUniformInt(SHADER_APPLY_TINT, 0); - - return shader; -} - -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 { - 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; - + glBindVertexArray(shader->uniformLocations[SHADER_SHADER_VAO]); 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; + 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, + }; - 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; + glBindBuffer(GL_ARRAY_BUFFER, shader->uniformLocations[SHADER_SHADER_VBO_UV]); + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(customUVs), customUVs); + } else { + glBindBuffer(GL_ARRAY_BUFFER, shader->uniformLocations[SHADER_SHADER_VBO_UV]); + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(fullVerts), fullVerts); } - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(verts), verts.data()); - if (!m_renderData.clipBox.empty() || !m_renderData.clipRegion.empty()) { CRegion damageClip = m_renderData.clipBox; @@ -1557,25 +1859,25 @@ void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, c if (!damageClip.empty()) { damageClip.forEachRect([this](const auto& RECT) { - scissor(&RECT, m_renderData.transformDamage); + scissor(&RECT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } } else { data.damage->forEachRect([this](const auto& RECT) { - scissor(&RECT, m_renderData.transformDamage); + scissor(&RECT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } - GLCALL(glBindVertexArray(0)); - GLCALL(glBindBuffer(GL_ARRAY_BUFFER, 0)); + glBindVertexArray(0); + 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->ok()), "Attempted to draw nullptr texture!"); + RASSERT((tex->m_texID > 0), "Attempted to draw nullptr texture!"); TRACY_GPU_ZONE("RenderTexturePrimitive"); @@ -1590,6 +1892,8 @@ 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(); @@ -1599,17 +1903,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, tex->magFilter); - tex->setTexParameter(GL_TEXTURE_MIN_FILTER, tex->minFilter); + tex->setTexParameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR); + tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); } - auto shader = useShader(getShaderVariant(SH_FRAG_PASSTHRURGBA)); + useProgram(shader->program); shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); shader->setUniformInt(SHADER_TEX, 0); - glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); + glBindVertexArray(shader->uniformLocations[SHADER_SHADER_VAO]); m_renderData.damage.forEachRect([this](const auto& RECT) { - scissor(&RECT, m_renderData.transformDamage); + scissor(&RECT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); @@ -1618,9 +1922,9 @@ void CHyprOpenGLImpl::renderTexturePrimitive(SP tex, const CBox& box) tex->unbind(); } -void CHyprOpenGLImpl::renderTextureMatte(SP tex, const CBox& box, SP matte) { +void CHyprOpenGLImpl::renderTextureMatte(SP tex, const CBox& box, CFramebuffer& matte) { RASSERT(m_renderData.pMonitor, "Tried to render texture without begin()!"); - RASSERT((tex->ok()), "Attempted to draw nullptr texture!"); + RASSERT((tex->m_texID > 0), "Attempted to draw nullptr texture!"); TRACY_GPU_ZONE("RenderTextureMatte"); @@ -1635,7 +1939,9 @@ void CHyprOpenGLImpl::renderTextureMatte(SP tex, const CBox& box, SPm_shMATTE; + + useProgram(shader->program); shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); shader->setUniformInt(SHADER_TEX, 0); shader->setUniformInt(SHADER_ALPHA_MATTE, 1); @@ -1644,13 +1950,13 @@ void CHyprOpenGLImpl::renderTextureMatte(SP tex, const CBox& box, SPbind(); glActiveTexture(GL_TEXTURE0 + 1); - auto matteTex = matte->getTexture(); + auto matteTex = matte.getTexture(); matteTex->bind(); - glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); + glBindVertexArray(shader->uniformLocations[SHADER_SHADER_VAO]); m_renderData.damage.forEachRect([this](const auto& RECT) { - scissor(&RECT, m_renderData.transformDamage); + scissor(&RECT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); @@ -1663,16 +1969,16 @@ void CHyprOpenGLImpl::renderTextureMatte(SP tex, const CBox& box, SP CHyprOpenGLImpl::blurMainFramebufferWithDamage(float a, CRegion* originalDamage) { +CFramebuffer* 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, *GLFB(m_renderData.currentFB)); + return blurFramebufferWithDamage(a, originalDamage, *m_renderData.currentFB); } -SP CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* originalDamage, CGLFramebuffer& source) { +CFramebuffer* CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* originalDamage, CFramebuffer& source) { TRACY_GPU_ZONE("RenderBlurFramebufferWithDamage"); const auto BLENDBEFORE = m_blend; @@ -1700,17 +2006,16 @@ SP CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* or 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; - auto currentRenderToFB = PMIRRORFB; + CFramebuffer* 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(); @@ -1721,34 +2026,33 @@ SP CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* or currentTex->bind(); currentTex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); - WP shader; + useProgram(m_shaders->m_shBLURPREPARE.program); // From FB to sRGB - const bool skipCM = !m_cmSupported || g_pHyprRenderer->workBufferImageDescription()->id() == DEFAULT_IMAGE_DESCRIPTION->id(); + const bool skipCM = !m_cmSupported || m_renderData.pMonitor->m_imageDescription->id() == DEFAULT_IMAGE_DESCRIPTION->id(); + m_shaders->m_shBLURPREPARE.setUniformInt(SHADER_SKIP_CM, skipCM); if (!skipCM) { - 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)); + 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); + } - 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); + 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); - glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); + glBindVertexArray(m_shaders->m_shBLURPREPARE.uniformLocations[SHADER_SHADER_VAO]); if (!damage.empty()) { damage.forEachRect([this](const auto& RECT) { @@ -1762,7 +2066,7 @@ SP CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* or } // declare the draw func - auto drawPass = [&](WP shader, ePreparedFragmentShader frag, CRegion* pDamage) { + auto drawPass = [&](SShader* pShader, CRegion* pDamage) { if (currentRenderToFB == PMIRRORFB) PMIRRORSWAPFB->bind(); else @@ -1776,19 +2080,21 @@ SP CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* or currentTex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); - // prep two shaders - 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 - 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); + useProgram(pShader->program); - glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); + // 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); + } 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); + + glBindVertexArray(pShader->uniformLocations[SHADER_SHADER_VAO]); if (!pDamage->empty()) { pDamage->forEachRect([this](const auto& RECT) { @@ -1814,16 +2120,14 @@ SP CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* or 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(shader, SH_FRAG_BLUR1, &tempDamage); // down + drawPass(&m_shaders->m_shBLUR1, &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(shader, SH_FRAG_BLUR2, &tempDamage); // up + drawPass(&m_shaders->m_shBLUR2, &tempDamage); // up } // finalize the image @@ -1844,14 +2148,14 @@ SP CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* or currentTex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); - 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); + 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); - shader->setUniformInt(SHADER_TEX, 0); + m_shaders->m_shBLURFINISH.setUniformInt(SHADER_TEX, 0); - glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); + glBindVertexArray(m_shaders->m_shBLURFINISH.uniformLocations[SHADER_SHADER_VAO]); if (!damage.empty()) { damage.forEachRect([this](const auto& RECT) { @@ -1975,12 +2279,9 @@ void CHyprOpenGLImpl::preBlurForCurrentMonitor() { const auto POUTFB = blurMainFramebufferWithDamage(1, &fakeDamage); // render onto blurFB - 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(); + 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)); @@ -2016,7 +2317,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 || !m_renderData.pCurrentMonData->blurFB->getTexture()) + if (!m_renderData.pCurrentMonData->blurFB.getTexture()) return false; if (pWindow && pWindow->m_ruleApplicator->xray().hasValue() && !pWindow->m_ruleApplicator->xray().valueOrDefault()) @@ -2034,13 +2335,11 @@ 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); @@ -2074,112 +2373,107 @@ 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; - SP POUTFB = nullptr; + CFramebuffer* 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(); - auto blurredBG = POUTFB->getTexture(); + // 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); - const auto NEEDS_STENCIL = m_renderData.discardMode != 0 && (!data.blockBlurOptimization || (m_renderData.discardMode & DISCARD_ALPHA)); - if (!*PBLEND) { + setCapStatus(GL_STENCIL_TEST, true); - if (NEEDS_STENCIL) { - scissor(nullptr); // allow the entire window and stencil to render - glClearStencil(0); - glClear(GL_STENCIL_BUFFER_BIT); + glStencilFunc(GL_ALWAYS, 1, 0xFF); + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); - setCapStatus(GL_STENCIL_TEST, true); + 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); - glStencilFunc(GL_ALWAYS, 1, 0xFF); - glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + glStencilFunc(GL_EQUAL, 1, 0xFF); + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); - 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 + // stencil done. Render everything. + const auto LASTTL = m_renderData.primarySurfaceUVTopLeft; + const auto LASTBR = m_renderData.primarySurfaceUVBottomRight; - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + 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); - glStencilFunc(GL_EQUAL, 1, 0xFF); - glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); - } + 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}; - // stencil done. Render everything. - const auto LASTTL = m_renderData.primarySurfaceUVTopLeft; - const auto LASTBR = m_renderData.primarySurfaceUVBottomRight; + m_renderData.primarySurfaceUVTopLeft = monitorSpaceBox.pos() / m_renderData.pMonitor->m_transformedSize; + m_renderData.primarySurfaceUVBottomRight = (monitorSpaceBox.pos() + monitorSpaceBox.size()) / m_renderData.pMonitor->m_transformedSize; - 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); + 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 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}; + m_renderData.primarySurfaceUVTopLeft = LASTTL; + m_renderData.primarySurfaceUVBottomRight = LASTBR; - 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; - } + // render the window, but clear stencil + glClearStencil(0); + glClear(GL_STENCIL_BUFFER_BIT); // 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 = *PBLEND && NEEDS_STENCIL, + .discardActive = false, .allowCustomUV = true, .allowDim = true, .noAA = false, .wrapX = data.wrapX, .wrapY = data.wrapY, - .blurredBG = blurredBG, }); - GLFB(m_renderData.currentFB)->invalidate({GL_STENCIL_ATTACHMENT}); + glStencilMask(0xFF); + glStencilFunc(GL_ALWAYS, 1, 0xFF); scissor(nullptr); } @@ -2216,22 +2510,19 @@ void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& gr const auto BLEND = m_blend; blend(true); - WP shader; + useProgram(m_shaders->m_shBORDER1.program); - 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); - 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); + 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); CBox transformedBox = newBox; transformedBox.transform(Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x, @@ -2240,15 +2531,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); - 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); + 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); - glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); + glBindVertexArray(m_shaders->m_shBORDER1.uniformLocations[SHADER_SHADER_VAO]); // calculate the border's region, which we need to render over. No need to run the shader on // things outside there @@ -2260,7 +2551,7 @@ void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& gr if (!borderRegion.empty()) { borderRegion.forEachRect([this](const auto& RECT) { - scissor(&RECT, m_renderData.transformDamage); + scissor(&RECT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } @@ -2303,25 +2594,23 @@ void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& gr const auto BLEND = m_blend; blend(true); - 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)); + useProgram(m_shaders->m_shBORDER1.program); - 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)); + 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)); if (!grad2.m_colorsOkLabA.empty()) - 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); + 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); CBox transformedBox = newBox; transformedBox.transform(Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x, @@ -2330,15 +2619,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); - 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); + 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); - glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); + glBindVertexArray(m_shaders->m_shBORDER1.uniformLocations[SHADER_SHADER_VAO]); // calculate the border's region, which we need to render over. No need to run the shader on // things outside there @@ -2350,7 +2639,7 @@ void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& gr if (!borderRegion.empty()) { borderRegion.forEachRect([this](const auto& RECT) { - scissor(&RECT, m_renderData.transformDamage); + scissor(&RECT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } @@ -2383,29 +2672,29 @@ void CHyprOpenGLImpl::renderRoundedShadow(const CBox& box, int round, float roun blend(true); - 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))); + 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); if (!skipCM) - passCMUniforms(shader, DEFAULT_IMAGE_DESCRIPTION); + passCMUniforms(m_shaders->m_shSHADOW, DEFAULT_IMAGE_DESCRIPTION); - shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); - shader->setUniformFloat4(SHADER_COLOR, col.r, col.g, col.b, col.a * a); + 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); 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 - 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); + 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); - glBindVertexArray(shader->getUniformLocation(SHADER_SHADER_VAO)); + glBindVertexArray(m_shaders->m_shSHADOW.uniformLocations[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}; @@ -2413,13 +2702,13 @@ void CHyprOpenGLImpl::renderRoundedShadow(const CBox& box, int round, float roun if (!damageClip.empty()) { damageClip.forEachRect([this](const auto& RECT) { - scissor(&RECT, m_renderData.transformDamage); + scissor(&RECT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } } else { m_renderData.damage.forEachRect([this](const auto& RECT) { - scissor(&RECT, m_renderData.transformDamage); + scissor(&RECT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }); } @@ -2428,25 +2717,21 @@ 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{ - .damage = &m_renderData.finalDamage, - .a = 1.F, + .a = 1.f, .round = 0, .discardActive = false, .allowCustomUV = false, - .cmBackToSRGB = true, }); blend(true); @@ -2468,7 +2753,7 @@ void CHyprOpenGLImpl::renderMirrored() { monbox.x = (monitor->m_transformedSize.x - monbox.w) / 2; monbox.y = (monitor->m_transformedSize.y - monbox.h) / 2; - auto PFB = m_monitorRenderResources[mirrored].monitorMirrorFB; + const auto PFB = &m_monitorRenderResources[mirrored].monitorMirrorFB; if (!PFB->isAllocated() || !PFB->getTexture()) return; @@ -2542,7 +2827,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); @@ -2564,11 +2849,12 @@ 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({cairo_image_surface_get_width(cairo), cairo_image_surface_get_height(cairo)}); + tex->allocate(); + tex->m_size = {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; @@ -2589,8 +2875,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"); @@ -2652,7 +2938,8 @@ SP CHyprOpenGLImpl::renderText(const std::string& text, CHyprColor col cairo_surface_flush(CAIROSURFACE); - tex->allocate({cairo_image_surface_get_width(CAIROSURFACE), cairo_image_surface_get_height(CAIROSURFACE)}); + tex->allocate(); + tex->m_size = {cairo_image_surface_get_width(CAIROSURFACE), cairo_image_surface_get_height(CAIROSURFACE)}; const auto DATA = cairo_image_surface_get_data(CAIROSURFACE); tex->bind(); @@ -2669,8 +2956,8 @@ SP CHyprOpenGLImpl::renderText(const std::string& text, CHyprColor col } void CHyprOpenGLImpl::initMissingAssetTexture() { - SP tex = makeShared(); - tex->allocate({512, 512}); + SP tex = makeShared(); + tex->allocate(); const auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 512, 512); const auto CAIRO = cairo_create(CAIROSURFACE); @@ -2709,14 +2996,12 @@ void CHyprOpenGLImpl::initMissingAssetTexture() { m_missingAssetTexture = tex; } -WP CHyprOpenGLImpl::useShader(WP prog) { - if (m_currentProgram == prog->program()) - return prog; +void CHyprOpenGLImpl::useProgram(GLuint prog) { + if (m_currentProgram == prog) + return; - glUseProgram(prog->program()); - m_currentProgram = prog->program(); - - return prog; + glUseProgram(prog); + m_currentProgram = prog; } void CHyprOpenGLImpl::initAssets() { @@ -2822,19 +3107,16 @@ 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 - auto PFB = m_monitorBGFBs[pMonitor]; + const 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(pMonitor->m_pixelSize); + tex->allocate(); const auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y); const auto CAIRO = cairo_create(CAIROSURFACE); @@ -2876,7 +3158,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) { @@ -2931,7 +3213,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))); } } @@ -2944,19 +3226,19 @@ void CHyprOpenGLImpl::destroyMonitorResources(PHLMONITORREF pMonitor) { auto RESIT = g_pHyprOpenGL->m_monitorRenderResources.find(pMonitor); if (RESIT != g_pHyprOpenGL->m_monitorRenderResources.end()) { - 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(); + 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(); g_pHyprOpenGL->m_monitorRenderResources.erase(RESIT); } auto TEXIT = g_pHyprOpenGL->m_monitorBGFBs.find(pMonitor); if (TEXIT != g_pHyprOpenGL->m_monitorBGFBs.end()) { - TEXIT->second.reset(); + TEXIT->second.release(); g_pHyprOpenGL->m_monitorBGFBs.erase(TEXIT); } @@ -2977,21 +3259,19 @@ void CHyprOpenGLImpl::restoreMatrix() { } void CHyprOpenGLImpl::bindOffMain() { - if (!m_renderData.pCurrentMonData->offMainFB) - m_renderData.pCurrentMonData->offMainFB = g_pHyprRenderer->createFB(); + 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->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.addStencil(m_renderData.pCurrentMonData->stencilTex); } - 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(CGLFramebuffer* off) { +void CHyprOpenGLImpl::renderOffToMain(CFramebuffer* off) { CBox monbox = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y}; renderTexturePrimitive(off->getTexture(), monbox); } @@ -3037,9 +3317,9 @@ void CHyprOpenGLImpl::setCapStatus(int cap, bool status) { if (idx == CAP_STATUS_END) { if (status) - GLCALL(glEnable(cap)) + glEnable(cap); else - GLCALL(glDisable(cap)); + glDisable(cap); return; } @@ -3049,62 +3329,31 @@ void CHyprOpenGLImpl::setCapStatus(int cap, bool status) { if (status) { m_capStatus[idx] = status; - GLCALL(glEnable(cap)); + glEnable(cap); } else { m_capStatus[idx] = status; - GLCALL(glDisable(cap)); + glDisable(cap); } } -DRMFormat CHyprOpenGLImpl::getPreferredReadFormat(PHLMONITOR pMonitor) { +uint32_t CHyprOpenGLImpl::getPreferredReadFormat(PHLMONITOR pMonitor) { static const auto PFORCE8BIT = CConfigValue("misc:screencopy_force_8b"); - auto monFmt = pMonitor->m_output->state->state().drmFormat; + if (!*PFORCE8BIT) + return 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; + auto fmt = pMonitor->m_output->state->state().drmFormat; - return monFmt; -} + 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; -std::vector CHyprOpenGLImpl::getDRMFormatModifiers(DRMFormat drmFormat) { - SDRMFormat format; - - for (const auto& fmt : m_drmFormats) { - if (fmt.drmFormat == drmFormat) { - format = fmt; - break; - } - } - - return format.modifiers; + return fmt; } 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; } @@ -3175,7 +3424,7 @@ UP CEGLSync::create() { EGLSyncKHR sync = g_pHyprOpenGL->m_proc.eglCreateSyncKHR(g_pHyprOpenGL->m_eglDisplay, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr); - if UNLIKELY (sync == EGL_NO_SYNC_KHR) { + if (sync == EGL_NO_SYNC_KHR) { Log::logger->log(Log::ERR, "eglCreateSyncKHR failed"); return nullptr; } @@ -3184,7 +3433,7 @@ UP CEGLSync::create() { glFlush(); int fd = g_pHyprOpenGL->m_proc.eglDupNativeFenceFDANDROID(g_pHyprOpenGL->m_eglDisplay, sync); - if UNLIKELY (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) { + if (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) { Log::logger->log(Log::ERR, "eglDupNativeFenceFDANDROID failed"); return nullptr; } @@ -3198,10 +3447,10 @@ UP CEGLSync::create() { } CEGLSync::~CEGLSync() { - if UNLIKELY (m_sync == EGL_NO_SYNC_KHR) + if (m_sync == EGL_NO_SYNC_KHR) return; - if UNLIKELY (g_pHyprOpenGL && g_pHyprOpenGL->m_proc.eglDestroySyncKHR(g_pHyprOpenGL->m_eglDisplay, m_sync) != EGL_TRUE) + if (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 c9008447..e71429b7 100644 --- a/src/render/OpenGL.hpp +++ b/src/render/OpenGL.hpp @@ -20,7 +20,6 @@ #include "Texture.hpp" #include "Framebuffer.hpp" #include "Renderbuffer.hpp" -#include "desktop/DesktopTypes.hpp" #include "pass/Pass.hpp" #include @@ -32,29 +31,17 @@ #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 IHyprRenderer; +class CHyprRenderer; -struct SVertex { - float x, y; // position - float u, v; // uv +inline const float fullVerts[] = { + 1, 0, // top right + 0, 0, // top left + 1, 1, // bottom right + 0, 1, // bottom left }; - -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}; +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, @@ -94,37 +81,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; - // std::array, SH_FRAG_LAST> frag; - // std::map> fragVariants; - std::array>, Render::SH_FRAG_LAST> fragVariants; + 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; }; struct SMonitorRenderData { - 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; + 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 stencilTex = makeShared(); + SP stencilTex = makeShared(); - bool blurFBDirty = true; - bool blurFBShouldRender = false; + bool blurFBDirty = true; + bool blurFBShouldRender = false; }; struct SCurrentRenderData { @@ -135,9 +122,9 @@ struct SCurrentRenderData { // FIXME: raw pointer galore! SMonitorRenderData* pCurrentMonData = nullptr; - SP currentFB = nullptr; // current rendering to - SP mainFB = nullptr; // main to render to - SP outFB = nullptr; // out to render to (if offloaded, etc) + CFramebuffer* currentFB = nullptr; // current rendering to + CFramebuffer* mainFB = nullptr; // main to render to + CFramebuffer* outFB = nullptr; // out to render to (if offloaded, etc) CRegion damage; CRegion finalDamage; // damage used for funal off -> main @@ -148,8 +135,6 @@ 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); @@ -215,11 +200,8 @@ class CHyprOpenGLImpl { bool noAA = false; bool blockBlurOptimization = false; GLenum wrapX = GL_CLAMP_TO_EDGE, wrapY = GL_CLAMP_TO_EDGE; - bool cmBackToSRGB = false; - bool noCM = false; - bool finalMonitorCM = false; + bool cmBackToSRGB = false; SP cmBackToSRGBSource; - SP blurredBG; }; struct SBorderRenderData { @@ -230,17 +212,16 @@ class CHyprOpenGLImpl { int outerRound = -1; /* use round */ }; - 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 begin(PHLMONITOR, const CRegion& damage, CFramebuffer* fb = nullptr, std::optional finalDamage = {}); + void beginSimple(PHLMONITOR, const CRegion& damage, SP rb = nullptr, CFramebuffer* 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, SP matte); - void renderTexturePrimitive(SP tex, const CBox& box); + void renderTextureMatte(SP tex, const CBox& pBox, CFramebuffer& matte); void pushMonitorTransformEnabled(bool enabled); void popMonitorTransformEnabled(); @@ -277,49 +258,49 @@ class CHyprOpenGLImpl { void applyScreenShader(const std::string& path); void bindOffMain(); - void renderOffToMain(CGLFramebuffer* off); + void renderOffToMain(CFramebuffer* 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 = {}); - DRMFormat getPreferredReadFormat(PHLMONITOR pMonitor); - std::vector getDRMFormats(); - std::vector getDRMFormatModifiers(DRMFormat format); - EGLImageKHR createEGLImage(const Aquamarine::SDMABUFAttrs& attrs); + uint32_t getPreferredReadFormat(PHLMONITOR pMonitor); + std::vector getDRMFormats(); + EGLImageKHR createEGLImage(const Aquamarine::SDMABUFAttrs& attrs); - bool initShaders(const std::string& path = ""); + bool initShaders(); - WP useShader(WP prog); + 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); - bool explicitSyncSupported(); - WP getShaderVariant(Render::ePreparedFragmentShader frag, Render::ShaderFeatureFlags features = 0); + void ensureLockTexturesRendered(bool load); - bool m_shadersInitialized = false; - SP m_shaders; + bool explicitSyncSupported(); - SCurrentRenderData m_renderData; + bool m_shadersInitialized = false; + SP m_shaders; - 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; + SCurrentRenderData m_renderData; - bool m_reloadScreenShader = true; // at launch it can be set + 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; - std::map> m_windowFramebuffers; - std::map> m_layerFramebuffers; - std::map, SP> m_popupFramebuffers; - std::map m_monitorRenderResources; - std::map> m_monitorBGFBs; + 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; struct { PFNGLEGLIMAGETARGETRENDERBUFFERSTORAGEOESPROC glEGLImageTargetRenderbufferStorageOES = nullptr; @@ -343,14 +324,13 @@ 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, @@ -391,22 +371,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; - SP m_finalScreenShader; + SP m_missingAssetTexture; + SP m_lockDeadTexture; + SP m_lockDead2Texture; + SP m_lockTtyTextTexture; + SShader 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(); @@ -420,22 +400,21 @@ class CHyprOpenGLImpl { std::optional> getModsForFormat(EGLint format); // returns the out FB, can be either Mirror or MirrorSwap - SP blurMainFramebufferWithDamage(float a, CRegion* damage); - SP blurFramebufferWithDamage(float a, CRegion* damage, CGLFramebuffer& source); + CFramebuffer* blurMainFramebufferWithDamage(float a, CRegion* damage); + CFramebuffer* blurFramebufferWithDamage(float a, CRegion* damage, CFramebuffer& source); - 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 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 preBlurForCurrentMonitor(); + void preBlurForCurrentMonitor(); friend class CHyprRenderer; friend class CTexPassElement; diff --git a/src/render/Renderbuffer.cpp b/src/render/Renderbuffer.cpp index bab4f73e..d7a77b74 100644 --- a/src/render/Renderbuffer.cpp +++ b/src/render/Renderbuffer.cpp @@ -1,21 +1,76 @@ #include "Renderbuffer.hpp" -#include "Framebuffer.hpp" -#include "render/Renderer.hpp" -#include "render/gl/GLRenderbuffer.hpp" -#include +#include "Renderer.hpp" +#include "OpenGL.hpp" +#include "../Compositor.hpp" +#include "../protocols/types/Buffer.hpp" #include #include #include -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() { + 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); } -bool IRenderbuffer::good() { +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() { return m_good; } -SP IRenderbuffer::getFB() { - return m_framebuffer; +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; } diff --git a/src/render/Renderbuffer.hpp b/src/render/Renderbuffer.hpp index c33144d3..c0924141 100644 --- a/src/render/Renderbuffer.hpp +++ b/src/render/Renderbuffer.hpp @@ -5,24 +5,30 @@ #include "Framebuffer.hpp" #include -class IRenderbuffer { +class CMonitor; + +class CRenderbuffer { public: - IRenderbuffer(SP buffer, uint32_t format); - virtual ~IRenderbuffer() = default; + CRenderbuffer(SP buffer, uint32_t format); + ~CRenderbuffer(); bool good(); - SP getFB(); - - virtual void bind() = 0; - virtual void unbind() = 0; + void bind(); + void bindFB(); + void unbind(); + CFramebuffer* getFB(); + uint32_t getFormat(); WP m_hlBuffer; - protected: - SP m_framebuffer; - bool m_good = false; + private: + void* m_image = nullptr; + GLuint m_rbo = 0; + CFramebuffer m_framebuffer; + uint32_t m_drmFormat = 0; + 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 165f580a..1aa85f15 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -3,14 +3,15 @@ #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" @@ -27,15 +28,9 @@ #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" @@ -45,18 +40,7 @@ #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; @@ -128,7 +112,7 @@ CHyprRenderer::CHyprRenderer() { // cursor hiding stuff - static auto P = Event::bus()->m_events.input.keyboard.key.listen([&](IKeyboard::SKeyEvent e, Event::SCallbackInfo&) { + static auto P = g_pHookSystem->hookDynamic("keyPress", [&](void* self, SCallbackInfo& info, std::any param) { if (m_cursorHiddenConditions.hiddenOnKeyboard) return; @@ -136,7 +120,7 @@ CHyprRenderer::CHyprRenderer() { ensureCursorRenderingMode(); }); - static auto P2 = Event::bus()->m_events.input.mouse.move.listen([&](Vector2D pos, Event::SCallbackInfo&) { + static auto P2 = g_pHookSystem->hookDynamic("mouseMove", [&](void* self, SCallbackInfo& info, std::any param) { if (!m_cursorHiddenConditions.hiddenOnKeyboard && m_cursorHiddenConditions.hiddenOnTouch == g_pInputManager->m_lastInputTouch && m_cursorHiddenConditions.hiddenOnTablet == g_pInputManager->m_lastInputTablet && !m_cursorHiddenConditions.hiddenOnTimeout) return; @@ -148,7 +132,7 @@ CHyprRenderer::CHyprRenderer() { ensureCursorRenderingMode(); }); - static auto P3 = Event::bus()->m_events.monitor.focused.listen([&](PHLMONITOR mon) { + static auto P3 = g_pHookSystem->hookDynamic("focusedMon", [&](void* self, SCallbackInfo& info, std::any param) { g_pEventLoopManager->doLater([this]() { if (!g_pHyprError->active()) return; @@ -158,9 +142,11 @@ CHyprRenderer::CHyprRenderer() { }); }); - static auto P4 = Event::bus()->m_events.window.updateRules.listen([&](PHLWINDOW window) { - if (window->m_ruleApplicator->renderUnfocused().valueOrDefault()) - addWindowToRenderUnfocused(window); + 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); }); m_cursorTicker = wl_event_loop_add_timer(g_pCompositor->m_wlEventLoop, cursorTicker, nullptr); @@ -303,7 +289,7 @@ bool CHyprRenderer::shouldRenderWindow(PHLWINDOW pWindow) { void CHyprRenderer::renderWorkspaceWindowsFullscreen(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& time) { PHLWINDOW pWorkspaceWindow = nullptr; - Event::bus()->m_events.render.stage.emit(RENDER_PRE_WINDOWS); + EMIT_HOOK_EVENT("render", RENDER_PRE_WINDOWS); // loop over the tiled windows that are fading out for (auto const& w : g_pCompositor->m_windows) { @@ -394,7 +380,7 @@ void CHyprRenderer::renderWorkspaceWindowsFullscreen(PHLMONITOR pMonitor, PHLWOR void CHyprRenderer::renderWorkspaceWindows(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& time) { PHLWINDOW lastWindow; - Event::bus()->m_events.render.stage.emit(RENDER_PRE_WINDOWS); + EMIT_HOOK_EVENT("render", RENDER_PRE_WINDOWS); std::vector windows, tiledFadingOut; windows.reserve(g_pCompositor->m_windows.size()); @@ -558,7 +544,7 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T // for plugins g_pHyprOpenGL->m_renderData.currentWindow = pWindow; - Event::bus()->m_events.render.stage.emit(RENDER_PRE_WINDOW); + EMIT_HOOK_EVENT("render", RENDER_PRE_WINDOW); const auto fullAlpha = renderdata.alpha * renderdata.fadeAlpha; @@ -657,13 +643,13 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T } if (TRANSFORMERSPRESENT) { - IFramebuffer* last = g_pHyprOpenGL->m_renderData.currentFB.get(); + CFramebuffer* last = g_pHyprOpenGL->m_renderData.currentFB; for (auto const& t : pWindow->m_transformers) { last = t->transform(last); } g_pHyprOpenGL->bindBackOnMain(); - g_pHyprOpenGL->renderOffToMain(dc(last)); + g_pHyprOpenGL->renderOffToMain(last); } } @@ -742,41 +728,11 @@ void CHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const T } } - Event::bus()->m_events.render.stage.emit(RENDER_POST_WINDOW); + EMIT_HOOK_EVENT("render", 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; @@ -946,10 +902,10 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA static auto PXPMODE = CConfigValue("render:xp_mode"); static auto PSESSIONLOCKXRAY = CConfigValue("misc:session_lock_xray"); - if UNLIKELY (!pMonitor) + if (!pMonitor) return; - if UNLIKELY (g_pSessionLockManager->isSessionLocked() && !*PSESSIONLOCKXRAY) { + if (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()) @@ -963,10 +919,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 UNLIKELY (scale != 1.f) + if (scale != 1.f) RENDERMODIFDATA.modifs.emplace_back(std::make_pair<>(SRenderModifData::eRenderModifType::RMOD_TYPE_SCALE, scale)); - if UNLIKELY (!RENDERMODIFDATA.modifs.empty()) + if (!RENDERMODIFDATA.modifs.empty()) g_pHyprRenderer->m_renderPass.add(makeUnique(CRendererHintsPassElement::SData{RENDERMODIFDATA})); CScopeGuard x([&RENDERMODIFDATA] { @@ -975,7 +931,7 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA } }); - if UNLIKELY (!pWorkspace) { + if (!pWorkspace) { // allow rendering without a workspace. In this case, just render layers. renderBackground(pMonitor); @@ -984,7 +940,7 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA renderLayer(ls.lock(), pMonitor, time); } - Event::bus()->m_events.render.stage.emit(RENDER_POST_WALLPAPER); + EMIT_HOOK_EVENT("render", RENDER_POST_WALLPAPER); for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]) { renderLayer(ls.lock(), pMonitor, time); @@ -1001,14 +957,14 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA return; } - if LIKELY (!*PXPMODE) { + if (!*PXPMODE) { renderBackground(pMonitor); for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]) { renderLayer(ls.lock(), pMonitor, time); } - Event::bus()->m_events.render.stage.emit(RENDER_POST_WALLPAPER); + EMIT_HOOK_EVENT("render", RENDER_POST_WALLPAPER); for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]) { renderLayer(ls.lock(), pMonitor, time); @@ -1018,13 +974,13 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA // pre window pass g_pHyprOpenGL->preWindowPass(); - if UNLIKELY /* subjective? */ (pWorkspace->m_hasFullscreenWindow) + if (pWorkspace->m_hasFullscreenWindow) renderWorkspaceWindowsFullscreen(pMonitor, pWorkspace, time); else renderWorkspaceWindows(pMonitor, pWorkspace, time); // and then special - if UNLIKELY (pMonitor->m_specialFade->value() != 0.F) { + if (pMonitor->m_specialFade->value() != 0.F) { const auto SPECIALANIMPROGRS = pMonitor->m_specialFade->getCurveValue(); const bool ANIMOUT = !pMonitor->m_activeSpecialWorkspace; @@ -1073,7 +1029,7 @@ void CHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPA renderWindow(w, pMonitor, time, true, RENDER_PASS_ALL); } - Event::bus()->m_events.render.stage.emit(RENDER_POST_WINDOWS); + EMIT_HOOK_EVENT("render", RENDER_POST_WINDOWS); // Render surfaces above windows for monitor for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) { @@ -1304,79 +1260,6 @@ 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(); @@ -1385,7 +1268,6 @@ 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 @@ -1422,8 +1304,7 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { if (pMonitor->m_scheduledRecalc) { pMonitor->m_scheduledRecalc = false; - if (pMonitor->m_activeWorkspace) // might be missing (mirror) - pMonitor->m_activeWorkspace->m_space->recalculate(); + g_pLayoutManager->getCurrentLayout()->recalculateMonitor(pMonitor->m_id); } if (!pMonitor->m_output->needsFrame && pMonitor->m_forceFullFrames == 0) @@ -1433,12 +1314,10 @@ 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() || pMonitor->m_directScanoutIsActive) { + } else if (!pMonitor->m_lastScanout.expired()) { 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) @@ -1447,7 +1326,7 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { pMonitor->m_drmFormat = pMonitor->m_prevDrmFormat; } - Event::bus()->m_events.render.pre.emit(pMonitor); + EMIT_HOOK_EVENT("preRender", pMonitor); const auto NOW = Time::steadyNow(); @@ -1462,7 +1341,7 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { return; } - Event::bus()->m_events.render.stage.emit(RENDER_PRE); + EMIT_HOOK_EVENT("render", RENDER_PRE); pMonitor->m_renderingActive = true; @@ -1515,49 +1394,50 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { pMonitor->m_forceFullFrames = 0; } - Event::bus()->m_events.render.stage.emit(RENDER_BEGIN); + EMIT_HOOK_EVENT("render", RENDER_BEGIN); bool renderCursor = true; - 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); + 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); - renderLockscreen(pMonitor, NOW, renderBox); + renderLockscreen(pMonitor, NOW, renderBox); - if (pMonitor == Desktop::focusState()->monitor()) { - g_pHyprNotificationOverlay->draw(pMonitor); - g_pHyprError->draw(); + 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; + } } - - // 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 + renderWindow(pMonitor->m_solitaryClient.lock(), pMonitor, NOW, false, RENDER_PASS_MAIN /* solitary = no popups */); } else if (!pMonitor->isMirror()) { sendFrameEventsToWorkspace(pMonitor, pMonitor->m_activeWorkspace, NOW); if (pMonitor->m_activeSpecialWorkspace) @@ -1579,7 +1459,7 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { m_renderPass.add(makeUnique(data)); } - Event::bus()->m_events.render.stage.emit(RENDER_LAST_MOMENT); + EMIT_HOOK_EVENT("render", RENDER_LAST_MOMENT); endRender(); @@ -1601,7 +1481,7 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { pMonitor->m_renderingActive = false; - Event::bus()->m_events.render.stage.emit(RENDER_POST); + EMIT_HOOK_EVENT("render", RENDER_POST); pMonitor->m_output->state->addDamage(frameDamage); pMonitor->m_output->state->setPresentationMode(shouldTear ? Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_IMMEDIATE : @@ -1672,8 +1552,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 > 0 ? settings.maxCLL : monitor->maxCLL()), - .max_fall = toNits(settings.maxFALL > 0 ? settings.maxFALL : monitor->maxFALL()), + .max_cll = toNits(settings.maxCLL), + .max_fall = toNits(settings.maxFALL), }, }; } @@ -1687,7 +1567,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->getFullscreenWindow(); + const auto FS_WINDOW = pMonitor->inFullscreenMode() ? pMonitor->m_activeWorkspace->getFullscreenWindow() : nullptr; if (pMonitor->supportsHDR()) { // HDR metadata determined by @@ -1766,7 +1646,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 || (!FS_WINDOW && pMonitor->m_noShaderCTM)) { + if (FS_WINDOW != pMonitor->m_previousFSWindow) { 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) { @@ -2024,7 +1904,7 @@ void CHyprRenderer::arrangeLayersForMonitor(const MONITORID& monitor) { // damage the monitor if can damageMonitor(PMONITOR); - g_layoutManager->invalidateMonitorGeometries(PMONITOR); + g_pLayoutManager->getCurrentLayout()->recalculateMonitor(monitor); } void CHyprRenderer::damageSurface(SP pSurface, double x, double y, double scale) { @@ -2034,33 +1914,22 @@ void CHyprRenderer::damageSurface(SP pSurface, double x, dou if (g_pCompositor->m_unsafeState) return; - const auto WLSURF = Desktop::View::CWLSurface::fromResource(pSurface); + const auto WLSURF = Desktop::View::CWLSurface::fromResource(pSurface); + CRegion damageBox = WLSURF ? WLSURF->computeDamage() : CRegion{}; if (!WLSURF) { Log::logger->log(Log::ERR, "BUG THIS: No CWLSurface for surface in damageSurface!!!"); return; } - // 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; - - 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); + // schedule frame events + g_pCompositor->scheduleFrameForMonitor(g_pCompositor->getMonitorFromVector(Vector2D(x, y)), Aquamarine::IOutput::AQ_SCHEDULE_DAMAGE); + + if (damageBox.empty()) + return; + damageBox.translate({x, y}); CRegion damageBoxForEach; @@ -2180,7 +2049,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 && surf->resource(); + m_cursorHasSurface = surf; m_lastCursorData.name = ""; m_lastCursorData.surf = surf; @@ -2271,19 +2140,30 @@ void CHyprRenderer::ensureCursorRenderingMode() { if (HIDE == m_cursorHidden) return; - if (HIDE) + if (HIDE) { Log::logger->log(Log::DEBUG, "Hiding the cursor (hl-mandated)"); - else + + 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 { 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; - g_pPointerManager->damageCursor(m, m->shouldSkipScheduleFrameOnMouseEvent()); + damageMonitor(m); // TODO: maybe just damage the cursor area? + } + + setCursorHidden(false); } - - setCursorHidden(HIDE); } void CHyprRenderer::setCursorHidden(bool hide) { @@ -2317,8 +2197,10 @@ std::tuple CHyprRenderer::getRenderTimes(PHLMONITOR pMonito float maxRenderTime = 0; float minRenderTime = 9999; for (auto const& rt : POVERLAY->m_lastRenderTimes) { - maxRenderTime = std::max(rt, maxRenderTime); - minRenderTime = std::min(rt, minRenderTime); + if (rt > maxRenderTime) + maxRenderTime = rt; + if (rt < minRenderTime) + minRenderTime = rt; avgRenderTime += rt; } avgRenderTime /= POVERLAY->m_lastRenderTimes.empty() ? 1 : POVERLAY->m_lastRenderTimes.size(); @@ -2357,13 +2239,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; @@ -2387,7 +2269,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, SP fb, bool simple) { +bool CHyprRenderer::beginRender(PHLMONITOR pMonitor, CRegion& damage, eRenderMode mode, SP buffer, CFramebuffer* fb, bool simple) { makeEGLCurrent(); @@ -2488,7 +2370,7 @@ void CHyprRenderer::endRender(const std::function& renderingDoneCallback } UP eglSync = CEGLSync::create(); - if LIKELY (eglSync && eglSync->isValid()) { + if (eglSync && eglSync->isValid()) { for (auto const& buf : m_usedAsyncBuffers) { for (const auto& releaser : buf->m_syncReleasers) { releaser->addSyncFileFd(eglSync->fd()); @@ -2519,11 +2401,11 @@ void CHyprRenderer::endRender(const std::function& renderingDoneCallback } } -void CHyprRenderer::onRenderbufferDestroy(CGLRenderbuffer* rb) { +void CHyprRenderer::onRenderbufferDestroy(CRenderbuffer* rb) { std::erase_if(m_renderbuffers, [&](const auto& rbo) { return rbo.get() == rb; }); } -SP CHyprRenderer::getCurrentRBO() { +SP CHyprRenderer::getCurrentRBO() { return m_currentRenderbuffer; } @@ -2576,10 +2458,7 @@ void CHyprRenderer::makeSnapshot(PHLWINDOW pWindow) { makeEGLCurrent(); - if (!g_pHyprOpenGL->m_windowFramebuffers.contains(ref)) - g_pHyprOpenGL->m_windowFramebuffers[ref] = g_pHyprRenderer->createFB(); - - const auto PFRAMEBUFFER = g_pHyprOpenGL->m_windowFramebuffers[ref]; + const auto PFRAMEBUFFER = &g_pHyprOpenGL->m_windowFramebuffers[ref]; PFRAMEBUFFER->alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, DRM_FORMAT_ABGR8888); @@ -2612,10 +2491,7 @@ void CHyprRenderer::makeSnapshot(PHLLS pLayer) { makeEGLCurrent(); - if (!g_pHyprOpenGL->m_layerFramebuffers.contains(pLayer)) - g_pHyprOpenGL->m_layerFramebuffers[pLayer] = g_pHyprRenderer->createFB(); - - const auto PFRAMEBUFFER = g_pHyprOpenGL->m_layerFramebuffers[pLayer]; + const auto PFRAMEBUFFER = &g_pHyprOpenGL->m_layerFramebuffers[pLayer]; PFRAMEBUFFER->alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, DRM_FORMAT_ABGR8888); @@ -2649,10 +2525,7 @@ void CHyprRenderer::makeSnapshot(WP popup) { makeEGLCurrent(); - if (!g_pHyprOpenGL->m_popupFramebuffers.contains(popup)) - g_pHyprOpenGL->m_popupFramebuffers[popup] = g_pHyprRenderer->createFB(); - - const auto PFRAMEBUFFER = g_pHyprOpenGL->m_popupFramebuffers[popup]; + const auto PFRAMEBUFFER = &g_pHyprOpenGL->m_popupFramebuffers[popup]; PFRAMEBUFFER->alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, DRM_FORMAT_ABGR8888); @@ -2701,7 +2574,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; @@ -2757,7 +2630,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; @@ -2801,7 +2674,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; @@ -2830,16 +2703,6 @@ 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; @@ -2863,92 +2726,3 @@ 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 bd14c219..f2377b3b 100644 --- a/src/render/Renderer.hpp +++ b/src/render/Renderer.hpp @@ -1,10 +1,7 @@ #pragma once #include "../defines.hpp" -#include -#include #include -#include #include "../helpers/Monitor.hpp" #include "../desktop/view/LayerSurface.hpp" #include "OpenGL.hpp" @@ -13,8 +10,6 @@ #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; @@ -22,20 +17,6 @@ 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, @@ -56,34 +37,15 @@ 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(); @@ -109,8 +71,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(CGLRenderbuffer* rb); - SP getCurrentRBO(); + void onRenderbufferDestroy(CRenderbuffer* rb); + SP getCurrentRBO(); bool isNvidia(); bool isIntel(); bool isSoftware(); @@ -125,12 +87,9 @@ 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 = {}, SP fb = nullptr, bool simple = false); + bool beginRender(PHLMONITOR pMonitor, CRegion& damage, eRenderMode mode = RENDER_MODE_NORMAL, SP buffer = {}, CFramebuffer* fb = nullptr, bool simple = false); void endRender(const std::function& renderingDoneCallback = {}); bool m_bBlockSurfaceFeedback = false; @@ -158,21 +117,7 @@ class CHyprRenderer { std::string name; } m_lastCursorData; - 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 = ""); + CRenderPass m_renderPass = {}; private: void arrangeLayerArray(PHLMONITOR, const std::vector&, bool, CBox*); @@ -199,7 +144,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; @@ -214,14 +159,13 @@ 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 ead841a5..5081d4c4 100644 --- a/src/render/Shader.cpp +++ b/src/render/Shader.cpp @@ -1,5 +1,4 @@ #include "Shader.hpp" -#include "../config/ConfigManager.hpp" #include "render/OpenGL.hpp" #define EPSILON(x, y) (std::abs((x) - (y)) < 1e-5f) @@ -15,235 +14,51 @@ static bool compareFloat(auto a, auto b) { return true; } -CShader::CShader() { - m_uniformLocations.fill(-1); +SShader::SShader() { + uniformLocations.fill(-1); } -CShader::~CShader() { +SShader::~SShader() { destroy(); } -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; +void SShader::createVao() { + GLuint shaderVao = 0, shaderVbo = 0, shaderVboUv = 0; glGenVertexArrays(1, &shaderVao); glBindVertexArray(shaderVao); - if (m_uniformLocations[SHADER_POS_ATTRIB] != -1) { + if (uniformLocations[SHADER_POS_ATTRIB] != -1) { glGenBuffers(1, &shaderVbo); glBindBuffer(GL_ARRAY_BUFFER, shaderVbo); - 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)); + 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); } // UV VBO (dynamic, may be updated per frame) - 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)); + 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); } glBindVertexArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); - m_uniformLocations[SHADER_SHADER_VAO] = shaderVao; - m_uniformLocations[SHADER_SHADER_VBO] = shaderVbo; + uniformLocations[SHADER_SHADER_VAO] = shaderVao; + uniformLocations[SHADER_SHADER_VBO_POS] = shaderVbo; + uniformLocations[SHADER_SHADER_VBO_UV] = shaderVboUv; - 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"); + 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"); } -void CShader::setUniformInt(eShaderUniform location, GLint v0) { - if (m_uniformLocations.at(location) == -1) +void SShader::setUniformInt(eShaderUniform location, GLint v0) { + if (uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -252,12 +67,11 @@ void CShader::setUniformInt(eShaderUniform location, GLint v0) { return; cached = v0; - - GLCALL(glUniform1i(m_uniformLocations[location], v0)); + glUniform1i(uniformLocations[location], v0); } -void CShader::setUniformFloat(eShaderUniform location, GLfloat v0) { - if (m_uniformLocations.at(location) == -1) +void SShader::setUniformFloat(eShaderUniform location, GLfloat v0) { + if (uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -269,11 +83,11 @@ void CShader::setUniformFloat(eShaderUniform location, GLfloat v0) { } cached = v0; - GLCALL(glUniform1f(m_uniformLocations[location], v0)); + glUniform1f(uniformLocations[location], v0); } -void CShader::setUniformFloat2(eShaderUniform location, GLfloat v0, GLfloat v1) { - if (m_uniformLocations.at(location) == -1) +void SShader::setUniformFloat2(eShaderUniform location, GLfloat v0, GLfloat v1) { + if (uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -285,11 +99,11 @@ void CShader::setUniformFloat2(eShaderUniform location, GLfloat v0, GLfloat v1) } cached = std::array{v0, v1}; - GLCALL(glUniform2f(m_uniformLocations[location], v0, v1)); + glUniform2f(uniformLocations[location], v0, v1); } -void CShader::setUniformFloat3(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2) { - if (m_uniformLocations.at(location) == -1) +void SShader::setUniformFloat3(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2) { + if (uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -301,11 +115,11 @@ void CShader::setUniformFloat3(eShaderUniform location, GLfloat v0, GLfloat v1, } cached = std::array{v0, v1, v2}; - GLCALL(glUniform3f(m_uniformLocations[location], v0, v1, v2)); + glUniform3f(uniformLocations[location], v0, v1, v2); } -void CShader::setUniformFloat4(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) { - if (m_uniformLocations.at(location) == -1) +void SShader::setUniformFloat4(eShaderUniform location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) { + if (uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -317,11 +131,11 @@ void CShader::setUniformFloat4(eShaderUniform location, GLfloat v0, GLfloat v1, } cached = std::array{v0, v1, v2, v3}; - GLCALL(glUniform4f(m_uniformLocations[location], v0, v1, v2, v3)); + glUniform4f(uniformLocations[location], v0, v1, v2, v3); } -void CShader::setUniformMatrix3fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value) { - if (m_uniformLocations.at(location) == -1) +void SShader::setUniformMatrix3fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value) { + if (uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -333,11 +147,11 @@ void CShader::setUniformMatrix3fv(eShaderUniform location, GLsizei count, GLbool } cached = SUniformMatrix3Data{.count = count, .transpose = transpose, .value = value}; - GLCALL(glUniformMatrix3fv(m_uniformLocations[location], count, transpose, value.data())); + glUniformMatrix3fv(uniformLocations[location], count, transpose, value.data()); } -void CShader::setUniformMatrix4x2fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value) { - if (m_uniformLocations.at(location) == -1) +void SShader::setUniformMatrix4x2fv(eShaderUniform location, GLsizei count, GLboolean transpose, std::array value) { + if (uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -349,11 +163,11 @@ void CShader::setUniformMatrix4x2fv(eShaderUniform location, GLsizei count, GLbo } cached = SUniformMatrix4Data{.count = count, .transpose = transpose, .value = value}; - GLCALL(glUniformMatrix4x2fv(m_uniformLocations[location], count, transpose, value.data())); + glUniformMatrix4x2fv(uniformLocations[location], count, transpose, value.data()); } -void CShader::setUniformfv(eShaderUniform location, GLsizei count, const std::vector& value, GLsizei vec_size) { - if (m_uniformLocations.at(location) == -1) +void SShader::setUniformfv(eShaderUniform location, GLsizei count, const std::vector& value, GLsizei vec_size) { + if (uniformLocations.at(location) == -1) return; auto& cached = uniformStatus.at(location); @@ -366,35 +180,36 @@ void CShader::setUniformfv(eShaderUniform location, GLsizei count, const std::ve cached = SUniformVData{.count = count, .value = value}; switch (vec_size) { - 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; + 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; default: UNREACHABLE(); } } -void CShader::setUniform1fv(eShaderUniform location, GLsizei count, const std::vector& value) { +void SShader::setUniform1fv(eShaderUniform location, GLsizei count, const std::vector& value) { setUniformfv(location, count, value, 1); } -void CShader::setUniform2fv(eShaderUniform location, GLsizei count, const std::vector& value) { +void SShader::setUniform2fv(eShaderUniform location, GLsizei count, const std::vector& value) { setUniformfv(location, count, value, 2); } -void CShader::setUniform4fv(eShaderUniform location, GLsizei count, const std::vector& value) { +void SShader::setUniform4fv(eShaderUniform location, GLsizei count, const std::vector& value) { setUniformfv(location, count, value, 4); } -void CShader::destroy() { +void SShader::destroy() { uniformStatus.fill(std::monostate()); - if (m_program == 0) + if (program == 0) return; - GLuint shaderVao, shaderVbo; + GLuint shaderVao, shaderVbo, shaderVboUv; - 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]; + 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]; if (shaderVao) glDeleteVertexArrays(1, &shaderVao); @@ -402,22 +217,9 @@ void CShader::destroy() { if (shaderVbo) glDeleteBuffers(1, &shaderVbo); - glDeleteProgram(m_program); - m_program = 0; -} + if (shaderVboUv) + glDeleteBuffers(1, &shaderVboUv); -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; + glDeleteProgram(program); + program = 0; } diff --git a/src/render/Shader.hpp b/src/render/Shader.hpp index 9b097c44..50ff5889 100644 --- a/src/render/Shader.hpp +++ b/src/render/Shader.hpp @@ -9,11 +9,12 @@ 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_XYZ, + SHADER_TARGET_PRIMARIES, SHADER_MAX_LUMINANCE, SHADER_SRC_REF_LUMINANCE, SHADER_DST_MAX_LUMINANCE, @@ -30,7 +31,8 @@ enum eShaderUniform : uint8_t { SHADER_DISCARD_ALPHA, SHADER_DISCARD_ALPHA_VALUE, SHADER_SHADER_VAO, - SHADER_SHADER_VBO, + SHADER_SHADER_VBO_POS, + SHADER_SHADER_VBO_UV, SHADER_TOP_LEFT, SHADER_BOTTOM_RIGHT, SHADER_FULL_SIZE, @@ -74,41 +76,19 @@ 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, }; -class CShader { - public: - CShader(); - ~CShader(); +struct SShader { + SShader(); + ~SShader(); - 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); + GLuint program = 0; - private: - GLuint m_program = 0; - float m_initialTime = 0; - std::array m_uniformLocations; + std::array uniformLocations; + + float initialTime = 0; struct SUniformMatrix3Data { GLsizei count = 0; @@ -134,9 +114,19 @@ class CShader { uniformStatus; // - 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); + 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); }; diff --git a/src/render/ShaderLoader.cpp b/src/render/ShaderLoader.cpp deleted file mode 100644 index 0d2d0ee4..00000000 --- a/src/render/ShaderLoader.cpp +++ /dev/null @@ -1,176 +0,0 @@ -#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 deleted file mode 100644 index e522e9fa..00000000 --- a/src/render/ShaderLoader.hpp +++ /dev/null @@ -1,77 +0,0 @@ -#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 28ae4b41..f1704afa 100644 --- a/src/render/Texture.cpp +++ b/src/render/Texture.cpp @@ -1,24 +1,207 @@ #include "Texture.hpp" +#include "Renderer.hpp" +#include "../Compositor.hpp" +#include "../protocols/types/Buffer.hpp" +#include "../helpers/Format.hpp" #include -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); +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(std::span lut3D, size_t N) : m_type(TEXTURE_3D_LUT), m_size(lut3D.size() / 3, 1), m_isSynchronous(true) {} +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; + } -bool ITexture::ok() { - return false; + 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::isDMA() { - 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); + } } -const std::vector& ITexture::dataCopy() { +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() { 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 38c3ff01..b9811230 100644 --- a/src/render/Texture.hpp +++ b/src/render/Texture.hpp @@ -3,7 +3,6 @@ #include "../defines.hpp" #include #include -#include class IHLBuffer; HYPRUTILS_FORWARD(Math, CRegion); @@ -12,47 +11,59 @@ 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 ITexture { +class CTexture { public: - ITexture(ITexture&) = delete; - ITexture(ITexture&&) = delete; - ITexture(const ITexture&&) = delete; - ITexture(const ITexture&) = delete; + CTexture(); - virtual ~ITexture() = default; + CTexture(CTexture&) = delete; + CTexture(CTexture&&) = delete; + CTexture(const CTexture&&) = delete; + CTexture(const CTexture&) = delete; - 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(uint32_t drmFormat, uint8_t* pixels, uint32_t stride, const Vector2D& size, bool keepDataCopy = false); + 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; - Vector2D m_size = {}; - eTransform m_transform = HYPRUTILS_TRANSFORM_NORMAL; - bool m_opaque = false; - + 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; uint32_t m_drmFormat = 0; // for shm bool m_isSynchronous = false; - // TODO move to GLTexture - GLuint m_texID = 0; - GLenum magFilter = GL_LINEAR; // useNearestNeighbor overwrites these - GLenum minFilter = GL_LINEAR; + 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, + }; - 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); + 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); - bool m_keepDataCopy = false; - std::vector m_dataCopy; + bool m_keepDataCopy = false; + std::vector m_dataCopy; + std::array, TEXTURE_PAR_LAST> m_cachedStates; }; diff --git a/src/render/Transformer.hpp b/src/render/Transformer.hpp index 8f401859..048b1898 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 IFramebuffer* transform(IFramebuffer* in) = 0; + virtual CFramebuffer* transform(CFramebuffer* 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 3e4f04a9..a082f073 100644 --- a/src/render/decorations/CHyprBorderDecoration.cpp +++ b/src/render/decorations/CHyprBorderDecoration.cpp @@ -4,6 +4,7 @@ #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) { ; @@ -114,15 +115,26 @@ void CHyprBorderDecoration::updateWindow(PHLWINDOW) { } void CHyprBorderDecoration::damageEntire() { - if (!validMapped(m_window) || m_window->m_fullscreenState.internal == FSMODE_FULLSCREEN) + if (!validMapped(m_window)) return; - const auto GLOBAL_BOX = assignedBoxGlobal(); - const auto ROUNDING = m_window->rounding(); - const auto BORDERSIZE = m_window->getRealBorderSize() + 1; + 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; - CRegion borderRegion(GLOBAL_BOX); - borderRegion.subtract(GLOBAL_BOX.copy().expand(-(BORDERSIZE + ROUNDING))); + 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); 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 5e1b6e8a..dd82abc5 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 - auto alphaFB = g_pHyprOpenGL->m_renderData.pCurrentMonData->mirrorFB; - auto alphaSwapFB = g_pHyprOpenGL->m_renderData.pCurrentMonData->mirrorSwapFB; - auto LASTFB = g_pHyprOpenGL->m_renderData.currentFB; + CFramebuffer& alphaFB = g_pHyprOpenGL->m_renderData.pCurrentMonData->mirrorFB; + CFramebuffer& 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 6ce69261..93a17341 100644 --- a/src/render/decorations/CHyprGroupBarDecoration.cpp +++ b/src/render/decorations/CHyprGroupBarDecoration.cpp @@ -2,21 +2,19 @@ #include "../../Compositor.hpp" #include "../../config/ConfigValue.hpp" #include "../../desktop/state/FocusState.hpp" -#include "../../desktop/view/Group.hpp" +#include "managers/LayoutManager.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; -static SP m_tGradientInactive; -static SP m_tGradientLockedActive; -static SP m_tGradientLockedInactive; +static SP m_tGradientActive = makeShared(); +static SP m_tGradientInactive = makeShared(); +static SP m_tGradientLockedActive = makeShared(); +static SP m_tGradientLockedInactive = makeShared(); constexpr int BAR_TEXT_PAD = 2; @@ -24,16 +22,7 @@ CHyprGroupBarDecoration::CHyprGroupBarDecoration(PHLWINDOW pWindow) : IHyprWindo static auto PGRADIENTS = CConfigValue("group:groupbar:enabled"); static auto PENABLED = CConfigValue("group:groupbar:gradients"); - 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) + if (m_tGradientActive->m_texID == 0 && *PENABLED && *PGRADIENTS) refreshGroupBarGradients(); } @@ -76,14 +65,19 @@ eDecorationType CHyprGroupBarDecoration::getDecorationType() { // void CHyprGroupBarDecoration::updateWindow(PHLWINDOW pWindow) { - if (!m_window->m_group) { + if (m_window->m_groupData.pNextWindow.expired()) { m_window->removeWindowDeco(this); return; } m_dwGroupMembers.clear(); - for (const auto& w : m_window->m_group->windows()) { - m_dwGroupMembers.emplace_back(w); + 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(); } damageEntire(); @@ -133,7 +127,6 @@ 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()); @@ -164,7 +157,7 @@ void CHyprGroupBarDecoration::draw(PHLMONITOR pMonitor, float const& a) { rect.scale(pMonitor->m_scale).round(); - const bool GROUPLOCKED = m_window->m_group->locked() || g_pKeybindManager->m_groupsLocked; + const bool GROUPLOCKED = m_window->getGroupHead()->m_groupData.locked || g_pKeybindManager->m_groupsLocked; const auto* const PCOLACTIVE = GROUPLOCKED ? GROUPCOLACTIVELOCKED : GROUPCOLACTIVE; const auto* const PCOLINACTIVE = GROUPLOCKED ? GROUPCOLINACTIVELOCKED : GROUPCOLINACTIVE; @@ -205,12 +198,11 @@ 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->ok()) { + if (GRADIENTTEX->m_texID) { CTexPassElement::SRenderData data; data.tex = GRADIENTTEX; data.blur = blur; data.box = rect; - data.a = a; if (*PGRADIENTROUNDING) { data.round = *PGRADIENTROUNDING; data.roundingPower = *PGRADIENTROUNDINGPOWER; @@ -236,14 +228,13 @@ 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 - (*PTEXTPADDING * 2)) * 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 * 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 @@ -252,7 +243,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 + *PTEXTPADDING) * pMonitor->m_scale) / 2.0) - ((titleTex->m_size.x + *PTEXTPADDING) / 2.0)); + rect.x += std::round(((m_barWidth * pMonitor->m_scale) / 2.0) - (titleTex->m_size.x / 2.0)); rect.round(); CTexPassElement::SRenderData data; @@ -316,7 +307,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; @@ -348,7 +339,15 @@ static void renderGradientTo(SP tex, CGradientValueData* grad) { cairo_surface_flush(CAIROSURFACE); // copy the data to an OpenGL texture we have - tex = g_pHyprRenderer->createTexture(CAIROSURFACE); + 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); // delete cairo cairo_destroy(CAIRO); @@ -368,11 +367,13 @@ void refreshGroupBarGradients() { auto* const GROUPCOLACTIVELOCKED = sc((PGROUPCOLACTIVELOCKED.ptr())->getData()); auto* const GROUPCOLINACTIVELOCKED = sc((PGROUPCOLINACTIVELOCKED.ptr())->getData()); - if (m_tGradientActive && m_tGradientActive->ok()) { - m_tGradientActive.reset(); - m_tGradientInactive.reset(); - m_tGradientLockedActive.reset(); - m_tGradientLockedInactive.reset(); + g_pHyprRenderer->makeEGLCurrent(); + + if (m_tGradientActive->m_texID != 0) { + m_tGradientActive->destroyTexture(); + m_tGradientInactive->destroyTexture(); + m_tGradientLockedActive->destroyTexture(); + m_tGradientLockedInactive->destroyTexture(); } if (!*PENABLED || !*PGRADIENTS) @@ -388,7 +389,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->m_group->size() == 1) + if (m_window.lock() == m_window->m_groupData.pNextWindow.lock()) return false; const float BARRELATIVEX = pos.x - assignedBoxGlobal().x; @@ -401,33 +402,95 @@ bool CHyprGroupBarDecoration::onBeginWindowDragOnDeco(const Vector2D& pos) { if (*PSTACKED && (BARRELATIVEY - (m_barHeight + *POUTERGAP) * WINDOWINDEX < *POUTERGAP)) return false; - PHLWINDOW pWindow = m_window->m_group->fromIndex(WINDOWINDEX); + PHLWINDOW pWindow = m_window->getGroupWindowByIndex(WINDOWINDEX); - const auto& GROUP = m_window->m_group; + // 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; + } - // remove the window from the group - GROUP->remove(pWindow); - - // start a move drag on it - g_layoutManager->dragController()->dragBegin(pWindow->layoutTarget(), MBIND_MOVE); + g_pInputManager->m_currentlyDraggedWindow = pWindow; if (!g_pCompositor->isWindowActive(pWindow)) - Desktop::focusState()->rawWindowFocus(pWindow, Desktop::FOCUS_REASON_CLICK); + Desktop::focusState()->rawWindowFocus(pWindow); 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"); - const bool FLOATEDINTOTILED = !m_window->m_isFloating && !g_layoutManager->dragController()->draggingTiled(); + 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; - if (!pDraggedWindow->canBeGroupedInto(m_window->m_group) || (*PDRAGINTOGROUP != 1 && *PDRAGINTOGROUP != 2) || (FLOATEDINTOTILED && !*PMERGEFLOATEDINTOTILEDONGROUPBAR) || - (!*PMERGEGROUPSONGROUPBAR && pDraggedWindow->m_group)) + 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; return false; + } - m_window->m_group->add(pDraggedWindow); + 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); if (!pDraggedWindow->getDecorationByType(DECORATION_GROUPBAR)) pDraggedWindow->addWindowDeco(makeUnique(pDraggedWindow)); @@ -454,7 +517,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->m_group->fromIndex(WINDOWINDEX)); + g_pXWaylandManager->sendCloseWindow(m_window->getGroupWindowByIndex(WINDOWINDEX)); return true; } @@ -467,17 +530,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::FOCUS_REASON_CLICK); + Desktop::focusState()->rawWindowFocus(m_window.lock()); return true; } - PHLWINDOW pWindow = m_window->m_group->fromIndex(WINDOWINDEX); + PHLWINDOW pWindow = m_window->getGroupWindowByIndex(WINDOWINDEX); if (pWindow != m_window) - pWindow->m_group->setCurrent(pWindow); + pWindow->setGroupCurrent(pWindow); if (!g_pCompositor->isWindowActive(pWindow) && *PFOLLOWMOUSE != 3) - Desktop::focusState()->rawWindowFocus(pWindow, Desktop::FOCUS_REASON_CLICK); + Desktop::focusState()->rawWindowFocus(pWindow); if (pWindow->m_isFloating) g_pCompositor->changeWindowZOrder(pWindow, true); @@ -488,13 +551,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_group) + if (!*PGROUPBARSCROLLING || m_window->m_groupData.pNextWindow.expired()) return false; if (e.delta > 0) - m_window->m_group->moveCurrent(true); + m_window->setGroupCurrent(m_window->m_groupData.pNextWindow.lock()); else - m_window->m_group->moveCurrent(false); + m_window->setGroupCurrent(m_window->getGroupPrevious()); return true; } diff --git a/src/render/decorations/CHyprGroupBarDecoration.hpp b/src/render/decorations/CHyprGroupBarDecoration.hpp index 5c3f4ae5..3e5d3c2d 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 470b5bb7..aa849bab 100644 --- a/src/render/decorations/DecorationPositioner.cpp +++ b/src/render/decorations/DecorationPositioner.cpp @@ -1,11 +1,18 @@ #include "DecorationPositioner.hpp" #include "../../desktop/view/Window.hpp" -#include "../../layout/target/Target.hpp" -#include "../../event/EventBus.hpp" +#include "../../managers/HookSystemManager.hpp" +#include "../../managers/LayoutManager.hpp" CDecorationPositioner::CDecorationPositioner() { - 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); }); + 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); + }); } Vector2D CDecorationPositioner::getEdgeDefinedPoint(uint32_t edges, PHLWINDOWREF pWindow) { @@ -207,23 +214,29 @@ void CDecorationPositioner::onWindowUpdate(PHLWINDOW pWindow) { continue; } - const auto desiredExtents = wd->positioningInfo.desiredExtents; + 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 EDGEPOINT = getEdgeDefinedPoint(wd->positioningInfo.edges, pWindow); Vector2D pos, size; if (EDGESNO == 4) { - stickyOffsetXL += desiredExtents.topLeft.x; - stickyOffsetXR += desiredExtents.bottomRight.x; - stickyOffsetYT += desiredExtents.topLeft.y; - stickyOffsetYB += desiredExtents.bottomRight.y; + pos = wb.pos() - EDGEPOINT - Vector2D{stickyOffsetXL + desiredSize, stickyOffsetYT + desiredSize}; + size = wb.size() + Vector2D{stickyOffsetXL + stickyOffsetXR + desiredSize * 2, stickyOffsetYB + stickyOffsetYT + desiredSize * 2}; - pos = wb.pos() - EDGEPOINT - Vector2D{stickyOffsetXL, stickyOffsetYT}; - size = wb.size() + Vector2D{stickyOffsetXL + stickyOffsetXR, stickyOffsetYB + stickyOffsetYT}; + stickyOffsetXL += desiredSize; + stickyOffsetXR += desiredSize; + stickyOffsetYT += desiredSize; + stickyOffsetYB += desiredSize; } 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}; @@ -231,16 +244,12 @@ 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)}; @@ -248,8 +257,6 @@ 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)}; @@ -271,7 +278,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}}; - pWindow->layoutTarget()->recalc(); + g_pLayoutManager->getCurrentLayout()->recalculateWindow(pWindow); } } diff --git a/src/render/gl/GLFramebuffer.cpp b/src/render/gl/GLFramebuffer.cpp deleted file mode 100644 index d821f766..00000000 --- a/src/render/gl/GLFramebuffer.cpp +++ /dev/null @@ -1,170 +0,0 @@ -#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 deleted file mode 100644 index c171444e..00000000 --- a/src/render/gl/GLFramebuffer.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#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 deleted file mode 100644 index 8299d0e4..00000000 --- a/src/render/gl/GLRenderbuffer.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#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 deleted file mode 100644 index 8367f702..00000000 --- a/src/render/gl/GLRenderbuffer.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#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 deleted file mode 100644 index 6a1fb172..00000000 --- a/src/render/gl/GLTexture.cpp +++ /dev/null @@ -1,223 +0,0 @@ -#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 deleted file mode 100644 index 34510e90..00000000 --- a/src/render/gl/GLTexture.hpp +++ /dev/null @@ -1,49 +0,0 @@ -#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 bc7c686a..77a29fba 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) { - SP fb = nullptr; + CFramebuffer* 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 a4436516..3910e6a7 100644 --- a/src/render/pass/Pass.cpp +++ b/src/render/pass/Pass.cpp @@ -55,16 +55,7 @@ void CRenderPass::simplify() { auto opaque = el->element->opaqueRegion(); if (!opaque.empty()) { - // 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; - } + opaque.scale(g_pHyprOpenGL->m_renderData.pMonitor->m_scale); // if this intersects the liveBlur region, allow live blur to operate correctly. // do not occlude a border near it. @@ -171,7 +162,7 @@ CRegion CRenderPass::render(const CRegion& damage_) { } else g_pHyprOpenGL->m_renderData.finalDamage = m_damage; - if (g_pHyprOpenGL->m_renderData.noSimplify || std::ranges::any_of(m_passElements, [](const auto& el) { return el->element->disableSimplification(); })) { + if (std::ranges::any_of(m_passElements, [](const auto& el) { return el->element->disableSimplification(); })) { for (auto& el : m_passElements) { el->elementDamage = m_damage; } @@ -216,7 +207,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 b45af88b..435b5301 100644 --- a/src/render/pass/Pass.hpp +++ b/src/render/pass/Pass.hpp @@ -4,7 +4,7 @@ #include "PassElement.hpp" class CGradientValueData; -class ITexture; +class CTexture; 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 c5feb8f7..d3027290 100644 --- a/src/render/pass/SurfacePassElement.cpp +++ b/src/render/pass/SurfacePassElement.cpp @@ -5,7 +5,6 @@ #include "../../protocols/core/Compositor.hpp" #include "../../protocols/DRMSyncobj.hpp" #include "../../managers/input/InputManager.hpp" -#include "../../layout/LayoutManager.hpp" #include "../Renderer.hpp" #include @@ -52,7 +51,7 @@ void CSurfacePassElement::draw(const CRegion& damage) { if (!TEXTURE->m_texID) return; - const auto INTERACTIVERESIZEINPROGRESS = m_data.pWindow && g_layoutManager->dragController()->target() && g_layoutManager->dragController()->mode() == MBIND_RESIZE; + const auto INTERACTIVERESIZEINPROGRESS = m_data.pWindow && g_pInputManager->m_currentlyDraggedWindow && g_pInputManager->m_dragMode == MBIND_RESIZE; TRACY_GPU_ZONE("RenderSurface"); auto PSURFACE = Desktop::View::CWLSurface::fromResource(m_data.surface); @@ -104,7 +103,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); @@ -164,7 +163,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_layoutManager->dragController()->target() && g_layoutManager->dragController()->mode() == MBIND_RESIZE; + const auto INTERACTIVERESIZEINPROGRESS = m_data.pWindow && g_pInputManager->m_currentlyDraggedWindow && g_pInputManager->m_dragMode == 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 058744de..f4dbb45a 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 ITexture; +class CTexture; 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 770e8b05..a922843d 100644 --- a/src/render/pass/TexPassElement.hpp +++ b/src/render/pass/TexPassElement.hpp @@ -3,13 +3,13 @@ #include class CWLSurfaceResource; -class ITexture; +class CTexture; 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 8023df8b..aeeeabc6 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 273c6474..57d0e1e3 100644 --- a/src/render/pass/TextureMatteElement.hpp +++ b/src/render/pass/TextureMatteElement.hpp @@ -2,14 +2,14 @@ #include "PassElement.hpp" #include "../Framebuffer.hpp" -class ITexture; +class CTexture; 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 new file mode 100644 index 00000000..031fe7f3 --- /dev/null +++ b/src/render/shaders/glsl/CM.frag @@ -0,0 +1,54 @@ +#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 323a3008..362d7cfb 100644 --- a/src/render/shaders/glsl/CM.glsl +++ b/src/render/shaders/glsl/CM.glsl @@ -1,27 +1,418 @@ -#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 vec2 srcTFRange; -uniform vec2 dstTFRange; - -uniform float srcRefLuminance; -uniform mat3 convertMatrix; - -#if USE_ICC -uniform highp sampler3D iccLut3D; -uniform float iccLutSize; -#endif - -#if USE_SDR_MOD -uniform float sdrSaturation; -uniform float sdrBrightnessMultiplier; -#endif - -#if USE_TONEMAP uniform float maxLuminance; +uniform float srcRefLuminance; uniform float dstMaxLuminance; uniform float dstRefLuminance; -#endif +uniform float sdrSaturation; +uniform float sdrBrightnessMultiplier; +uniform mat3 convertMatrix; + +//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; +} diff --git a/src/render/shaders/glsl/blur1.frag b/src/render/shaders/glsl/blur1.frag index 044df3cc..796fb42d 100644 --- a/src/render/shaders/glsl/blur1.frag +++ b/src/render/shaders/glsl/blur1.frag @@ -1,21 +1,143 @@ #version 300 es -#define ALLOW_INCLUDES -#extension GL_ARB_shading_language_include : enable +precision highp float; +uniform sampler2D tex; -precision highp float; -uniform sampler2D tex; +uniform float radius; +uniform vec2 halfpixel; +uniform int passes; +uniform float vibrancy; +uniform float vibrancy_darkness; -uniform float radius; -uniform vec2 halfpixel; -uniform int passes; -uniform float vibrancy; -uniform float vibrancy_darkness; +in vec2 v_texcoord; -in vec2 v_texcoord; -layout(location = 0) out vec4 fragColor; +// see http://alienryderflex.com/hsp.html +const float Pr = 0.299; +const float Pg = 0.587; +const float Pb = 0.114; -#include "blur1.glsl" +// 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 +// -void main() { - fragColor = blur1(v_texcoord, tex, radius, halfpixel, passes, vibrancy, vibrancy_darkness); +// 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; +} + +layout(location = 0) out vec4 fragColor; +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]); + } } diff --git a/src/render/shaders/glsl/blur1.glsl b/src/render/shaders/glsl/blur1.glsl deleted file mode 100644 index 36e7d660..00000000 --- a/src/render/shaders/glsl/blur1.glsl +++ /dev/null @@ -1,130 +0,0 @@ -// 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 62caae56..bfe448d5 100644 --- a/src/render/shaders/glsl/blur2.frag +++ b/src/render/shaders/glsl/blur2.frag @@ -1,18 +1,25 @@ #version 300 es -#define ALLOW_INCLUDES -#extension GL_ARB_shading_language_include : enable - -precision highp float; +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() { - fragColor = blur2(v_texcoord, tex, radius, 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; + + fragColor = sum / 12.0; } diff --git a/src/render/shaders/glsl/blur2.glsl b/src/render/shaders/glsl/blur2.glsl deleted file mode 100644 index e73e90e3..00000000 --- a/src/render/shaders/glsl/blur2.glsl +++ /dev/null @@ -1,15 +0,0 @@ -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 deleted file mode 100644 index f3d225c3..00000000 --- a/src/render/shaders/glsl/blurFinish.glsl +++ /dev/null @@ -1,17 +0,0 @@ -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 0342646b..6ab48337 100644 --- a/src/render/shaders/glsl/blurfinish.frag +++ b/src/render/shaders/glsl/blurfinish.frag @@ -1,19 +1,32 @@ #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; -#include "blurFinish.glsl" +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); +} layout(location = 0) out vec4 fragColor; void main() { vec4 pixColor = texture(tex, v_texcoord); - fragColor = blurFinish(pixColor, v_texcoord, noise, brightness); + // 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; } diff --git a/src/render/shaders/glsl/blurprepare.frag b/src/render/shaders/glsl/blurprepare.frag index e96c54bb..6b9809f8 100644 --- a/src/render/shaders/glsl/blurprepare.frag +++ b/src/render/shaders/glsl/blurprepare.frag @@ -1,38 +1,48 @@ #version 300 es -#define ALLOW_INCLUDES #extension GL_ARB_shading_language_include : enable -#include "defines.h" - -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 contrast; -uniform float brightness; +uniform float contrast; +uniform float brightness; -uniform int sourceTF; // eTransferFunction -uniform int targetTF; // eTransferFunction +uniform int skipCM; +uniform int sourceTF; // eTransferFunction +uniform int targetTF; // eTransferFunction -#if USE_CM -uniform vec2 srcTFRange; -uniform vec2 dstTFRange; +#include "CM.glsl" -uniform float srcRefLuminance; -uniform mat3 convertMatrix; - -uniform float sdrBrightnessMultiplier; -#include "cm_helpers.glsl" -#endif - -#include "blurprepare.glsl" +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; +} layout(location = 0) out vec4 fragColor; void main() { - fragColor = fragColor = blurPrepare(texture(tex, v_texcoord), contrast, brightness -#if USE_CM - , - sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange, srcRefLuminance, sdrBrightnessMultiplier -#endif - ); + 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; } diff --git a/src/render/shaders/glsl/blurprepare.glsl b/src/render/shaders/glsl/blurprepare.glsl deleted file mode 100644 index e4a0daad..00000000 --- a/src/render/shaders/glsl/blurprepare.glsl +++ /dev/null @@ -1,37 +0,0 @@ -#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 151593c1..223b4b29 100644 --- a/src/render/shaders/glsl/border.frag +++ b/src/render/shaders/glsl/border.frag @@ -1,60 +1,182 @@ #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 sourceTF; // eTransferFunction -uniform int targetTF; // eTransferFunction -uniform mat3 targetPrimariesXYZ; +uniform int skipCM; +uniform int sourceTF; // eTransferFunction +uniform int targetTF; // eTransferFunction +uniform mat4x2 targetPrimaries; -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" -#include "border.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)); +} layout(location = 0) out vec4 fragColor; void main() { - 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 - ); + 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; } diff --git a/src/render/shaders/glsl/border.glsl b/src/render/shaders/glsl/border.glsl deleted file mode 100644 index fa2a6980..00000000 --- a/src/render/shaders/glsl/border.glsl +++ /dev/null @@ -1,203 +0,0 @@ -#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 deleted file mode 100644 index 5e0d14f6..00000000 --- a/src/render/shaders/glsl/cm_helpers.glsl +++ /dev/null @@ -1,248 +0,0 @@ -#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 deleted file mode 100644 index bbab5284..00000000 --- a/src/render/shaders/glsl/constants.h +++ /dev/null @@ -1,62 +0,0 @@ -#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 deleted file mode 100644 index 31b120a4..00000000 --- a/src/render/shaders/glsl/defines.h +++ /dev/null @@ -1,10 +0,0 @@ -// 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 1c614bd3..f540a9f9 100644 --- a/src/render/shaders/glsl/ext.frag +++ b/src/render/shaders/glsl/ext.frag @@ -1,43 +1,38 @@ #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 tex; -uniform float alpha; +precision highp float; +in vec2 v_texcoord; +uniform samplerExternalOES texture0; +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(tex, v_texcoord); + vec4 pixColor = texture(texture0, 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, radius, roundingPower, topLeft, fullSize); + pixColor = rounding(pixColor); fragColor = pixColor * alpha; } diff --git a/src/render/shaders/glsl/gain.glsl b/src/render/shaders/glsl/gain.glsl deleted file mode 100644 index 2bdc0002..00000000 --- a/src/render/shaders/glsl/gain.glsl +++ /dev/null @@ -1,6 +0,0 @@ -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 d7259cc4..e399a8b1 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 fullSize; +uniform vec2 screenSize; 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) / fullSize.y; + float MELT_AMOUNT = (distort * 8.0) / screenSize.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 * fullSize.x * 17.719)) * 281.882, 1.0)); + float meltSeed = abs(mod(rand(floor(v_texcoord.x * screenSize.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 / fullSize.x + blockOffset.x, v_texcoord.y - meltAmount + 0.02 * NOISE / fullSize.x + NOISE * 3.0 / fullSize.y + blockOffset.y); + 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); vec4 pixColor = texture(tex, pixCoord); - vec4 pixColorLeft = texture(tex, pixCoord + vec2(ABERR_OFFSET / fullSize.x, 0)); - vec4 pixColorRight = texture(tex, pixCoord + vec2(-ABERR_OFFSET / fullSize.x, 0)); + vec4 pixColorLeft = texture(tex, pixCoord + vec2(ABERR_OFFSET / screenSize.x, 0)); + vec4 pixColorRight = texture(tex, pixCoord + vec2(-ABERR_OFFSET / screenSize.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 61895a60..5dae493e 100644 --- a/src/render/shaders/glsl/quad.frag +++ b/src/render/shaders/glsl/quad.frag @@ -1,27 +1,17 @@ #version 300 es -#define ALLOW_INCLUDES #extension GL_ARB_shading_language_include : enable -#include "defines.h" - precision highp float; -in vec4 v_color; +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 USE_ROUNDING - pixColor = rounding(pixColor, radius, roundingPower, topLeft, fullSize); -#endif + if (radius > 0.0) + pixColor = rounding(pixColor); fragColor = pixColor; } diff --git a/src/render/shaders/glsl/rgba.frag b/src/render/shaders/glsl/rgba.frag new file mode 100644 index 00000000..e4e04500 --- /dev/null +++ b/src/render/shaders/glsl/rgba.frag @@ -0,0 +1,39 @@ +#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 new file mode 100644 index 00000000..84672d76 --- /dev/null +++ b/src/render/shaders/glsl/rgbx.frag @@ -0,0 +1,35 @@ +#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 61a0bb9c..472415fd 100644 --- a/src/render/shaders/glsl/rounding.glsl +++ b/src/render/shaders/glsl/rounding.glsl @@ -1,10 +1,13 @@ -#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) -vec4 rounding(vec4 color, float radius, float roundingPower, vec2 topLeft, vec2 fullSize) { +uniform float radius; +uniform float roundingPower; +uniform vec2 topLeft; +uniform vec2 fullSize; + +vec4 rounding(vec4 color) { vec2 pixCoord = vec2(gl_FragCoord); pixCoord -= topLeft + fullSize * 0.5; pixCoord *= vec2(lessThan(pixCoord, vec2(0.0))) * -2.0 + 1.0; @@ -12,7 +15,7 @@ vec4 rounding(vec4 color, float radius, float roundingPower, vec2 topLeft, vec2 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; @@ -24,4 +27,3 @@ vec4 rounding(vec4 color, float radius, float roundingPower, vec2 topLeft, vec2 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 c23ebd5d..b6fdf6ee 100644 --- a/src/render/shaders/glsl/shadow.frag +++ b/src/render/shaders/glsl/shadow.frag @@ -1,57 +1,99 @@ #version 300 es -#define ALLOW_INCLUDES #extension GL_ARB_shading_language_include : enable -#include "defines.h" +precision highp float; +in vec4 v_color; +in vec2 v_texcoord; -precision highp float; -in vec4 v_color; -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 topLeft; -uniform vec2 bottomRight; -uniform vec2 fullSize; +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 -#include "shadow.glsl" +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); +} layout(location = 0) out vec4 fragColor; void main() { - vec4 pixColor = v_color; - 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 - ); + 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; } \ No newline at end of file diff --git a/src/render/shaders/glsl/shadow.glsl b/src/render/shaders/glsl/shadow.glsl deleted file mode 100644 index 48cde562..00000000 --- a/src/render/shaders/glsl/shadow.glsl +++ /dev/null @@ -1,126 +0,0 @@ -#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 deleted file mode 100644 index 30023bc8..00000000 --- a/src/render/shaders/glsl/surface.frag +++ /dev/null @@ -1,104 +0,0 @@ -#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 deleted file mode 100644 index a0ba24ef..00000000 --- a/src/render/shaders/glsl/tonemap.glsl +++ /dev/null @@ -1,64 +0,0 @@ -#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 5c5f3b5c..bc74f54d 100644 --- a/src/xwayland/XSurface.cpp +++ b/src/xwayland/XSurface.cpp @@ -7,6 +7,8 @@ #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) { @@ -40,43 +42,9 @@ 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; @@ -258,19 +226,10 @@ void CXWaylandSurface::restackToTop() { } void CXWaylandSurface::close() { - - // 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()); - } + 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); } void CXWaylandSurface::setWithdrawn(bool withdrawn_) { diff --git a/src/xwayland/XSurface.hpp b/src/xwayland/XSurface.hpp index a8ccac4d..10eecbaf 100644 --- a/src/xwayland/XSurface.hpp +++ b/src/xwayland/XSurface.hpp @@ -11,7 +11,6 @@ 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; @@ -112,7 +111,6 @@ class CXWaylandSurface { void unmap(); void considerMap(); void setWithdrawn(bool withdrawn); - void recheckSupportedProps(); struct { CHyprSignalListener destroyResource; @@ -120,7 +118,5 @@ 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 7e20d9b1..4e7fc5be 100644 --- a/src/xwayland/XWM.cpp +++ b/src/xwayland/XWM.cpp @@ -33,8 +33,6 @@ 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) @@ -68,8 +66,6 @@ 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) @@ -345,17 +341,14 @@ 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) { - removeTransfersForWindow(e->window); + if (!XSURF) 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 for window {}", e->window); - removeTransfersForWindow(e->window); + Log::logger->log(Log::ERR, "[xwm] Failed to read property notify cookie"); return; } @@ -650,33 +643,19 @@ void CXWM::handleSelectionNotify(xcb_selection_notify_event_t* e) { } bool CXWM::handleSelectionPropertyNotify(xcb_property_notify_event_t* e) { - for (auto* sel : {&m_clipboard, &m_primarySelection, &m_dndSelection}) { + if (e->state != XCB_PROPERTY_DELETE) + return false; + + for (auto* sel : {&m_clipboard, &m_primarySelection}) { auto it = std::ranges::find_if(sel->transfers, [e](const auto& t) { return t->incomingWindow == e->window; }); - 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) { + if (it != sel->transfers.end()) { + if (!(*it)->getIncomingSelectionProp(true)) { sel->transfers.erase(it); - return true; + return false; } - - 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; @@ -1090,8 +1069,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"]); @@ -1336,23 +1315,20 @@ void CXWM::getTransferData(SXSelection& sel) { return; } - // Store window ID before onWrite() - transfer may be erased during the call - const xcb_window_t targetWindow = transfer->incomingWindow; - int writeResult = sel.onWrite(); + const size_t transferIndex = std::distance(sel.transfers.begin(), it); + int writeResult = sel.onWrite(); if (writeResult != 1) return; - // 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()) + if (transferIndex >= sel.transfers.size()) return; - auto& updatedTransfer = *updatedIt; + Hyprutils::Memory::CUniquePointer& updatedTransfer = sel.transfers[transferIndex]; 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); @@ -1631,32 +1607,16 @@ 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 af1fa06a..1a922a45 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 = 0; - xcb_get_property_reply_t* propertyReply = nullptr; - xcb_window_t incomingWindow = 0; + int propertyStart; + xcb_get_property_reply_t* propertyReply; + xcb_window_t incomingWindow; bool getIncomingSelectionProp(bool erase); }; @@ -54,7 +54,6 @@ 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; @@ -165,8 +164,6 @@ 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 2f5007bd..c89d9d0b 100644 --- a/start/src/core/Instance.cpp +++ b/start/src/core/Instance.cpp @@ -1,24 +1,17 @@ #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; @@ -48,19 +41,9 @@ 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 - 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()); + 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); @@ -181,4 +164,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 6a44c8d0..6cf73a96 100644 --- a/start/src/core/State.hpp +++ b/start/src/core/State.hpp @@ -8,8 +8,6 @@ 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 deleted file mode 100644 index da66183e..00000000 --- a/start/src/helpers/Nix.cpp +++ /dev/null @@ -1,125 +0,0 @@ -#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 deleted file mode 100644 index edc01b19..00000000 --- a/start/src/helpers/Nix.hpp +++ /dev/null @@ -1,9 +0,0 @@ -#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 45a78357..74de393c 100644 --- a/start/src/main.cpp +++ b/start/src/main.cpp @@ -3,7 +3,6 @@ #include #include "helpers/Logger.hpp" -#include "helpers/Nix.hpp" #include "core/State.hpp" #include "core/Instance.hpp" @@ -17,13 +16,7 @@ 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 - -Additional arguments for start-hyprland: - --path [path] -> Override Hyprland path - --no-nixgl -> Force disable nixGL - --force-nixgl -> Force enable nixGL -)#"; +Any arguments after -- are passed to Hyprland. For Hyprland help, run start-hyprland -- --help or Hyprland --help)#"; // static void onSignal(int sig) { @@ -76,14 +69,6 @@ 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) @@ -92,15 +77,6 @@ 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 05cceee0..37aff70d 160000 --- a/subprojects/tracy +++ b/subprojects/tracy @@ -1 +1 @@ -Subproject commit 05cceee0df3b8d7c6fa87e9638af311dbabc63cb +Subproject commit 37aff70dfa50cf6307b3fee6074d627dc2929143 diff --git a/tests/desktop/Reserved.cpp b/tests/desktop/Reserved.cpp index b3942e32..8fbb7172 100644 --- a/tests/desktop/Reserved.cpp +++ b/tests/desktop/Reserved.cpp @@ -35,18 +35,4 @@ 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